第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 は 当初 1993年 2月 24日 に 生まれ , 1995年 12月 に 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を掛け合わせていくとアンダーフローしてしまうので、対数を取って積を和にする