集合知プログラミング第6章 準備

テキストフィルタリングから始めます.最初は環境を整えるところから.PythonからSQLiteをつかえるようにします.

Pythonをインストール

2.5を入れておくと,標準で使えるライブラリがたくさんあって何かと便利なようです.Finkだと"python25"というパッケージ名になっています."python"というパッケージを入れれば最新バージョンが入るのかもしれませんが,一応手動で入れます.

% sudo apt-get install python25
(ずらずらとメッセージが出てきます.省略)
% sudo apt-get install python

これで/sw/bin/python ができました.ls などで確認しておきましょう.ついでにバージョンも確認しておきます.

% ls /sw/bin/python
/sw/bin/python
% /sw/bin/python -V
Python 2.5.1

これでMac標準のpythonを汚さずにいろいろ試すことができます.

SQLite3のインストール

Python2.5にはsqlite3のモジュールが標準でついているのですが,肝心のSQLite3そのものが入っていないので,インストール.Finkを使っていれます.便利.

% sudo apt-get install sqlite3

集合知プログラミング第6章 実装

集合知プログラミング第6章のサンプルほぼそのままだけれど.違う点はアイテムがクラスに属する確率をそのままつかって分類するのではなく,その確率の対数を使っている点.小数点以下の桁が大きくなるのは嫌なものですからね.

ソースはこんな感じ.

import re
import math
import sys
import sqlite3
import os

class classifier:
    def __init__(self, getfeatures, filename=None):
        self.getfeatures = getfeatures
        self.setdb(filename)
        
    def setdb(self, dbfile):
        self.con = sqlite3.connect(dbfile);
        try:
            self.con.execute('create table fc(feature, category, count);')
        except sqlite3.OperationalError:
            print("table fc has already been created. skip")
        try:
            self.con.execute('create table cc(category, count);')
        except sqlite3.OperationalError:
            print("table cc has already been created. skip")

    def incf(self, f, cat):
        #self.fc.setdefault(f, {})
        #self.fc[f].setdefault(cat, 0)
        #self.fc[f][cat] += 1
        count = self.fcount(f, cat)
        if(count == 0):
            self.con.execute("insert into fc values('%s', '%s', 1);"%(f,cat))
        else:
            self.con.execute("update fc set count=%d where feature='%s' and category='%s';"%(count+1,f,cat))
        
    def incc(self, cat):
        count = self.catcount(cat)
        if(count == 0):
            print("insert new category %s into table cc"%(cat))
            self.con.execute("insert into cc values('%s', 1);" %(cat))
        else:
            self.con.execute("update cc set count=%d where category='%s';" %(count+1,cat))

    def fcount(self, f, cat):
        result = self.con.execute("select count from fc where feature = '%s' and category = '%s';"%(f,cat)).fetchone()
        if(result == None):
            return 0.0
        else:
            return float(result[0])

    def catcount(self, cat):
        #if cat in self.cc:
            #return float(self.cc[cat])
        #return 0
        sql = "select count from cc where category = '%s';"%(cat)
        #print(sql)
        result = self.con.execute(sql).fetchone()
        if(result == None):
            return 0.0
        else:
            return float(result[0])

    def totalcount(self):
        #return sum(self.cc.values())
        result = self.con.execute("select sum(count) from cc;").fetchone()
        if(result == None):
            return 0.0
        else:
            return result[0] 
    
    def categories(self):
        #return self.cc.keys()
        result = self.con.execute("select category from cc;")
        return [row[0] for row in result]

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

    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()])
        return (weight * ap + totals * basicprob) / (weight + totals)
        
class naivebayes(classifier):
    # Modified to use logged value of each features's probability
    def docprob(self, item, cat):
        features =  self.getfeatures(item)
        return sum([math.log(self.weightedprob(f, cat, self.fprob)) for f in features])

    def catprob(self, cat):
        if(self.totalcount() == 0):
            return 0
        return self.catcount(cat) / self.totalcount()

    def prob(self, item, cat):
        return math.log(self.catprob(cat)) + self.docprob(item, cat)

    def classify(self, item, default=None):
        max = 0.0 # This value means "-infinity".
        max_c = ''
        for cat in self.categories():
            p = self.prob(item, cat)
            sys.stdout.write("p(%s|item)=%f\n"%(cat,p))
            if(max == 0.0 or p > max):
                max = p
                max_c = cat
        return max_c

create table on SQLite

SQLiteのドキュメント(http://www.sqlite.org/lang_createtable.html)には「使えるよ」書いてあり,sqliteインタプリタ上では実行できるのに,pythonのsqlite3モジュールではcreate table if not existsができないんですよ.

これに結構悩まされました.

集合知プログラミング第6章 解説

プログラム概要は下図の通り.分類対象を受け取って,それを特徴に分解するFeature extractorと,その結果を受け取って分類を行うClassifierの二つを実装することになります.

前者はgetwordsという関数として,後者はclassifierというクラスとして実装しています.

このような実装のポイントは二つ.

  • feature extractorをclassifierの内部で規定しないことにより様々な特徴抽出関数を利用できるようにしたこと
  • 分類アルゴリズムの具体的な実装をサブクラスに任せることにより,さまざまな分類アルゴリズムを利用できるようになっていること

とりあえずラフに解説するとこんな感じです.