樸素貝葉斯公式及其推導回來補上,今天只上代碼
一、提取文檔特徵
#提取文檔特徵,此處爲對文檔進行分詞處理,每個單詞即爲文檔的特徵
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#獲得提取特徵函數
def incf(self,f,cat):
self.fc.setdefault(f,{})
self.fc[f].setdefault(cat,0)
self.fc[f][cat]+=1#特徵f所屬分類cat的頻度加1
def incc(self,cat):
self.cc.setdefault(cat,0)
self.cc[cat]+=1#分類cat的頻度加1
def fcount(self,f,cat):
if f in self.fc and cat in self.fc[f]:
return float(self.fc[f][cat])#返回特徵f所屬分類cat的頻度
return 0.0
def catcount(self,cat):
if cat in self.cc:
return float(self.cc[cat])#返回分類cat的頻度
return 0.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)#對每個特徵屬於cat分類的頻度加1
self.incc(cat)#cat分類的頻度加1
def fprob(self,f,cat):
if self.catcount(cat)==0:
return 0
return self.fcount(f,cat)/self.catcount(cat)#計算在cat分類的條件下含有特徵f的條件概率
#相當於對特殊情況(比如特徵f屬於分類cat的頻度爲0)計算條件概率進行的平滑處理
def weightedprob(self,f,cat,prf,weight=1.0,ap=0.5):
basicprob=prf(f,cat)
totals=sum(self.fc[f].values())
#totals=sum([self.fcount(f,c) for c in self.categories()])
bp=(totals*basicprob+weight*ap)/(weight+totals)
return bp
三、樸素貝葉斯分類器設計
class naivebayes(classifier):
def __init__(self,getfeatures):
classifier.__init__(self,getfeatures)
self.thresholds={}
#閾值,即最佳預測分類必須滿足特定條件才能作爲預測結果,不同的設定可影響預測的準確率
#比如在分類垃圾郵件的應用中就可提高分類爲垃圾郵件的門檻,避免誤將重要郵件分類爲垃圾郵件造成損失
def setthresholds(self,cat,t):
self.thresholds[cat]=t#設定閾值
def getthresholds(self,cat):
if cat not in self.thresholds:
return 1.0
return self.thresholds[cat]#得到閾值
def classfy(self,item,default=None):
probs={}
max=0.0
best=default
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.getthresholds(best)>probs[best]:
return default
return best#滿足閾值設定才返回作爲預測結果
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()#分類cat的概率
docprob=self.docprob(item,cat)#在分類爲cat的條件下含有item所有特徵的概率
return docprob*catprob#返回樸素貝葉斯分類器值
附加:費舍爾方法
class fisherclassifier(classifier):
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 classfy(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
def cprob(self,f,cat):
clf=self.fprob(f,cat)#f所屬爲cat分類的概率
if clf==0:
return 0
freqsum=sum([self.fprob(f,c) for c in self.categories()])#f所屬爲各個分類的概率總和
p=clf/freqsum#直接計算給定特徵f屬於分類cat的條件概率
return p
def fisherprob(self,item,cat):
p=1
features=self.getfeatures(item)
for f in features:
p*=self.weightedprob(f,cat,self.cprob)#item每個特徵屬於cat分類條件概率的乘積
fscore=-2*math.log(p)#取自然對數,並乘以-2
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 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')
cl_bayes=naivebayes(getwords)
sampletrain(cl_bayes)
print(cl_bayes.classfy('quick rabbit'))
print(cl_bayes.classfy('quick money'))
cl_bayes.setthresholds('bad',3.0)
for i in range(5):
sampletrain(cl_bayes)
print(i,cl_bayes.classfy('quick money',default='unknown'))
print(' ')
cl_fisher=fisherclassifier(getwords)
sampletrain(cl_fisher)
print(cl_fisher.classfy('quick rabbit'))
print(cl_fisher.classfy('quick money'))
cl_fisher.setminimum('bad',0.75)
for i in range(5):
sampletrain(cl_fisher)
print(i,cl_fisher.classfy('quick money',default='unknown'))
結果輸出
good
bad
0 unknown
1 unknown
2 unknown
3 bad
4 bad
good
bad
0 good
1 good
2 bad
3 bad
4 bad
從結果可以看出,設定閾值可以提升分類的門檻,在一些重要場合下可以降低誤分類的風險。同時,隨着訓練的不斷加深,超過閾值設定後的分類顯得“更有底氣”。