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

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

試行錯誤その3:辞書を作って、自然言語の1行データのネガポジ判定を試してみる(一発目)

前回、シンプルなアイディアを実施し、オリジナルネガポジ辞書管理クラスをラップして、学習・推論するクラスを作ってみました。

 

それを使って、かき集めた元ネタ文章にネガティブ・ポジティブをマーキングしたデータを学習させました。

 

まずは、あまり偏りとか気にせずに、単語を辞書に溜め込むことを優先しています。

 

割りと地道な作業で、正直、途中で飽きかけましたけどね(笑)

 

辞書の確認のために、いくつか適当な文章で判断をしてみる

 

とりあえず、登録単語数6万件を目標にしてみました。

 

その数字に別に意味はありません。

 

なんとなくそれくらい・・というゆるーい目標です。

 

実は、ブログに記事を書く前から文章を収集する作業ははじめてました。

 

ちょうど正月から始めたので、だいたい1月ちょっとかかりました。

 

それで、さっき学習して、件数を確認したら「62478」。

 

ああ、いい感じ。

 

さっそく、辞書を使った予測をやってみます。

 

正解率は全体としては90%程度のまあまあだったけど

 

100個ほどの適当な文章でネガポジ判断をさせて、答え合わせをした結果です。

 

だいたい・・90%ちょっとくらいの正解率。

 

なかなかに優秀ではあるのですが・・。

 

どうも、失敗する文章に決まった傾向があるので、そこが気に入りません。

 

そこ対する対策が必要です。

 

以後、その間違うパターンと、どんな対策をしたかを書いていきます。

 

今回は予測部分のソースは最後にのせます。

 

2018/02/08 追記

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

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

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

>以下に書いている対策も、一部見直してます。

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

  

最初のハードルはネガティブ言葉の否定でポジティブにするパターン

 

ひとつめの傾向が「ネガティブ言葉を否定してポジティブにする文章を間違う」です。

 

例をいくつかあげます。

  • 世の中、悪いことばかりじゃないよ。
  • あなたのことは、嫌いではありません。
  • 結果は残念だけど、勝負に負けたわけじゃない。

 

これは考えてみれば当たり前です。

 

例えば、最初の「世の中、悪いことばかりじゃないよ」です。

 

これはjanomeの標準辞書だと以下のように展開されます。

世の中:名詞,一般,*,*
、:記号,読点,*,*
悪い:形容詞,自立,*,*
こと:名詞,非自立,一般,*
ばかり:助詞,副助詞,*,*
じゃ:助詞,副助詞,*,*
ない:助動詞,*,*,*
よ:助詞,終助詞,*,*

 

名詞、動詞、形容詞、助動詞だけを対象にしているので、「世の中」「悪い」「こと」「ない」の4つが評価対象になります。

 

この「ない」という助動詞はネガティブな文章に現れる頻度が圧倒的に多いので、スコアは当然ネガティブ寄りです。

 

なので、「好き」とか「楽しい」などのポジティブよりの言葉の否定する文章(例えば、「君はとても真面目で優秀だけど、僕は嫌いだ」とか)では非常にうまく働いているみたいで、ほぼ100%正解でした。

 

でも、ネガティブな言葉にネガティブである「ない」を重ねても、ポジティブにはなりません。

 

これは、この辞書だけで処理する限界ですね。

 

なので、この対処として予測するロジックに「反転語」をとりいれました。

 

例えば「ではない」「ではありません」などの言葉です。

 

とりあえず、ポジティブ言葉の否定はうまくいっているので、全体の判定がネガティブの時に上記の反転語があれば、文章全体の判定を反転させるロジックで対応することにします。

 

2つ目の問題は対象にする品詞の不足です

 

上記の対策を施しても、なくならない問題はありました。

 

それは、ごく「不」のような接頭語でポジティブからネガティブに転換する単語があった場合です。

 

例えば、「受験は不合格」です。

 

どう見てもネガティブなんですが、これをポジティブと判断してしまいます。

 

この原因は2つ。

 

これをjanomeが処理すると、こうなります。

受験:名詞,サ変接続,*,*
は:助詞,係助詞,*,*
不:接頭詞,名詞接続,*,*
合格:名詞,サ変接続,*,*

 

前回の処理ルーチンだと、接頭語を判断対象にしていないので、「不」が無視されて、「受験」と「合格」だけで判断してしまいます。

 

そりゃあ、ポジティブになりますね。

 

なので、処理対象の品詞に「接頭詞」を追加して、辞書を再生成します。

 

修正部分はコンストラクタ(__init__)の部分なので、最後の方にソースを書きます。

 

最後に短い文章に対する正解率の低さの問題が残った

 

最後に残ったのは、ごく短い文章の対応です。

 

やってみると、長めの文章ほど正解率が高いのです。

 

対して、含まれる単語数が2つとか3つの短い文章だと、正解率が70%程度になってしまいます。

 

例えば「夢も希望もない」。

 

最後に「ない」で打ち消しているけれど、「夢」と「希望」という明らかにポジティブな言葉があり、単純に単語数で比較すると、ポジティブに判断されてしまいます。

 

これはウエイトで対応します。

 

色々な文章セットで試した結果、文章の後ろに登場する単語に1.1倍していく方法が一番正解率が高かったので、今回はそれを採用しました。

 

とりあえず、今日のところはこんなものかな・・

 

一応、自分で思いつくままに準備したテストデータだと100%正解になるようになりました。

 

一旦、ひとくぎりですね。

 

予測する部分のソースはこんな感じになりました。

    def __predict__(self,l_in):
        l_result = []
        n_wait = 1.0
        # リストが空でない場合のみ推論する
        if(len(l_in) > 0):
            for i in range(len(l_in)):
                prate = 0
                nrate = 0
                n_wait = 1
                l_line = []
                # 1行ずつ形態素分解
                o_malist = self.__tok.tokenize(l_in[i][0])
                for n in o_malist:
                    if(self.__re.match(n.part_of_speech)):
                        n_wait = n_wait * 1.1
                        prate = prate + (self.__dic.get_prate(n.surface) * n_wait)
                        nrate = nrate + (self.__dic.get_nrate(n.surface) * n_wait)
                l_line.append(l_in[i][0])
                if(prate > nrate):
                    l_line.append('P')
                else:
                    if(self.__nre.search(l_in[i][0])):
                       l_line.append('P')
                    else:   
                       l_line.append('N')
                l_result.append(','.join(l_line))
        return l_result

 

単語ごとにスコアを加算していって、最後に比較しているだけです。

 

ただ、前の方の対策のために、ネガポジ判断を反転させる必要があるので、そこは「 if(self.__nre.search(l_in[i][0])」で判断してます。

 

この判断のために、反転語のパターンが必要になったのと、品詞の追加もあって、コンストラクタ(__init__)を以下のように変更する必要があります。

    def __init__(self):
        self.__dic = NgpgDict()
        self.__tok = Tokenizer()
        self.__pat = r'名詞|動詞|形容詞|助動詞|接頭詞'
        self.__re = re.compile(self.__pat)
        self.__npat = r'ではない|じゃない|ではありません|じゃありません|になれない|にはなれない'
        self.__nre = re.compile(self.__npat)

 

 

まあ、まだまだ、辞書と評価メソッドを改良していく必要はありそうですが。