Bayesian Filter in Python

式とアルゴリズムが間違っていたので後日修正

オライリーの集合知プログラミングを読んで。迷惑メールをフィルターするのに使われている例が有名。ベイズの定理が分かれば実装はとても簡単。

#coding:utf-8
import re
import math
import MeCab


class NaiveBayes(object):

    def __init__(self, spliter):
        self.features = {}
        self.spliter = spliter

    def value(self, category, item):
        """与えられたcategoryのなかで、itemの生起した回数"""
        if item in self.features[category]:
            return float(self.features[category][item])
        return 0.0

    def pr_cat(self, category):
        """ Pr(category)
        与えられたcategoryが選ばれる確率
        = 与えられたcategoryのitem数 / 全カテゴリのitem数"""
	   #疑問 これだと学習量の多いcategoryほど事前確率が大きくなる。それでいいのか?"
        return float(sum(self.features[category].values())) \
            / sum([sum(self.features[cat].values()) \
                   for cat in self.features])

    def pr_item_in_cat(self, category, item):
        """ Pr(item|category)
        与えられたcategoryの中に itemが生起する確率"""
        return self.value(category, item) / \
            sum(self.features[category].values())

    def pr_smooth_item_in_cat(self, category, item,
                              weight=1.0, default_pr=0.001):
        """ あるcategoryにitemがない場合、確率を0にしてしまうのは
        極端なので、デフォルト確率と重みを使って調整 """
        basic_pr = self.pr_item_in_cat(category, item)
        totals = sum([self.value(cat, item) for cat in self.features])
        # 単語一つ分 * デフォルト確率 と
        # 実際に求めたPr(item|category) * item数を足して
        # 単語一つ+item数の数で割る。
        # デフォルト確率と単語一つ分を加えた確率の平均を求める
        br = ((weight * default_pr) + (totals * basic_pr)) / \
            (weight + totals)
        return br

    def prob_item(self, item):
        """あるitemがあるcategoryに生起する確率
        Pr(category|item)     ベイズの定理より
        = Pr(category AND item) / Pr(item)
        = Pr(item|category) Pr(category) / Pr(item)
        = Pr(item|category) Pr(category) /
          (Pr(item AND category) + Pr(item AND NOT category))
        """
        result = {}
        for category in self.features:
            pr_item_in_cat = self.pr_smooth_item_in_cat(category, item)
            pr_cat = self.pr_cat(category)
            # Pr(item AND category) = Pr(item|category) * Pr(category)
            pr_item_and_cat = pr_item_in_cat * pr_cat
            pr_item_and_not_cat = 0
            for _category in self.features:
                if _category != category:
                    # Pr(item AND NOT category)
                    # = Pr(item | NOT category) * Pr(NOT category)
                    pr_item_and_not_cat += \
                        self.pr_smooth_item_in_cat(_category, item) * \
                        (1.0 - pr_cat)
            result[category] = pr_item_and_cat / \
                (pr_item_and_cat + pr_item_and_not_cat)
        return item, result

    def prob(self, data):
        scores = {}
        for item in self.spliter(data):
            _item, result = self.prob_item(item)
            for cat in result:
                scores.setdefault(cat, 0.0)
                scores[cat] += math.log(result[cat])
        return scores

    def train(self, data, category):
        items = self.spliter(data)
        self.features.setdefault(category, {})
        for item in items:
            self.features[category].setdefault(item, 0)
            self.features[category][item] += 1

def split_mecab(data):
    m = MeCab.Tagger('-Owakati')
    _data = m.parse(data.encode('utf8'))
    items = [s.lower().strip() for s in _data.split(' ')]
    return items


if __name__ == '__main__':
    nb = NaiveBayes(split_mecab)

    # 凧(kaite)と蛸(octopus)について学習させる

    nb.train('''凧(たこ)とは風の力を利用して空中に揚げる玩具である。
日本では正月の遊びとして知られている。木や竹などの骨組みに紙、布、ビニー
ルなどを張って紐で反りや形を整えて作られる。''', 'kite')

    nb.train('''タコ(蛸、鮹、章魚、鱆、英語名:Octopus)は、頭足綱
- 鞘形亜綱(en)-八腕形上目のタコ目(学名:ordo Octopoda)に分類され
る動物の総称。 海洋棲の軟体動物で、主に岩礁や砂地で活動する。淡水に棲息
する種は知られていない。''', 'octopus')

    print '%s,%s' % nb.prob_item('動物')
    print '%s,%s' % nb.prob_item('風')
動物,{'octopus': 0.94649122807017538, 'kite': 0.053508771929824568}
風,{'octopus': 0.069298245614035081, 'kite': 0.93070175438596481}


‘動物’が
octopusカテゴリの単語である確率は 94.64%
kiteカテゴリの単語である確率は 5.35%


‘風’が
octopusカテゴリの単語である確率は 6.92%
kiteカテゴリの単語である確率は 93.07%


このような形で文章中の単語ひとつひとつのカテゴリごとの生起確率を計算し、
文章全体がどのカテゴリのものであるかを推測する。
Naive Bayesのアルゴリズムでは1度も出現したことのないitemの確率を
どう重み付け(smoothing)するかによって確率が大きく変わってくるので、注意が必要。

Posted: November 18th, 2010 | Author: | Filed under: 技術 | No Comments »

Leave a Reply