初めまして、Aidemy研修生の尾熊です。 突然ですが、皆さんも大学や就職で自分の地元以外の人と会話をしたとき自分は普通だと思っていたのに方言だったー!!なんてことはありませんか??
今回は方言をベイズの定理を用いて方言を分類してみたいと思います。
環境
今回使った環境について紹介します。
- Anaconda 1.6.14 環境開発に使います
- Jupyter 5.4.0 プログラムを実行できます、実行結果が分かりやすいです。
- Tokenizer 形態素解析に使用します。
ベイズの定理の説明
『条件付き確率』に関して成り立つ定理でP(A) > 0 のとき次の式が成り立ちます。
P(B|A)=P(A|B)P(B)/P(A)
P(A)とは、Aが起こる確率 P(B)とは、事象 A が起きる前の、事象 B の確率 P(B|A)とは事象 A が起きた後での、事象 B の確率 式で書かれただけでは意味が分かりづらいのですね・・そこで例をひとつ示します A(P)を雨が降る確率だとし、B(P)を友達が約束の時間に遅れる確率とすると P(B|A)は雨が降っていた時に友達が約束の時間に遅れる確率となります。
ナイーブベイズ分類
ベイズの定理では、Aを入力文章とすると、Bはどこの方言であるかということになります。 ナイーブベイズ分類では、ある文章を方言ごとに分けるのに、文章中の単語の出現率を調べます。
ナイーブベイズ分類は全ての方言の確率を計算し、その中で一番確率の高い方言を結果として出力します。 そのためとてもシンプルな分類です。 しかし、少ないトレーニングデータでも正しい結果が出る。重要でない特徴量の影響を受けにくいなどの特徴があるので今回の分類に使用します。
形態素分析
さて、長かった前置きが終わりました、これから実際に文章を分解し解析をするコードを示します。 まずは形態素解析に必要なTokenizerをインストールしておきます。
import math, sys
from janome.tokenizer import Tokenizer
class BayesianFilter:
""" ベイジアンフィルタ """
def __init__(self):
self.words = set()
self.word_dict = {}
self.category_dict = {}
def split(self, text):
result = []
t = Tokenizer()
malist = t.tokenize(text)
for w in malist:
sf = w.surface
bf = w.base_form
if bf == '' or bf == "*": bf = sf
result.append(bf)
return result
def inc_word(self, word, category):
if not category in self.word_dict:
self.word_dict[category] = {}
if not word in self.word_dict[category]:
self.word_dict[category][word] = 0
self.word_dict[category][word] += 1
self.words.add(word)
def inc_category(self, category):
if not category in self.category_dict:
self.category_dict[category] = 0
self.category_dict[category] += 1
方言を分類する
方言を分類するために単語をスコア化し方言ごとに分ける。
def fit(self, text, category):
""" 文章の学習 """
word_list = self.split(text)
for word in word_list:
self.inc_word(word, category)
self.inc_category(category)
def score(self, words, category):
score = math.log(self.category_prob(category))
for word in words:
score += math.log(self.word_prob(word, category))
return score
def predict(self, text):
best_category = None
max_score = -sys.maxsize
words = self.split(text)
score_list = []
for category in self.category_dict.keys():
score = self.score(words, category)
score_list.append((category, score))
if score > max_score:
max_score = score
best_category = category
return best_category, score_list
def get_word_count(self, word, category):
if word in self.word_dict[category]:
return self.word_dict[category][word]
else:
return 0
def category_prob(self, category):
sum_categories = sum(self.category_dict.values())
category_v = self.category_dict[category]
return category_v / sum_categories
def word_prob(self, word, category):
n = self.get_word_count(word, category) + 1
d = sum(self.word_dict[category].values()) + len(self.words)
return n / d
方言ごとのスコアを計算した時に、確率を掛け合わせ続けると値が小さくなりすぎる可能性があります。 なので、log関数を使用して対数を求めています。
学習
ベイジアンフィルタは機械学習の教師あり学習になるため最初にいくつかの文章を学習させる必要があります。 今回は広島弁、大阪弁、博多弁、沖縄弁、北海道弁の5つに分類できるようにしました。 一つの方言につき20個の例文を学習させています。
bf = BayesianFilter()
bf.fit("寝不足じゃけぶちたいぎー","広島弁")
bf.fit("じゃけえ、ゆったじゃろ","広島弁")
bf.fit("お盆は帰ってきんさい","広島弁")
bf.fit("たちまち","広島弁")
bf.fit("汚な!あんたの部屋わやくちゃじゃ","広島弁")
bf.fit("ワレぶちまわしたろうか","広島弁")
:
:
bf.fit("道産子(どさんこ)","北海道弁")
bf.fit("いつもあんたのことを考えてるっしょ","北海道弁")
bf.fit("あんたのこと好きだけどさ、どうしたら良いっしょか","北海道弁")
実行結果
学習には使用していない文章を使ってどの方言か分類できているか検証してみます。 文章は大阪弁を使っているので、結果に大阪弁が表示されると正解になります。
pre, scorelist = bf.predict("今週末ユニバ行けへん?")
print("結果",pre)
print(scorelist)
結果 大阪弁
[('広島弁', -37.14937973016429), ('大阪弁', -30.792911151724105), ('博多弁', -36.56367867243777), ('沖縄弁', -36.90188366791113), ('北海道弁', -38.39497824243125)]
出力結果の解説をすると 左が方言名で、右の数字が単語の出現率となっています。出現率はマイナスなので、値が小さいほど出現率は高くなります。 今回は大阪弁の出現率が一番高いので、ちゃんと大阪弁に分類されていますね‼︎ 他の方言でも試してみましたが、分類できました。
まとめ
最初はMLP(多層パーセプトロン)で分類をしようと思っていたのですが、方言で書かれた文章は以外に少なく断念しました。 良く考えると方言で話すことはあっても、文章を書くことは少ないのでデータが少なかったのかなと思いました。
学習させる文章を変えると分類に成功する確率が変わってきて面白かったです。 簡単に実装できるので興味が湧いた方はぜひやってみてください