Rubyでナイーブベイズの簡単なサンプル

More than 3 years have passed since last update.

rubyでナイーブベイズをやってみたくて、簡単なサンプルを作ってみました。

文章のネガポジ判定をやってみます。文章はすでに単語の配列となっているとします。

train_data = [

{cat: "pos", doc: ["天才", "優秀"]},
{cat: "pos", doc: ["天才"]},
{cat: "pos", doc: ["優秀", "バカ"]},
{cat: "pos", doc: ["アホ"]},
{cat: "neg", doc: ["バカ", "天才"]},
{cat: "neg", doc: ["バカ", "アホ"]},
]

test_data = [
{doc: ["天才", "アホ"]},
{doc: ["バカ", "baka"]},
{doc: ["阿呆"]},
]

上のように、トレーニングデータを用意して、テストデータの分類を試みます。

「阿呆」のようにトレーニングデータに含まれていないdocumentはどちらに判定されるのでしょうか?


コード


naive_bays_sample.rb

include Math

require "set"

class NaiveBayes

def initialize()
@category = Set.new

@num_of_train_data = 0
@num_of_cat = Hash.new(0.0) # @num_of_cat[cat] = {catの数}
@num_of_word = Hash.new{|h,k| h[k] = Hash.new(0.0)} # @num_of_word[cat][word] = {wordの数}

@prob_of_category = Hash.new(0.0) # P(cat)
@prob_of_word = Hash.new{|h,k| h[k] = Hash.new(0.0)} # P(word|cat)
end

# トレーニングをする(P(cat)までを求めています)
def train(train_data)
@num_of_train_data = train_data.count
set_num(train_data)
set_prob_cat
end

# テストデータの分類をする(P(word|cat)まで求めています)
def classify(test_data)
test_data.each do |v|
doc = v[:doc]
set_prob_word(doc)
likelihood = {}
@category.each do |cat|
likelihood[cat] = log(@prob_of_category[cat]) + doc.map{|word| log(@prob_of_word[cat][word])}.inject(:+)
end
# 出力
puts @prob_of_word
puts likelihood
print "テストデータ: #{v[:doc].join(',')} => 判定: #{likelihood.max{|a,b| a[1]<=>b[1]}[0]}\n\n"
end
end

private

# トレーニングデータから、カテゴリ数やワード数を計算しておく
def set_num(train_data)
train_data.each do |v|
cat = v[:cat]
@category.add(cat)
@num_of_cat[cat] +=1
doc = v[:doc]
doc.each do |word|
@num_of_word[cat][word] += 1
end
end
end

# P(cat)を求める
def set_prob_cat
@category.each do |cat|
@prob_of_category[cat] = @num_of_cat[cat] / @num_of_train_data
end
end

# P(word|cat) を求める
def set_prob_word(doc)
vocabulary = Set.new
doc.each { |word| vocabulary.add(word) }
@prob_of_word = Hash.new{|h,k| h[k] = Hash.new(0.0)}
@category.each do |cat|
vocabulary.each do |word|
@prob_of_word[cat][word] = (@num_of_word[cat][word] + 1) / (@num_of_word[cat].map{|k,v| v}.inject(:+) + vocabulary.size)
end
end
end

end

train_data = [
{cat: "pos", doc: ["天才", "優秀"]},
{cat: "pos", doc: ["天才"]},
{cat: "pos", doc: ["優秀", "バカ"]},
{cat: "pos", doc: ["アホ"]},
{cat: "neg", doc: ["バカ", "天才"]},
{cat: "neg", doc: ["バカ", "アホ"]},
]

test_data = [
{doc: ["天才", "アホ"]},
{doc: ["バカ", "baka"]},
{doc: ["阿呆"]},
]

nb = NaiveBayes.new
nb.train(train_data)
nb.classify(test_data)



実行結果

% ruby naive_moch.rb

{"pos"=>{"天才"=>0.375, "アホ"=>0.25}, "neg"=>{"天才"=>0.3333333333333333, "アホ"=>0.3333333333333333}}
{"pos"=>-2.7725887222397816, "neg"=>-3.295836866004329}
テストデータ: 天才,アホ => 判定: pos

{"pos"=>{"バカ"=>0.25, "baka"=>0.125}, "neg"=>{"バカ"=>0.5, "baka"=>0.16666666666666666}}
{"pos"=>-3.8712010109078907, "neg"=>-3.58351893845611}
テストデータ: バカ,baka => 判定: neg

{"pos"=>{"阿呆"=>0.14285714285714285}, "neg"=>{"阿呆"=>0.2}}
{"pos"=>-2.351375257163478, "neg"=>-2.70805020110221}
テストデータ: 阿呆 => 判定: pos


おわりに

スームジングを行っていますので、「阿呆」もしっかりと分類されています。(この場合、posの割合が多いのでそちらに分類されているそうな。)

サンプルとして選んだ単語たちが、とても稚拙で、悲しくなってきました。