個人的な正誤表 6章

ドキュメントフィルタリングの章です。

本家のdocclass.pyをダウンロードしてしまうと、余計なコードが入っていて、そのままだと動きません。
また、6.5にも落とし穴がいくつかあります。ここではnaivebayesクラスを作成しますが、本の通りにやるとcategories関数宣言していないのでエラーになります。適宜classifierクラスと同様に作ってあげましょう。基本的には6.7に入るまでは以下のソースコードを用いることで本の通りの結果を得ることができます。

from pysqlite2 import dbapi2 as sqlite
import re
import math

def getwords(doc):
  splitter=re.compile('\\W*')
  print doc
  words=[s.lower() for s in splitter.split(doc) 
          if len(s)>2 and len(s)<20]
  
  return dict([(w,1) for w in words])

class classifier:
  def __init__(self,getfeatures,filename=None):
    self.fc={}
    self.cc={}
    self.getfeatures=getfeatures

  def setdb(self,dbfile):
    self.con=sqlite.connect(dbfile)    
    self.con.execute('create table if not exists fc(feature,category,count)')
    self.con.execute('create table if not exists cc(category,count)')

  def incf(self,f,cat):
    self.fc.setdefault(f,{})
    self.fc[f].setdefault(cat,0)
    self.fc[f][cat]+=1

  def incc(self,cat):
    self.cc.setdefault(cat,0)
    self.cc[cat]+=1
  
  def fcount(self,f,cat):
    if f in self.fc and cat in self.fc[f]:
      return float(self.fc[f][cat])
    return 0.0

  def catcount(self,cat):
    if cat in self.cc:
      return float(self.cc[cat])    
    return 0

  def totalcount(self):
    return sum(self.cc.values())

  def categories(self):
    return self.cc.keys()

  def train(self,item,cat):
    features=self.getfeatures(item)
    for f in features:
      self.incf(f,cat)
    self.incc(cat)

  def fprob(self,f,cat):
    if self.catcount(cat)==0: return 0
    return self.fcount(f,cat)/self.catcount(cat)

  def weightedprob(self,f,cat,prf,weight=1.0,ap=0.5):
    basicprob=prf(f,cat)
    totals=sum([self.fcount(f,c) for c in self.categories()])
    bp=((weight*ap)+(totals*basicprob))/(weight+totals)
    return bp

def sampletrain(cl):
  cl.train('Nobody owns the water.','good')
  cl.train('the quick rabbit jumps fences','good')
  cl.train('buy pharmaceuticals now','bad')
  cl.train('make quick money at the online casino','bad')
  cl.train('the quick brown fox jumps','good')

class naivebayes(classifier):
  
  def __init__(self,getfeatures):
    classifier.__init__(self,getfeatures)
    self.thresholds={}
  
  def docprob(self,item,cat):
    features=self.getfeatures(item)   
    p=1
    for f in features: p*=self.weightedprob(f,cat,self.fprob)
    return p

  def prob(self,item,cat):
    catprob=self.catcount(cat)/self.totalcount()
    docprob=self.docprob(item,cat)
    return docprob*catprob

  def categories(self):
    return self.cc.keys()

  def setthreshold(self,cat,t):
    self.thresholds[cat]=t

  def getthreshold(self,cat):
    if cat not in self.thresholds: return 1.0
    return self.thresholds[cat]

  def classify(self,item,default=None):
    probs={}
    max=0.0
    for cat in self.categories():
      probs[cat]=self.prob(item,cat)
      if probs[cat]>max: 
        max=probs[cat]
        best=cat

    for cat in probs:
      if cat==best: continue
      if probs[cat]*self.getthreshold(best)>probs[best]: return default
    return best

class fisherclassifier(classifier):

  def cprob(self,f,cat):
    clf=self.fprob(f,cat)
    if clf==0: return 0
    freqsum=sum([self.fprob(f,c) for c in self.categories()])
    p=clf/(freqsum)
    return p

  def fisherprob(self,item,cat):
    p=1
    features=self.getfeatures(item)
    for f in features:
      p*=(self.weightedprob(f,cat,self.cprob))
    fscore=-2*math.log(p)
    return self.invchi2(fscore,len(features)*2)

  def invchi2(self,chi, df):
    m = chi / 2.0
    sum = term = math.exp(-m)
    for i in range(1, df//2):
        term *= m / i
        sum += term
    return min(sum, 1.0)

  def __init__(self,getfeatures):
    classifier.__init__(self,getfeatures)
    self.minimums={}

  def setminimum(self,cat,min):
    self.minimums[cat]=min
  
  def getminimum(self,cat):
    if cat not in self.minimums: return 0
    return self.minimums[cat]

  def classify(self,item,default=None):
    best=default
    max=0.0
    for c in self.categories():
      p=self.fisherprob(item,c)
      if p>self.getminimum(c) and p>max:
        best=c
        max=p
    return best

ということで、6.7までの演習は上記ソースコードで行いましょう

p.142のところは不親切というか、いきなりDBを触ります。test.dbが必要になりますので本家から適宜ダウンロードしましょう。また、setdb関数が必要になりますが、これについてはどこで触れてるの?という感じ。

そもそも論としては、英語版はFisherを使っていない間違った実行例になっていて、親切に翻訳者が直してくれたのでしょうが、その参考にしたコードがDB触った部分だったのでしょう。

ですので、

c1.setdb('test.db')

のところは実行しなくてOKです。実行しても結果は一緒ですが、本の手順どおりやるとエラーになります。

上のソースコードでは実行しちゃっても大丈夫なように細工しますので、本の通りでもいけるようになってるはず。

6.7からはいよいよSQLiteを用いりますが、事前準備が必要です。以前の章でも使ったfeedparser.pyやpython_feed.dbやpython_search.xmlが必要になります。(test1.dbやtest2.dbとかも)

さて、結論から言うとp.147から、本のようにはうまくいきません。あまりチェックをしないでコードを載せたのでしょう…

docclassのimport忘れはいいとして、せめてDBを作るところから教えてもらえたら、なんとかできたかも…

説明不足っす

Persisting the classifier using SQLite is a good idea, but its implementation is terribly naive for the simple reason that primary keys for the tables are not specified. This leads to simply atrocious performance for anything but the most trivial of applications. Makes me think that the author wrote pretty much untested Python code for the book.

実際にやってみると微妙に小文字大文字の処理でバグってますね。

ここ以降はテストする気力がないので、ここまでにします。暇があったら、いつの日にかやってみようと思います。