「ベイジアンフィルタを実装してみよう」をRubyで

2011-08-15

第3回 ベイジアンフィルタを実装してみよう:機械学習 はじめよう|gihyo.jp … 技術評論社をRubyでやってみる。テキスト解析:日本語形態素解析 - Yahoo!デベロッパーネットワークも使わずに、自分で単語分けして。

#!/usr/bin/ruby -Ku

class Array
def sum
self.inject(0) {|r, x| r + x}
end
end

class NaiveBayes
def initialize
@vocabularies = {} # 単語の集合
@wordcount = Hash.new {|h, k| h[k] = Hash.new(0)} # {category : { words : n, ...}}
@catcount = Hash.new(0) # {category : n}
end

def wordcountup(word, cat)
@wordcount[cat][word] += 1
@vocabularies[word] = 1
end

def catcountup(cat)
@catcount[cat] += 1
end

def train(tokens, cat)
tokens.each do |w|
wordcountup(w, cat)
end
catcountup(cat)
end

def priorprob(cat)
return @catcount[cat].to_f / @catcount.values.sum
end

# あるカテゴリの中に単語が登場した回数を返す
def incategory(word, cat)
if @wordcount[cat].has_key?(word)
return @wordcount[cat][word].to_f
end
return 0.0
end

# P(word|cat) が生起する確率を求める
def wordprob(word, cat)
prob = (incategory(word, cat) + 1.0) /
(@wordcount[cat].values.sum + @vocabularies.length)
return prob
end

def score(tokens, cat)
sc = Math::log(priorprob(cat))
tokens.each do |w|
sc += Math::log(wordprob(w, cat))
end
return sc
end

def classifier(tokens)
best = nil # 最適なカテゴリ
max = -1.0 / 0.0 # -Infinity

# カテゴリ毎に確率の対数を求める
@catcount.keys.each do |cat|
prob = score(tokens, cat)
if prob > max
max = prob
best = cat
end
end
return best
end
end

def getwords(doc)
doc.split.select {|w| !stop_word?(w)}.map {|w| w.downcase}
end

def stop_word?(word)
word =~ /^[・。,()ぁ-んァ-ヴ]$/
end

if $0 == __FILE__
nb = NaiveBayes.new
nb.train(getwords(<<EOD), 'Python')
Python ( パイソン ) は , オランダ 人 の グイド ・ ヴァン ロッサム が 作 った オープンソース の プログラミング 言語 。
オブジェクト 指向 スクリプト 言語 の 一種 で あり , Perl と とも に 欧米 で 広く 普及 して い る 。 イギリス の テレビ 局 BBC が 製作 し た コメディ 番組 『 空 飛ぶ モンティ パイソン 』 に ちな ん で 名付け られ た 。
Python は 英語 で 爬虫類 の ニシキヘビ の 意味 で , Python 言語 の マスコット や アイコン と して 使われ る こと が あ る 。 Python は 汎用 の 高水準 言語 で あ る 。 プログラマ の 生産性 と コード の 信頼性 を 重視 して 設計 されて お り , 核 と な る シンタックス および セマンティクス は 必要 最小限 に 抑え られ て い る 反面 , 利便性 の 高い 大規模 な 標準 ライブラリ を 備え て い る 。
Unicode に よる 文字列 操作 を サポート して お り , 日本語 処理 も 標準 で 可能 で あ る 。 多く の プラットフォーム を サポート して お り ( 動作 する プラットフォーム ) , また , 豊富 な ドキュメント , 豊富 な ライブラリ が あ る こと から , 産業界 でも 利用 が 増え つつ あ る 。
EOD

nb.train(getwords(<<EOD), 'Ruby')
Ruby ( ルビー ) は , まつもと ゆきひろ ( 通称 Matz ) に より 開発 され た オブジェクト 指向 スクリプト 言語 で あり , 従来 Perl など の スクリプト 言語 が 用いられ て きた 領域 で の オブジェクト 指向 プログラミング を 実現 する 。 Ruby は 当初 1993224日 に 生まれ , 199512月 に fj 上 で 発表 され た 。 名称 の Ruby は , プログラミング 言語 Perl が 6月 の 誕生石 で あ る Pearl ( 真珠 ) と 同じ 発音 を する こと から , まつもと の 同僚 の 誕生石 ( 7月 ) の ルビー を 取って 名付け られ た 。
EOD

nb.train(getwords(<<EOD), '機械学習')
豊富 な 機械 学習 ( きかい がくしゅう , Machine learning ) とは , 人工知能 に おける 研究 課題 の 一つ で , 人間 が 自然 に 行っ て い る 学習 能力 と 同様 の 機能 を コンピュータ で 実現 させ る ため の 技術 ・ 手法 の こと で あ る 。 ある 程度 の 数 の サンプル データ 集合 を 対象 に 解析 を 行い , その データ から 有用 な 規則 , ルール , 知識 表現 ,判断 基準 など を 抽出 する 。 データ 集合 を 解析 する ため , 統計学 との 関連 も 非常 に 深い 。
機械 学習 は 検索 エンジン , 医療 診断 , スパム メール の 検出 , 金融 市場 の 予測 , DNA 配列 の 分類 , 音声 認識 や 文字 認識 など の パターン 認識 , ゲーム 戦略 , ロボット , など 幅広い 分野 で 用いられ て い る 。 応用 分野 の 特性 に 応じ て 学習 手法 も 適切 に 選択 する 必要 が あり , 様々 な 手法 が 提案 されて い る 。 それら の 手法 は , Machine Learning や IEEE Transactions on Pattern Analysis and Machine Intelligence など の 学術 雑誌 など で 発表 される こと が 多い 。
EOD

texts = [
# Python
'ヴァン ロッサム 氏 に よっ て 開発 され ま し た .',
'豊富 な ドキュメント や 豊富 な ライブラリ が あり ま す. ',
# Ruby
'純粋 な オブジェクト 指向 言語 です .',
'Ruby は まつもと ゆきひろ 氏 ( 通称 Matz ) に より 開発 され ま し た .',
# 機械学習
'「 機械 学習 はじ め よう 」 が 始まり ま し た .',
'検索 エンジン や 画像 認識 に 利用 され て い ま す .',
]

texts.each do |text|
puts "#{nb.classifier(getwords(text))}: #{text.split.join}"
end
end

で走らせると、単語分けの方法が違うからか、一部合わない:

Ruby: ヴァンロッサム氏によって開発されました.
Python: 豊富なドキュメントや豊富なライブラリがあります.
Ruby: 純粋なオブジェクト指向言語です.
Ruby: Rubyはまつもとゆきひろ氏(通称Matz)により開発されました.
機械学習: 「機械学習はじめよう」が始まりました.
機械学習: 検索エンジンや画像認識に利用されています.

最初のテキストがRubyと誤判定されてしまっている。うーむ…

  • 「ヴァンロッサム」と入ってても誤判定されるとは…
  • もとのプログラムでは、Yahoo!のAPIで形態素解析した結果のうち、形容詞、形容動詞、感動詞、副詞、連帯子、名詞、動詞のみを使ってる。
    • こちらは、正規表現で一文字のひらがなorカタカナor括弧などを省いている
  • もっと訓練データを増やせば精度が上がるか?
  • 一番確率が高いものを求めるので、3ページ目のメソッドclassifierの不等号は逆?
  • プログラムはシンプルでいいですね。数学的なことはあまり理解してないですが…。
    • 気をつけるところは、訓練データに出てこない単語の確率となってしまって無効になってしまうのを避けるため、+1する
      • 訓練データが少ないと、誤差が重大?
    • 単語の出現頻度の確率0.0~1.0を掛け合わせていくとアンダーフローしてしまうので、対数を取って積を和にする