欢迎关注我的个人博客blog.timene.com
在前面《集体智慧编程学习之聚类系统》中,我对收藏的一些电子书做了聚类。当时是在没有任何前验知识的情况下,采用K-均值聚类的算法将书籍分为经济类,心理类,摄影类等等。今天,我打算手工给书籍分类,我先把特别特别特别喜欢的书整理出来,然后分好类。分好搞得我精疲力尽,却发现还有大量的特别特别喜欢的,特别喜欢的,喜欢的,还好的,不喜欢的。。。就这么手工分了一小部分,我已经完全不想再手工分下去了。我已经分了一小部分,能不能根据已经分好的这部分去区分其他的书籍呢?也就是我现在有了一些前验知识,能不能把这些知识利用起来?
要分类,肯定要根据一些特征来做,特征是判断内容中具有或者缺失的信息。对电子书分类这事,内容就是书籍文档,特征就是文档中的单词或者词组。这和聚类系统中说过的一样,不同分类的书籍这些特征是不同的。
首先来分词整理这些特征
def getwords(doc): splitter=re.compile('\\W*') print doc # Split the words by non-alpha characters words=[s.lower() for s in splitter.split(doc) if len(s)>2 and len(s)<20] # Return the unique set of words only return dict([(w,1) for w in words])
定义一个代表分类器的类,这个类对分类器到目前为止所掌握的信息进行封装:
class classifier: def __init__(self,getfeatures,filename=None): self.fc={} self.cc={} self.getfeatures=getfeatures 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 self.fc[f][cat] else: return 0.0 def catcount(self,cat): if cat in self.cc: return float(self.cc[cat]) else: return 0.0 def categories(self): return self.cc.keys() def totalcount(self): return sum(self.cc.values())可以训练模型喽,很简单的一个函数:
def train(self,item,cat): features=self.getfeatures(item) for f in features: self.incf(f,cat) self.incc(cat)训练好之后,可以计算特征f属于cat分类的概率了,也就是cat分类下书籍的总量除以cat分类下具有特征f的的书籍数:
def fprob(self,f,cat): if self.catcount(cat)==0: return 0 return self.fcount(f,cat)/self.catcount(cat)
到了这里,模型建好了,根据这个模型如何对未分类的书籍分类呢?可以用朴素贝叶斯分类,也可以用费舍尔分类。
(一)贝叶斯分类:贝叶斯的初衷很简单:盒子里有十个球,六个绿色的,四个蓝色的,我随手一摸,摸出蓝色的概率很容易知道。如果我知道盒子里有十个球,但不知道几个绿的几个蓝的,我随手一摸是个绿的,那盒子里会有几个绿球几个蓝球呢?贝叶斯公式也很简单,用来解决这种条件概率的调换,根据训练模型很容易知道某个分类下某个特征的概率,怎么计算这个特征可能会属于这个分类的概率。
朴素贝叶斯公式假设一篇文章中的各个词组是不相关的,这样,计算整本书籍的概率,就是所有词组概率之积:
def docprob(self,item,cat): features=self.getfeatures(item) p=1 for f in features: p*=self.weightedprob(f,cat,self.fprob) return p
根据贝叶斯公式,P(类目|书籍)=P(书籍|类目)*P(类目)/P(书籍),我们不考虑P(书籍),这个值在计算书籍属于那个类目时是不会变化的。
def prob(self,item,cat): catprob=self.catcount(cat)/self.totalcount() docprob=self.docprob(item,cat) return docprob*catprob
这样,要预计一本书属于那个类目,只要计算这本书属于所有类目的概率,取其中概率最大的即是。这里我再引深一点,取概率最大在有些场合并不合适,比如分类邮件,垃圾邮件和非垃圾邮件,我们宁可让几份垃圾邮件进入正常邮件,如果把非垃圾邮件误判为垃圾邮件就很不好了。这时我们在做判断时加上一个阈值,比如判断为垃圾邮件的概率大于判断为正常邮件的概率的几倍,我们就确认为垃圾邮件过滤,否则我们就不判断。
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={} # Find the category with the highest probability max=0.0 for cat in self.categories(): probs[cat]=self.prob(item,cat) if probs[cat]>max: max=probs[cat] best=cat # Make sure the probability exceeds threshold*next best for cat in probs: if cat==best: continue if probs[cat]*self.getthreshold(best)>probs[best]: return default return best
def cprob(self,f,cat): # The frequency of this feature in this category clf=self.fprob(f,cat) if clf==0: return 0 # The frequency of this feature in all the categories freqsum=sum([self.fprob(f,c) for c in self.categories()]) # The probability is the frequency in this category divided by # the overall frequency p=clf/(freqsum) return p def fisherprob(self,item,cat): # Multiply all the probabilities together p=1 features=self.getfeatures(item) for f in features: p*=(self.weightedprob(f,cat,self.cprob)) # Take the natural log and multiply by -2 fscore=-2*math.log(p) # Use the inverse chi2 function to get a probability 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 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): # Loop through looking for the best result best=default max=0.0 for c in self.categories(): p=self.fisherprob(item,c) # Make sure it exceeds its minimum if p>self.getminimum(c) and p>max: best=c max=p return best