文档和单词
将单词作为特征。将任何非字母类字符为分隔符对文本进行划分,将文本拆分为一个个单词,并转化成小写形式。
import re
import math
def getwords(doc):
splitter.re.compile('\\W*')
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
fc将记录位于各分类中不同特征的数量:
{‘python’:{‘bad’:0,‘good’:6},‘the’:{‘bad’:3,‘good’:3}}
变量cc表示记录各分类被使用次数的字典。getfeatures为从即将被归类的内容项中提取出特征,在本例中就是我们定义过的getwords函数。
#增加对特征f、分类cat组合的计数值
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
#属于某一分类的内容项
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()
Train方法接受一个内容项和一个分类作为参数。利用getfeatures函数,将内容项拆分为彼此独立的各个特征。利用incf函数,针对该分类为每个特征增加计数值。最后,函数会增加针对该分类的总计数值。
def train(self,item,cat):
features=self.getfeatures(item)
#features为拆分出来的单词
for f in features:
self.incf(f,cat)
self.incc(cat)
之前的代码放置在docclass.py中。
import docclass
c1=docclass.classifier(docclass.getwords)
c1.train('the quick brown fox jumps over the lazy dog','good')
#代表上述单词分到good类
c1.train('quick','good')
计算概率
可以用一个单词在一篇属于某个分类的文档中出现的次数,除以该分类的文档总数,计算出单词在分类中出现的概率。
def fprob(self,f,cat):
if self.catcount(cat)==0: return 0
return self.fcount(f,cat)/self.catcount(cat)
fprob概率为条件概率,pr(word|classification),即对于给定的分类某个单词出现的概率。
c1.fprob('quick','good')
表示一篇good分类的文档中包含该单词的概率为,pr(quick|good)=0.666
从一个合理的推测开始
上述的算法,对那些极少出现的单词异常敏感。对上述算法进行改进。
def weightedprob(self,f,cat,prf,weight=1.0,ap=0.5):
#计算当前概率值
basicprob=prf(f,cat)
#统计特征在所有分类中出现的次数
totals=sum([self.fcount(f,c) for f in self.categories()])
#计算加权平均
bp=((weight*ap)+(totals*basicprob))/(weight+totals)
return bp
朴素分类器
朴素表示将要被组合的概率是彼此独立的。
整片文档的概率
pr(python|bad)=0.2
pr(casino|bad)=0.8
pr(python&casino|bad)=0.16
若计算整片文档,曾江文档中出现的特征概率相乘即可。如下算法。
#新建一个classifier的子类
class naviebayes(classifier):
def docprob(self,item,cat):
features=self.getfeatures(item)
#将所有特征概率相乘
p=1
for f in features:p*=self.weightedprob(f,cat,self,fprob)
return p
目前我们已经知道如何计算pr(document|category),但我们需要给定一篇文档,它属于某个分类的概率是多少?
即pr(category|document),为此我们采用贝叶斯定理。
贝叶斯定理
pr(category|document)=pr(document|category)*pr(category)/pr(document)
def prob(self,item,cat):
catprob=self.catcount(cat)/self.totalcount()
docprob=self.docprob(item,cat)
return catprob*docprob
c1.prob('quick rabbit','good')
c1.prob('quick rabbit','bad')
那个概率大,说明该短语更倾向于那个类。
选择分类
避免将普通邮件当做垃圾邮件。设定阈值。
假设过滤到bad的分类阈值为3,则针对bad分类的概率就必须至少3倍于针对good的概率才行。
def __init__(self,getfeatures):
classifier.__init__(self,getfeatures)
self.thresholds={}
def setthreshold(self,cat,t):
self.thresholds[cat]=t
def getthreshold(self,cat):
if cat not in self.thresholds: return 1
return self.thresholds[cat]
将计算每个分类的概率,从中得到最大值,并将其余次大概率值进行对比,确定是否超过了阈值。
def classify(self,item,default=None):
probs={}
#寻找概率最大的分类
max=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
我们已经建立起一个完整的文档分类系统。
c1.setthreshold('bad',3.0)
c1.classify('quick money',default='unknown')
费舍尔方法
是前面介绍的朴素贝叶斯的一种替代方案
将经过训练的分类器持久化
本节中将classifier所用的字典,都替换成一个持久化的数据存储结构。请在classifier中添加一个方法,为该分类器打开数据库,执行建表操作。
from pysqlite2 import dbapi2 as sqlite
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)')
如果我们打算将分类器移植到另个数据库上,需要修改:
#增加对特征f、分类cat组合的计数值
def incf(self,f,cat):
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))
....
获取所用分类的列表与文档总数的方法也被替换掉。
最后添加一条提交语句:
self.con.commit()
c1.setdb('test1.db')
过滤博客订阅源
我们只希望阅读某个分类的文章。
import feedparser
import re
def read(feed,classifier):
f=feedparser.parse(feed)
for entry in f['entries']:
print...
print 'guess: '+str(classifier.classify(fulltext))
#请求用户给出正确分类,并据此进行训练
c1=raw_input('enter category: ')
classifier.train(fulltext,c1)
略
对特征检测的改进
def entryfeatures(entry):
splitter.re.compile('\\W*')
f={}
titlewords=[s.lower() for s in splitter.split(entry['title']) if len(s)>2 and len(s)<20]
for w in titlewords:f['title :'+w]=1
summarywords=[s.lower() for s in splitter.split(entry['summary']) if len(s)>2 and len(s)<20]
#统计大写单词
uc=0
for i in range(len(summarywords)):
w=summarywords[i]
f[w]=1
if w.isupper():uc+=1
if i<len(summarywords)-1:
twowords=' '.join(summarywords[i:i+1])
f[twowords]=1
...
贝叶斯分类器经常被用于文档分类的原因是,它所要求的计算资源更少。但是神经网络和支持向量机有个很大的优势,可以捕捉输入特征之间更为复杂的关系。