Aidemy Blog

株式会社アイデミーのブログです。機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる技術記事や、キャリア記事等を発信しています

ベイズの定理を用いて方言を分類してみた

初めまして、Aidemy研修生の尾熊です。

 
突然ですが、皆さんも大学や就職で自分の地元以外の人と会話をしたとき自分は普通だと思っていたのに方言だったー!!なんてことはありませんか??
f:id:thipot:20180717110416j:plain

今回は方言をベイズの定理を用いて方言を分類してみたいと思います。

目次

1.     環境
2.    ベイズの定理の説明
3.    ナイーブベイズ分類
4.    形態素解析
5.    方言を分類する
6.    学習
7.    実行結果
8.    まとめ
9.    参考文献

1.  環境

今回使った環境について紹介します。

Python 3.6.4      プログラミング言語
Anaconda 1.6.14   環境開発に使います
Jupyter 5.4.0     プログラムを実行できます、実行結果が分かりやすいです。
Tokenizer         形態素解析に使用します。

 

2. ベイズの定理の説明

 『条件付き確率』に関して成り立つ定理で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)は雨が降っていた時に友達が約束の時間に遅れる確率となります。

3. ナイーブベイズ分類

ベイズの定理では、Aを入力文章とすると、Bはどこの方言であるかということになります。
ナイーブベイズ分類では、ある文章を方言ごとに分けるのに、文章中の単語の出現率を調べます。

ナイーブベイズ分類は全ての方言の確率を計算し、その中で一番確率の高い方言を結果として出力します。
そのためとてもシンプルな分類です。
しかし、少ないトレーニングデータでも正しい結果が出る。重要でない特徴量の影響を受けにくいなどの特徴があるので今回の分類に使用します。

4. 形態素分析

さて、長かった前置きが終わりました、これから実際に文章を分解し解析をするコードを示します。

まずは形態素解析に必要な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

5. 方言を分類する

方言を分類するために単語をスコア化し方言ごとに分ける。

    # 文章を学習する
    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関数を使用して対数を求めています。

6. 学習

ベイジアンフィルタは機械学習の教師あり学習になるため最初にいくつかの文章を学習させる必要があります。
今回は広島弁、大阪弁、博多弁、沖縄弁、北海道弁の5つに分類できるようにしました。
一つの方言につき20個の例文を学習させています。

bf = BayesianFilter()

#広島弁
bf.fit("寝不足じゃけぶちたいぎー","広島弁")
bf.fit("じゃけえ、ゆったじゃろ","広島弁")
bf.fit("お盆は帰ってきんさい","広島弁")
bf.fit("たちまち","広島弁")
bf.fit("汚な!あんたの部屋わやくちゃじゃ","広島弁")
bf.fit("ワレぶちまわしたろうか","広島弁")
           :
             :
#北海道弁
bf.fit("道産子(どさんこ)","北海道弁")
bf.fit("いつもあんたのことを考えてるっしょ","北海道弁")
bf.fit("あんたのこと好きだけどさ、どうしたら良いっしょか","北海道弁")

7. 実行結果

学習には使用していない文章を使ってどの方言か分類できているか検証してみます。
文章は大阪弁を使っているので、結果に大阪弁が表示されると正解になります。

pre, scorelist = bf.predict("今週末ユニバ行けへん?")
print("結果",pre)
print(scorelist)

結果 大阪弁
[('広島弁', -37.14937973016429), ('大阪弁', -30.792911151724105), ('博多弁', -36.56367867243777), ('沖縄弁', -36.90188366791113), ('北海道弁', -38.39497824243125)]

出力結果の解説をすると
左が方言名で、右の数字が単語の出現率となっています。出現率はマイナスなので、値が小さいほど出現率は高くなります。
今回は大阪弁の出現率が一番高いので、ちゃんと大阪弁に分類されていますね‼︎


他の方言でも試してみましたが、分類できました。

8. まとめ


最初はMLP(多層パーセプトロン)で分類をしようと思っていたのですが、方言で書かれた文章は以外に少なく断念しました。
良く考えると方言で話すことはあっても、文章を書くことは少ないのでデータが少なかったのかなと思いました。

学習させる文章を変えると分類に成功する確率が変わってきて面白かったです。
簡単に実装できるので興味が湧いた方はぜひやってみてください

9. 参考文献

books.google.co.jp