在前面《集體智慧編程學習之聚類系統》中,我對收藏的一些電子書做了聚類。當時是在沒有任何前驗知識的情況下,採用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
(二)費舍爾分類:
貝葉斯方法是計算書籍中所有片語在某個類目下的存在率之積,來判斷這本書籍會屬於那個分類的機率。費舍爾方法是直接根據片語屬於某個分類的存在率來估計書籍的分類,這個存在率的計算是P=該類目中該片語的數量/該片語出現總數。這樣判斷一本書籍屬於那個類目就簡單了,該書籍所有片語的存在率P之積中取個最大的即可。
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
這樣,分類結束了,原理和代碼都很簡單,希望自己能把自己說清楚。