1. 程式人生 > >基於概率論的分類方法:樸素貝葉斯

基於概率論的分類方法:樸素貝葉斯

需要分類器做出分類決策,可以使分類器給出各個類別的概率估計值,然後選擇概率最高的作為其的類別。在這裡使用到了概率論中的貝葉斯公式: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,'屬於非侮辱類')   

結果顯示為:

參考學習自《機器學習實戰》