基於概率論的分類方法:樸素貝葉斯
需要分類器做出分類決策,可以使分類器給出各個類別的概率估計值,然後選擇概率最高的作為其的類別。在這裡使用到了概率論中的貝葉斯公式:P(A|B)=P(A)*P(B|A)/P(B),其中P(A|B)是後驗概率,P(A)是先驗概率,P(B|A)/P(B)為調整因子(在已知結果的情況下對先驗概率大小做出相應調整得到後驗概率)
使用樸素貝葉斯進行文件分類
可以觀察文件中出現的單詞,並把每個詞的出現或者不出現作為一個特徵,這樣的得到的特徵數目就是單詞表的詞目數。
那麼由統計學得知,如果每個特徵需要N個樣本,則一千個特徵的詞彙表需要N^1000個樣本,但是如果假設特徵之間是相互獨立的,那麼樣本數就可以減少到1000*N(特徵相互獨立即特徵之間完全沒有關聯和規律,互不影響,那麼對其來說樣本N^1000或是1000*N的意義是一樣的),這也就是“樸素”的含義。當然這種假設並不正確,因為絕大多數特徵之間並不可能是毫無關聯毫無規律的,儘管如此,但樸素貝葉斯的實際效果卻很好。
樸素貝葉斯的分類器通常有兩種實現方式:一種是基於伯努力模型實現,一種是基於多項式模型實現。在文件分類中我們選擇第一種模型,不考慮詞在文件中出現多少次,只考慮出不出現(記錄為0或1),因此在這個意義上我們假設的是所有詞都是等權重的。
首先準備資料:需要我們把文字轉換為相應的詞條向量才能進行後面的運算。
import numpy as np from functools import reduce def loadDataSet(): datalist=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分出的詞條 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], ['stop', 'posting', 'stupid', 'worthless', 'garbage'], ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] label = [0,1,0,1,0,1] #各個詞條的類別標籤向量,1代表屬於侮辱性詞彙,0代表不是 return datalist,label def creatVocablist(dataset): #建立一個詞彙表,記錄所有出現過的單詞 vocabuset=set([]) for i in dataset: vocabuset=vocabuset|set(i) #set(i)將該行中重複單詞去除,並運算子將其並在詞彙列表中 return list(vocabuset) def setwords_tovec(vocabulist,inputset): #根據詞彙表,將inputset向量化,word存在則1,否則為零 vec=[0]*len(vocabulist) #初始化一個詞彙表長度的列表 for word in inputset: if word in vocabulist: vec[vocabulist.index(word)]=1 else:print("%s is not in my vocabulary!"% word) return vec datalist,label=loadDataSet() vocabulist=creatVocablist(datalist) #得到詞彙表 trainmat=[] for line in datalist: trainmat.append(setwords_tovec(vocabulist,line)) #得到向量化後的訓練集 print("總詞彙表為:\n" ,vocabulist) print("向量化的訓練集為:\n" ,trainmat)
下一步要開始訓練演算法,從詞向量來計算概率。
我們的目標是已知一個詞條的各個特徵(有哪些單詞出現了),然後給這個詞條分類(是否屬於侮辱性詞條),X表示這是一個向量,它由多個數值組成,A代表屬於侮辱類,B代表屬於非侮辱類,那麼根據貝葉斯公式:
文件屬於侮辱類概率:P(A|W)=P(A)*P(W|A)/P(W)
文件不屬於侮辱類概率:P(B|W)=P(B)*P(W|B)/P(W)
只需要比較兩者大小即可,概率大的就是該文件的類別。其中P(W)可以用全概率公式求解,但此處並不用,因為比較大小P(W)可以同時約去,減少計算量。
那麼P(A)就是所說的先驗概率:P(A)=訓練集文件中侮辱類數目÷訓練集文件總數目
由特徵獨立分佈可得:P(W|A)=P(w1|A)*P(w2|A)*......*P(wn|A),P(wn|A)則很好求,就是分類為A的詞條中該單詞出現的次數÷A分類所有單詞出現次數的總數。
求出P(A)、P(W|A)就可以得到P(A|W),同理得到P(B|W),比較兩者大小,選擇概率大的作為它的分類結果。
#樸素貝葉斯分類器訓練函式,求解需要的引數P(W|A),P(W|B),P(A),1-P(A)
def trainNB(trainmat,trainlabel):
numdocs=len(trainmat)
numwords=len(trainmat[0])
Pabusive=sum(trainlabel)/float(numdocs) #文件屬於侮辱類的概率,即先驗概率P(A)
p0num=np.ones(numwords);p1num=np.ones(numwords) #初始化為一,加一平滑,解決零概率問題,避免出現某一特徵概率為零從而導致P(X|A)為零
p0denom=2.0;p1denom=2.0 #分母初始化為二,原理相同
for i in range(numdocs):
if(trainlabel[i]==1):
p1num+=trainmat[i] #計算屬於侮辱類的概率
p1denom+=sum(trainmat[i])
else:
p0num+=trainmat[i]
p0denom+=sum(trainmat[i])
p1vec=np.log(p1num/p1denom)
p0vec=np.log(p0num/p0denom) #對特徵概率P(Xn|A)做對數處理,避免下溢位(因為數值太小不斷相乘越來越小導致最後四捨五入為零),採用對數處理不會造成任何損失。
return p0vec,p1vec,Pabusive
#根據得到的引數 p0vec,p1vec,Pabusive來計算最後的P1,P0並比較大小
def NBclassify(vec,p0vec,p1vec,Pabusive):
#reduce函式會對引數序列按照指定函式方法進行運算,使用lambda匿名函式
#p1=reduce(lambda x,y:x*y,vec*p0vec)*Pabusive 根據公式本該如此計算,但是在取了對數之後可以利用對數的性質將乘法運算化為求和(log(ab)=log(a)+log(b))
#p0=reduce(lambda x,y:x*y,vec*p1vec)*(1-Pabusive)
#改進後為:
p1=sum(vec*p1vec)+np.log(Pabusive) #並且取對數將連乘運算通過對數轉換為求和,還避免了零概率問題!因為只要有一個為零則相乘結果就會變為零!
p0=sum(vec*p0vec)+np.log(1-Pabusive)
print('p0:',p0)
print('p1:',p1)
if p1 > p0: #比較兩者的概率選取最大的哪一個
return 1
else:
return 0
最後輸入測試集,通過得到的分類器對其進行測試。
testEntry = ['love', 'stupid', 'dalmation'] #測試詞條樣本
thisDoc = np.array(setwords_tovec(vocabulist, testEntry)) #將測試樣本向量化
p0vec,p1vec,Pabusive=trainNB(trainmat,label) #放入訓練器求解引數
print(p0vec,p1vec)
if NBclassify(thisDoc,p0vec,p1vec,Pabusive):
print(testEntry,'屬於侮辱類') #執行分類並列印分類結果
else:
print(testEntry,'屬於非侮辱類')
結果顯示為:
參考學習自《機器學習實戰》