自然言語処理の話題でよく目にするネガポジ判定。
単語感情極性なんて言い方もあるみたいですが・・。
ようするに、文章を解析してネガティブな文章かネガティブな文章かの2択で判断するというだけのことなのですけど。
自然言語処理の基本の勉強を兼ねて、いろいろ試してみるには、とっつきやすいテーマですし、面白そうです。
早速、初めてみようかと・・。
まずはネガポジ辞書を作らないとはじまらない
ネガポジ判断には、ネガポジ辞書が必要です。
作成して公開いただいているものもあるので、それを使う手もあるかなとは思いました。
例えば「日本語評価極性辞書」とか「単語感情極性対応表」です。
でも、今回は使わないことにしました。
理由は単純で、既存の辞書に勝手に新しい語を追加できないから。
あと、ある単語がネガティブかポジティブかの判断は人によって違うんじゃないかな・・と思ったということがあります。
やっぱり、自分の判断で辞書を作って判定して、自分が納得できるかでやってみないとわからないです。
なので、ネガポジ辞書を自前で作る方法を考えてみます。
ネガポジ辞書管理の仕様はシンプルに
辞書の仕様はとりあえず、シンプルにしてみます。
アイディアはこうです。
単語ではなく、文章をもとデータにする。
その文章単位でネガティブかポジティブかを指定します。
で・・、ネガティブな文章に登場する単語はネガティブなスコアにカウントする。
逆にポジティブな文章に登場する単語はポジティブにカウントする。
それを繰り返すことを学習と考えれば、同じ単語でも、ポジティブ・ネガティブの各文章に登場する頻度によって、例えばポジティブ 60%、ネガティブ40%みたいなスコアができあがります。
それを適用して、評価する文章に登場する単語のスコアを加算していけば、合計でポジティブかネガティブを判定できるんじゃないか?
ということです。
悪くない気はしますが、試してみます。
pythonのクラスでネガポジ辞書管理を実装してみます
名前は「NgpgDict」にします。
辞書には、単語ごとに、ポジティブ・ネガティブそれぞれの出現回数を保持するカウンターと、都度再計算してもとめるレート(ポジティブレート、ネガティブレート)を持ちます。
学習時には、ポジティブとして加算(add_p)とネガティブとして加算(add_n)して、都度、レートを計算しておきます。
学習結果はJSON形式の辞書ファイルに保存(save())します。
その辞書はコンストラクタで読み込むようにします。
それで文章のネガポジ判断をする時は、ポジティブレート(get_prate)と、ネガティブレート(get_nrate)を取り出して、加算していって、文字数カウントした結果か、TF-IDF等で処理した結果に掛け合わせて、ネガポジ判定する・・みたいな使い方を想定した仕様にします。
まず、クラス定義とコンストラクトと保存(save())のソースです。
当面、自分しか使わないので、辞書ファイルを直書きしたりしてるのは、ご愛嬌とさせてください。
from janome.tokenizer import Tokenizer # 形態素解析用 import os import json class NgpgDict: def __init__(self): self.__save_file_name = "ngpgdict.json" self.__total = 'Total' self.__pcount = 'pcount' self.__ncount = 'ncount' self.__prate = 'prate' self.__nrate = 'nrate' if(os.path.isfile(self.__save_file_name)): fr = open(self.__save_file_name,"r") self.__word_dict = json.load(fr) fr.close() else: self.__word_dict = {} self.__word_dict.setdefault(self.__total,{}) self.__word_dict[self.__total].setdefault(self.__pcount,0) self.__word_dict[self.__total].setdefault(self.__ncount,0) self.__word_dict[self.__total].setdefault(self.__prate,0) self.__word_dict[self.__total].setdefault(self.__nrate,0) def save(self): fw = open(self.__save_file_name,"w") json.dump(self.__word_dict,fw,indent=4) fw.close()
あと、加算して都度レートを計算する部分ですね。
初めて登場する単語のための辞書の初期化とかしてるので、ごちゃごちゃ見えますが、やってることはカウンタを加算して、比率を求めなおしているだけです。
def add_p(self,word,value): self.__word_dict.setdefault(word,{}) self.__word_dict[word].setdefault(self.__pcount,0) self.__word_dict[word].setdefault(self.__ncount,0) self.__word_dict[word].setdefault(self.__prate,0) self.__word_dict[word].setdefault(self.__nrate,0) pc = self.__word_dict[word][self.__pcount] self.__word_dict[word][self.__pcount] = pc + value tc = self.__word_dict[self.__total][self.__pcount] self.__word_dict[self.__total][self.__pcount] = tc+ value pcu = self.__word_dict[word][self.__pcount] ncu = self.__word_dict[word][self.__ncount] tcu = self.__word_dict[word][self.__pcount] + self.__word_dict[word][self.__ncount] self.__word_dict[word][self.__prate] = pcu / tcu self.__word_dict[word][self.__nrate] = ncu / tcu def add_n(self,word,value): self.__word_dict.setdefault(word,{}) self.__word_dict[word].setdefault(self.__pcount,0) self.__word_dict[word].setdefault(self.__ncount,0) self.__word_dict[word].setdefault(self.__prate,0) self.__word_dict[word].setdefault(self.__nrate,0) pc = self.__word_dict[word][self.__ncount] self.__word_dict[word][self.__ncount] = pc + value tc = self.__word_dict[self.__total][self.__ncount] self.__word_dict[self.__total][self.__ncount] = tc+ value pcu = self.__word_dict[word][self.__pcount] ncu = self.__word_dict[word][self.__ncount] tcu = self.__word_dict[word][self.__ncount] + self.__word_dict[word][self.__pcount] self.__word_dict[word][self.__prate] = pcu / tcu self.__word_dict[word][self.__nrate] = ncu / tcu
getterにあたるレートを取り出す部分です。
文字が辞書になかったら、0.5(要するに中立)を返すようにしてます。
def get_prate(self,word): if(word not in self.__word_dict): return 0.5 else: return self.__word_dict[word][self.__prate] def get_nrate(self,word): if(word not in self.__word_dict): return 0.5 else: return self.__word_dict[word][self.__nrate]
あと、内容を全部書きだす処理(list_all)と辞書を初期化する(clear())です。
def list_all(self): self.__recursive_dict__(self.__word_dict) def __recursive_dict__(self,argd): for k in sorted(argd.keys(),reverse=False): print("{0}:{1}:{2}".format(k,self.__pcount,argd[k][self.__pcount])) print("{0}:{1}:{2}".format(k,self.__ncount,argd[k][self.__ncount])) print("{0}:{1}:{2}".format(k,self.__prate,argd[k][self.__prate])) print("{0}:{1}:{2}".format(k,self.__nrate,argd[k][self.__nrate])) def clear(self): self.__recursive_clear__(self.__word_dict) def __recursive_clear__(self,argd): for k in sorted(argd.keys(),reverse=False): if(isinstance(argd[k],dict)): self.__recursive_clear__(argd[k]) else: if(k != self.__total): argd[k] = 0
とりあえず、このくらいあれば、基本的なことはできるでしょう。
動作確認をしてみます。
n = NgpgDict() n.clear() n.add_p('てすと',1) n.add_n('てすと',2) n.add_p('てすと',3) n.add_n('てすと',1) n.add_p('てらす',3) n.add_n('てらす',2) n.add_n('困った',2) n.add_n('困った',5) print("positive-rate:{0} / negative-rate:{1}".format(str(n.get_prate('てすと')),str(n.get_nrate('てすと')))) print("positive-rate:{0} / negative-rate:{1}".format(str(n.get_prate('てらす')),str(n.get_nrate('てらす')))) print("positive-rate:{0} / negative-rate:{1}".format(str(n.get_prate('困った')),str(n.get_nrate('困った'))))
結果はこんな感じ。
positive-rate:0.5714285714285714 / negative-rate:0.42857142857142855
positive-rate:0.6 / negative-rate:0.4
positive-rate:0.0 / negative-rate:1.0
検算してみると、あってる。
とりあえず、初期化して<n = NgpgDict()>、学習して<add_p,add_n>、保存する<save()>の3つだけ使ってグルグルまわしておけば良いので、学習させるプログラムもシンプルに書けそうな感じにはできましたし。
一旦、第一弾としては悪くないかな。