アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

試行錯誤その2:自然言語ネガポジ辞書管理クラスをラップして学習・推論するクラスにする

ちょっとしたアイディアにもとづいて「ネガポジ辞書」を管理するクラスをpythonで作りました。

arakan-pgm-ai.hatenablog.com

 

作成したクラスの仕様をざっくりとおさらいします。

 

アイディアはごくシンプル。

 

単語ではなく、文章をもとデータにする。

 

その文章単位でネガティブかポジティブかを指定します。

 

で・・、ネガティブな文章に登場する単語はネガティブなスコアにカウントする。

 

逆にポジティブな文章に登場する単語はポジティブにカウントする。

 

それを繰り返すことを学習と考えれば、同じ単語でも、ポジティブ・ネガティブの各文章に登場する頻度によって、例えばポジティブ 60%、ネガティブ40%みたいなスコアをもった辞書になるというものです。

 

クラスの名前は、「NgpgDict」です。

 

メソッドとしてはこんな感じ。

 

NgpgDict()のコンストラクタで、保存済の辞書を読み込む。

 

add_p(単語、カウント)で、対象単語のポジティブカウントを加算して学習。

 

add_n(単語、カウント)で、対象単語のネガティブカウントを加算して学習。

 

save() で学習した結果を辞書に反映。

 

今回は、この辞書管理クラスを使って、実際に教師付き学習で与えられた文章をもとに学習して、辞書を育てていく機能を実装していきます。

 

2018/02/08 追記

>この記事の後、さらにいろいろ試行錯誤がありました。

>現時点では学習データを作り直して辞書の作り直しをしています。

理由はこちらの記事に書いてます。

>それに伴い、以下に書いている仕様も、一部見直しを検討してます。

>途中経過という感じで見てもらえればと思います。  

 

まずはインターフェースにあたる部分です。

 

今回作るのは、その辞書管理クラスをラップして、学習と予測をするメソッドを実装するものです。

 

クラス名は「NaPg」にします。

import os
import csv
import re
import numpy as np
from janome.tokenizer import Tokenizer
from ngpjdict import NgpgDict

class NgPg:
    def __init__(self):
        self.__dic = NgpgDict()
        self.__tok = Tokenizer()
        self.__pat = r'名詞|動詞|形容詞|助動詞'
        self.__re = re.compile(self.__pat)
 
    def fit_from_csv(self,in_csv_name):
        # CSVから読み込む
        l_in = self.__list_from_csv(in_csv_name)
        self.__fit__(l_in)
        return len(l_in)

    def fit_from_list(self,l_in):
        self.__fit__(l_in)
        return len(l_in)

 

前回作った「NgpgDict」と「janome」をインポートして、コンストラクタ(__init__)で、 インスタンスを生成しています。

 

インタフェースは、とりあえず、fit(学習)と、predict(予測)の2つだけにします。

 

ただ、インプットデータをCSVファイルから読み込むものと、リスト([[]])から読み込むものと2つ作ります。

 

上記のソースのメソッド名を見れば、どれがどれかは、すぐわかると思います。

 

CSV または リストで期待するフォーマットは以下です.

 

文章,正解ラベル

 

正解ラベルは、ポジティブの場合は「0」または「P」または「p」。

 

ネガティブの場合は、「1」または「N」または「n」です。

 

例です。

いまの僕には勢いがある。新しいことを始めてもうまくいきそうな気がする。,P
僕には勢いがない。新しいことをやってもうまくいかない気がする。,N

 

予測(predict)の場合は、正解ラベルはあっても無視されます。

 

次は、ネガポジ辞書を育成(学習)する部分です

 

該当部分のソースです。

    def __fit__(self,l_in):
        # リストが空でない場合のみ学習する
        if(len(l_in) > 0):
            for i in range(len(l_in)):
                # 1行ずつ形態素分解
                o_malist = self.__tok.tokenize(l_in[i][0])
                # 単語の数だけ学習する
                if(str(l_in[i][1]) in [u'0',u'P',u'p']):# ポジティブ
                    for n in o_malist:
                        if(self.__re.match(n.part_of_speech)):
                            self.__dic.add_p(n.surface,1)
                elif(str(l_in[i][1]) in [u'1',u'N',u'n']):# ネガティブ
                    for n in o_malist:
                        if(self.__re.match(n.part_of_speech)):
                            self.__dic.add_n(n.surface,1)
                else:
                    # ラベルがポジでもネガでもないときは両方に加算する
                    for n in o_malist:
                        if(self.__re.match(n.part_of_speech)):
                            self.__dic.add_n(n.surface,1)
                            self.__dic.add_p(n.surface,1)
            self.__dic.save()

 

2018/02/06追記

>ソースにバグがあり、修正しましたm(_ _)m 

 

まず、一発目なので、ごくシンプルにやってます。

 

janome形態素解析をして、単語に分解します。

o_malist = self.__tok.tokenize(l_in[i][0])

 

そして、正解ラベル(l_in [i][1])で、ポジティブかネガティブかを判断して、ポジティブの場合はその文章内の単語で「名詞|動詞|形容詞|助動詞」であるものを辞書のポジティブスコアを加算します。

 

ネガティブだった場合は、その逆です。

 

そして、ポジティブでもネガティブでもなければ、両方に同じだけのスコアを加算します・・イーブンであるということですね。

 

こうして、大量の文章を学習させていけば、単語ごとのポジティブスコアとネガティブスコアのバランスが、あるべき数値に収束していくであろう。

 

そういうシンプルな考え方ですね。

 

どれくらいの文章を学習させれば、それなりになるのか?

 

そのへんの予測はつきませんが、まあ、とりあえず、学習する機能はできたので、ネットから文章データを取得して、それに自分でネガポジの判定をくっつける形で、どんどんデータを作って学習させてみます。

 

ある程度・・辞書の単語数が数万語くらいかな・・になったら、それを使って予測して、結果を見ながら改善するというやり方ですすめてみます。

 

まずは、データをかき集めないと・・ですね。

 

あ・・CSVを読み込むメソッドのソース忘れてました

最後にソースだけのせておきます。

 

特にこれということはないですが、こんな感じです。

    # CSVファイルを読み込んでリストに変換する
    def __list_from_csv(self,csvfile):
        l_tmp =[]
        try:
            with open(csvfile,'r',encoding='utf8') as csvf:
                csvreader = csv.reader(csvf)
                return list(csvreader)
        except:
            return l_tmp