2012年1月7日土曜日

[python] これは誰のせりふでしょう?→「また負けた・・・働いてないのに また負けちゃった・・・・・・ギャンブル負けちゃった・・・」

分類器とは、入力の特徴を元にクラス分けするプログラムの事。メールスパムフィルタ、テキストのカテゴリ分け、形態素解析の品詞推定などに使われている。今回調べてみた「単純ベイズ分類器」のほかにも、「決定木」、「最大エントロピー分類器」とかがあるみたい。

ここで丁寧に解説されている。ほぼほぼパクリw。
ナイーブベイズを用いたテキスト分類

統計的手法に基づくスパムフィルタの設計と実装
あと、NLTK本の6章。


分類器の動作手順

良くある分類器の処理の流れ。

① 素性抽出
まず、入力から素性を抽出する。ここでの入力は、任意の文章を想定している。素性とは、入力を分類をするための指標となる文字列で、どのように分類したいかによって変わる。たとえば、ブログのラベリングであれば、文章中の頻出語句を素性として使えるだろうし、話し手の性別を判定したいのであれば、文の語尾を素性として使えるかもしれない。ようは、分類に大きな影響を与えるものを素性として選ぶ。

② 機械学習
機械学習では、素性とラベルを対応付ける分類モデルを作成する。分類モデルの種類としては、決定木や単純ベイズ分類器のほかにも、ニューラルネットワーク、サポートベクターマシンとかかなりいろいろあるっぽい。分類モデルを作成するためには、もととなる素性とラベルの組み合わせデータが必要で人の手で作成される事が多いらしい。このデータから分類モデルを作成する事を訓練といい、訓練が必要となる分類モデルを教師あり分類と教師あり~って言われる。

③ 分類
入力から素性を抽出して、機械学習で作成した分類モデルを使って入力を分類する。


単純ベイズ分類器の仕組み


単純ベイズ分類器では、「文章Xが与えられたとき、そのラベルAである条件付き確率」を計算し、最も確率が高いラベルを文章に付与する。この条件付き確率 P(label|doc)を計算する。

① ベイズの定理より。

※ P(doc)はどのカテゴリでも共通なので無視。

② 文章を素性の集合と考え、それぞれの素性は独立に(ベイズ仮定)


③ P(label), P(word_i|label)を計算する。
P(label)は、「labelに属する文書 / 総文書数」で求められる。
P(word_i|label)は、「対象文書に含まれるword_iの数 / labelに属する文書の総単語数」で求められる。



※ ゼロ頻度問題に対応するため、P(wordi|label)の計算の際、出現回数に1を加えるラプラススムージングを行う。

④ アンダーフロー回避のため、logをとる。

※ logをとっても,P(label|doc)の大小関係は変化しないため、判定に影響はない。

⑤ ④に③を代入し、P(label|doc)を求める。


単純ベイズ分類器の実装


せっかくベイズ分類ができるので、
カイジに出てきたキャラクターの台詞を学習させ、
他のせりふが誰のものか判別できるかどうかためしてみた。

ここの台詞を学習させた。
心に刻め!!カイジの名言集

ソースコードは結構長いので最後に貼り付ける。

また、似たような事は潜在的意味解析でもできるが、
潜在的意味解析で判定できるのは、「与えた台詞が、誰のどの台詞に近いか」であって、
分類器でできる「与えた台詞が、誰の台詞に近いか」とは異なる。



まずは、カイジ君
「また負けた・・・働いてないのに また負けちゃった・・・・・・ギャンブル負けちゃった・・・」

結果
[karino@localhost simple_bayesian_classifier]$ ./simple_bayesian.py 

また負けた・・・働いてないのに また負けちゃった・・・・・・ギャンブル負けちゃった・・・

カイジ: -48.1215877591
利根川: -51.8378671316
会長  : -52.5295179405
遠藤  : -52.0766103197
大槻  : -52.5066907554

label :  カイジ
お。ちゃんとカイジと判定された!

次は、あえての大槻班長。
「大槻班長:食べ終わったら、奴はとりあえず満足してこう考えるだろう。明日からがんばろう。明日から節制だと。」

[karino@localhost simple_bayesian_classifier]$ ./simple_bayesian.py 

食べ終わったら、奴はとりあえず満足してこう考えるだろう。明日からがんばろう。明日から節制だと。

カイジ: -57.5804609164
利根川: -56.3075993467
会長  : -57.4567716257
遠藤  : -56.8765245825
大槻  : -55.7732979831

label :  大槻

おぉ。ちゃんと班長と判定されたw。かなり恣意的な気もするが。

最後を飾るのはもちろん利根川先生のこの台詞。
「ファックユー ぶち殺すぞ ゴミめら!」

[karino@localhost simple_bayesian_classifier]$ ./simple_bayesian.py 

ファックユー ぶち殺すぞ ゴミら!

カイジ: -24.2264367345
利根川: -23.9961446128
会長  : -24.4275136119
遠藤  : -23.9181559224
大槻  : -25.1875317303

label :  遠藤勇次
あぁ、遠藤さんになっちゃった。近いといえば近いが。

まぁ、こんな様な事ができるってデモでした。


#!/usr/bin/python
# coding=utf-8

import MeCab
import math
import sys
import codecs
from decimal import Decimal
from collections import defaultdict
reload(sys)
sys.setdefaultencoding('utf-8')
sys.stdout = codecs.getwriter('utf-8') (sys.stdout)

kaiji =[
        ["カイジ","おまえは100%成功しないタイプ…!"],
        ["利根川幸雄","一生迷ってろ…!そして失い続けるんだ…貴重な機会(チャンス)をっ!"],
        ["大槻","明日からがんばるんじゃない…今日…今日だけがんばるんだっ…!今日をがんばった者…今日をがんばり始めた者にのみ…明日が来るんだよ…!"],
        ["カイジ","奇跡なんて望むな!「勝つ」ってことは…そんな神頼みなんかじゃなく…具体的な勝算の彼方にある…現実だ…!勝つべくして勝つ…!"],
        ["カイジ","疑ってるうちはまだしもそれを口にしたら…戦争だろうがっ!"],
        ["利根川幸雄"," 金は命より重い…!そこの認識をごまかす輩は生涯地を這う…!"],
        ["兵藤和尊","大詰めで弱い人間は信用できぬ…!つまりそれは管理はできても勝負のできぬ男…平常時の仕事は無難にこなしても緊急時にはクソの役にも立たぬということだ!]",],
        ["兵藤和尊","剥げたな…お前の化けの皮…二流だ…しょせんお前は指示待ち人間…!",],
        ["カイジ","胸を張れっ!手痛く負けた時こそ…胸をっ…!",],
        ["利根川幸雄"," 大人は質問に答えたりしない それが基本だ",],
        ["カイジ","堂々といけっ…!やばい時ほど堂々と…",],
        ["カイジ","ピンチだけど…チャンスッ…",],
        ["遠藤勇次","おまえの毎日って今……ゴミって感じだろ?無気力で自堕落で非生産的",],
        ["岡林","友情や口約束でもらえるものは旅先からの絵ハガキや土産…あるいは思い出の品というガラクタ…そんな程度のものだ…",],
        ["利根川幸雄","なめてなどいない…熟知しているだけだ…人間の無力について…!",],
        ["カイジ","変でいい、変でなきゃダメだ…狂ってなきゃ、逸脱してなきゃ悪魔は殺せない…!常軌を逸してこそ開かれる、勝ちへの道が…!",],
        ["兵藤和尊","借金における誠意なんて、これはもう誰が考えたって一つしかないのだ…内臓を売ろうと、強盗をしでかそうと…何をしてもいいから、要するに…期限までに金を返すことだっ…!",],
        ["カイジ","敗者は失うっ…!それをねじ曲げたら…なにがなにやらわからない…受け入れるべきだっ…!負けを受け入れることが…敗者の誇り…オレは…負けをぼかさないっ…!",],
        ["カイジ","運・勘・人に頼る勝負はやめだ…そういうノータリンな振る舞いはもうやめ…自分の頭で考え…勝つべくして勝つ…",],
        ["利根川幸雄","よく戦ったからじゃない…彼らは勝った、ゆえに今、そのすべて…人格まで肯定されている…!",],
        ["カイジ","いっちゃ悪いが、奴ら正真正銘のクズ…負けたからクズってことじゃなくて可能性を追わないからクズ…",],
        ["カイジ"," 目先を追うな!いい加減気がつけ!耐えることなくして勝利はないんだっ!",],
        ["カイジ","他人なんか関係ねえんだよ…!オレだっ…!オレだっ…!オレなんだっ…!肝心なのはいつも…!オレがやると決めてやる…ただそれだけだっ…!",],
        ["遠藤勇次","一体「何」を「いつまで」待つつもりだ…?こんな薄汚いアパートで…貧しいバイトをして…半ば眠ったような意識で鬱々と「待つ」…?そういうのを無為っていうんだっ…!",],
        ["カイジ"," 落とさなきゃ落とされる…この仕組みは…この世の姿そのもの…基本も基本…大原則だっ…!",],
        ["カイジ","できるかどうかじゃない!やるんだっ!勝つために生きなくてどうするっ…!",],
        ["兵藤和尊","命はもっと粗末に扱うべきなのだ…!命は、生命は…丁寧に扱いすぎると澱み腐る",],
        ["ナレーション","いたずらに時間だけが流れていく…!まるで命そのもののような…血の時間が…",],
       ]

def get_kaiji_morpheme(sentences):
    data = []
    for title, sentence in sentences:
        data.append([title] + get_morpheme(sentence))
    return data

def get_morpheme(sentence):
    """Use Mecab"""
    t = MeCab.Tagger (" ".join(sys.argv))
    m = t.parseToNode(sentence)
    data = defaultdict(list)
    data = []

    while m:
        parse= m.feature.split(',')[0]
        if parse == "名詞" or parse == "動詞":
            #print m.surface
            data.append(m.surface)
        m = m.next
    return data

class NaiveBayes:
    """NaiveBayes"""

    def __init__(self):
        self.categories = set()
        self.vocabularies = set()
        self.wordcount = {}
        self.catcount = {}
        self.denominator = {}

    def train(self, data):
        """ナイーブベイズ分類器の訓練"""

        # 文章集合からカテゴリを抽出して辞書を初期化
        for d in data:
            cat = d[0]
            self.categories.add(cat)
        for cat in self.categories:
            #print cat
            self.wordcount[cat] = defaultdict(int)
            self.catcount[cat] = 0

        # 文章集合からカテゴリと単語をカウント
        for d in data:
            cat, doc = d[0], d[1:]
            self.catcount[cat] += 1
            for word in doc:
                self.vocabularies.add(word)
                self.wordcount[cat][word] += 1

        # 単語の条件付確率分布の分母をあらかじめ一括計算しておく
        for cat in self.categories:
            self.denominator[cat] = sum(self.wordcount[cat].values()) + len(self.vocabularies)

    def classify(self, doc):
        """事後確率の対数log(P(cat|doc))がもっとも大きなカテゴリを返す"""
        best = None
        max = -sys.maxint
        for cat in self.catcount.keys():
            p = self.score(doc, cat)
            if p > max:
                max = p
                best = cat
        return best


    def score(self, doc, cat):
        """文書が与えられたときのカテゴリの事後確率の対数"""
        total = sum(self.catcount.values())
        score = math.log(Decimal(self.catcount[cat]) / Decimal(total))
        for word in doc:
            score += math.log(self.wordprod(word, cat))

        return score

    def keywords(self):
        """単語の条件付き確率P(word|cat)を求める"""
        for cat in self.catcount.keys():
            print cat

    def wordprod(self, word, cat):
        """単語の条件付き確率P(word|cat)を求める"""
        # ラプラススムージングを適用
        return Decimal(self.wordcount[cat][word] + 1) / Decimal(self.denominator[cat])

    def __str__(self):
        total = sum(self.catcount.values())
        return "documents: %d, vocabularies: %d, categories: %d" % (total, len(self.vocabularies), len(self.categories))


# main
def main():
    #
    data = get_kaiji_morpheme(kaiji)

    # ナイーブベイズ分類器の訓練
    nb = NaiveBayes()
    nb.train(data)

    #print nb
    #nb.keywords()


    # テストデータのカテゴリを予測
    string = "また負けた・・・働いてないのに また負けちゃった・・・・・・ギャンブル負けちゃった・・・"
    string = "食べ終わったら、奴はとりあえず満足してこう考えるだろう。明日からがんばろう。明日から節制だと。"
    string = "ファックユー ぶち殺すぞ ゴミら!"

    test = get_morpheme(string)
    print
    print string
    print
    print "カイジ:", nb.score(test, "カイジ")
    print "利根川:", nb.score(test, "利根川幸雄")
    print "会長  :", nb.score(test, "兵藤和尊")
    print "遠藤  :", nb.score(test, "遠藤勇次")
    print "大槻  :", nb.score(test, "大槻")
    print
    print "label : ",nb.classify(test)


if __name__ == '__main__':
    main()

0 件のコメント:

コメントを投稿