第7回データマイニング+WEB勉強会@東京で発表してきた

こんにちは。2010/09/26(日)、第7回データマイニング+WEB勉強会@東京に参加してきました。

この勉強会の特徴について

双方向の議論が多く、参加者の多くの方が発言するのが大きな特徴です。またIT系以外の方が参加される異業種交流ができる貴重な勉強回の一つです。内容については「理論〜詳細な実装〜実務」と幅広いものとなってます。
開始時の自己紹介、終了時の振り返り、で少なくとも全員2回ずつ発言する必要がありますw
今回も会場を提供してくださったニフティ・エンジニアサポート様に感謝!また、次回の勉強会は11/14(日)開催予定です。

今回は発表しました

以下発表資料です。

はじめてでもわかるベイズ分類器 -基礎からMahout実装まで-


機会学習も統計も素人ですが、素人なりに勉強した結果と、MahoutというHadoop(またはElastic MapReduce)上で動作する分類器の使い方を解説しています。ベイズ分類器の学習・評価データにはWikipediaのデータを利用してます。

参加者をまとめたホワイトボード

恒例の参加者一覧です。

プレゼン内で紹介した本とRubyのサンプルコード

「この本持っている人います?」の質問に7割近くの人が手を挙げたのが印象的でした。この「集合知プログラミング」の6章では、Pythonによるナイーブベイズ分類器の実装を解説していますが、それを私が使い慣れているRubyで実装してみました。

集合知プログラミング

まず使い方から

# 分類器生成
n = NaiveBayes.new
# 学習
n.train('Nobody owns the water.','good')
n.train('the quick rabbit jumps fences','good')
n.train('buy pharmaceuticals now','bad')
n.train('make quick money at the online casino','bad')
n.train('the quick brown fox jumps','good')
# 推定
puts n.classify("quick rabbit",default="unknown")
puts n.classify("quick money",default="unknown")
# 閾値を与える
n.setthreshold("bad",3.0)
# 推定
puts n.classify("quick money",default="unknown")

分類器のコード

# 特徴抽出クラス
class GetWordFeature
  def get_feature(doc)
    words = doc.split(' ')
    words.map{|w| w.downcase}.select{|w| w.length < 20 && w.length > 2 }.uniq
  end
end
 
# 分類器の基底クラス
class Classifier
  def initialize(f)
    @fc,@cc,@feature = {},{},f
  end
 
  def incf(f,cat)
    @fc[f] ||= {}
    @fc[f][cat] ||= 0.0
    @fc[f][cat] = @fc[f][cat] + 1.0
  end
 
  def incc(cat)
    @cc[cat] ||= 0.0
    @cc[cat] = @cc[cat] + 1.0
  end
 
  def fcount(f,cat)
    @fc[f] ||= {}
    @fc[f][cat] ||= 0.0
    @fc[f][cat]
  end
 
  def catcount(cat)
    @cc[cat] ? @cc[cat].to_f : 0.0
  end
 
  def totalcount
    sum = 0
    @cc.each_value {|value| sum += value}
    return sum
  end
 
  def categories
    @cc.keys
  end
 
  # 条件付き確率 Pr(A|B)
  def fprob(f,cat)
    return 0 if catcount(cat) == 0 || fcount(f,cat) == nil
    fcount(f,cat) / catcount(cat)
  end
 
  # 重み付き確率
  def weightedprob(f, cat, weight=1.0, ap=0.5)
    # 通常の条件付き確率
    basicprob = fprob(f,cat)
    # この特徴がすべてのカテゴリ中に存在する確率
    totals = 0
    categories.each do |c|
      totals = totals + fcount(f,c) if fcount(f,c)
    end
    # 重み付けした平均計算
    ((weight*ap)+(totals*basicprob)) / (weight+totals)
  end
 
  def train(item, cat)
    features =  @feature.get_feature(item);
    features.each do |f|
      incf(f, cat)
    end
    incc(cat)
  end
end
 
# 単純ベイズ分類器
class NaiveBayes < Classifier
  def initialize
    super(GetWordFeature.new)
    @thresholds = {}
  end
 
  # P(B|A):アイテム全体の与えられたカテゴリでの確率を求める(掛け合わせる)
  def docprob(item,cat)
    features = @feature.get_feature(item)
    p = 1.0
    features.each do |f|
      ep = weightedprob(f,cat)
      p = p * ep
    end
    return p
  end
 
  def prob(item,cat)
    catprob = catcount(cat) / totalcount
    docprob = docprob(item,cat)
    return docprob * catprob
  end
 
  def setthreshold(cat,t)
    @thresholds[cat] = t
  end
 
  def getthreshold(cat)
    @thresholds[cat] || 1.0
  end
 
  def classify(item, default=nil)
    probs,max,best = {},0.0,nil
    categories.each do |c|
      probs[c] = prob(item,c)
      if probs[c] > max
        max = probs[c]
        best = c
      end
    end
    probs.each_key do |c|
      # (2番目に確率の高いもの * 閾値)と比較
      second = probs[c].to_f * getthreshold(best)
      return default if second > probs[best]
    end
    return best
  end
end

以上です!