1. 程式人生 > >【ML學習筆記】樸素貝葉斯演算法的demo(機器學習實戰例子)

【ML學習筆記】樸素貝葉斯演算法的demo(機器學習實戰例子)

礙於這學期課程的緊迫,現在需要儘快從課本上掌握一些ML演算法,我本不想經過danger zone,現在看來卻只能儘快進入danger zone,數學理論上的缺陷只能後面找時間彌補了。

如果你在讀這篇文章,希望你不要走像我一樣的道路,此舉實在是出於無奈,儘量不要去做一個心急的程式設計師,應當分清楚哪些資源是過了學生時期就不再容易獲得的。

樸素貝葉斯演算法簡述

不同於前面說的k-近鄰演算法,貝葉斯分類器是一種概率分類器,而樸素貝葉斯則是建立在兩個前提假設上的:

①特徵之間相互獨立
②每個特徵同等重要

如果要對一些物件分類,如桶裡有白球和黑球,隨機拿出一個球猜想拿出的是什麼球。如果完全沒有這個球的其它特徵資訊,很難判斷該猜是什麼球,如果通過先前的知識,如先前拿出過100個球,其中60個都是黑色的,又不知道這個球的其它特徵,為了減少錯誤率,肯定會猜這個球是黑球。這種基於標籤取各個值的概率的判別方式,就是在用先驗概率

做判別:
這裡寫圖片描述

而如果知道了一些特徵,如白色的球往往更粗糙(粗糙的球是白色的概率比粗糙的球是黑色的概率大),那如果摸到的是粗糙的球,很顯然就要判別為白色才會錯誤率最小了,這種獲得了特徵以後對標籤的判別,就是在用後驗概率做判別:
這裡寫圖片描述

如果特徵只有粗糙程度這一個,那麼很好辦,如果有很多特徵,如大/小,有缺口/無缺口,重/輕…,這裡舉的特徵都只有布林型取值即0或者1,也就是在判決之前,能獲得n個特徵資訊:
這裡寫圖片描述

這時候這些特徵的取值就可以組成一個向量了:
這裡寫圖片描述

而這時要做判別的方式,也就是特徵向量在這樣的值下,物件屬於哪一個類的概率最高,就判別為哪一類:
這裡寫圖片描述

根據貝葉斯公式,對每個特徵W而言,在該特徵取值為w時屬於類Ci的概率(後驗概率)可以這樣計算:
這裡寫圖片描述

而對於多個特徵資訊的情況,在這個特徵向量取這樣的值的情況下,屬於類Ci的概率(後驗概率)也是一樣的做法:
這裡寫圖片描述

因為最後是要拿後驗概率比較大小,找出最大的那個,而對於已經獲得的一個特徵序列而言,各個類在該特徵下的後驗概率與上式右邊的分母——特徵取該序列值的概率沒有關係,所以只要去比較分子上的類條件概率先驗概率
這裡寫圖片描述

對於先驗概率是比較容易求得的,而這裡的類條件概率是已知類為Ci的條件下,特徵向量取這個序列值的概率。

前面說了,樸素貝葉斯假設特徵之間是相互獨立的,因此特徵的聯合概率可以拆開來:
這裡寫圖片描述

從而在樸素貝葉斯假設下,所要比較的類條件概率和類的先驗概率乘積可以這樣展開:
這裡寫圖片描述

在現實的情況下,畢竟是用樣本集中的頻率去表徵概率,上面的式子中的某個特徵的類條件概率有可能算出來是0(因為樣本集總是有限的),這樣乘起來整個式子就是0,顯然不應因為一個特徵的值的出現而否決所有特徵,所以在實際做的時候,會把每個特徵的頻數都初始化為1,這樣即便後面再也沒出現過,也不至於讓這個頻率變成0;同時顯然要把每個類中各個特徵出現的總頻數初始化為特徵的數目n,因為剛剛已經把每個特徵的頻數都初始化為1了。這是一件事。

另一件事是,即便這些數相乘起來不該得到0了,但是演算法是在計算機上跑的,概率總是<1的值,太多很小的數相乘會造成下溢位。為了避免這件事,對概率取對數,因為對數和原來的數在相同的區域內同時增加或者減少,在相同的點取到極值,而我們要做的也僅僅是拿最後的概率去比較大小。對要比較大小的這塊(類條件概率和先驗概率的乘積)取對數得:
這裡寫圖片描述

也就是說樸素貝葉斯演算法要做的事情是:對每個類(標籤的每種可能取值)而言,對每個特徵在這個類的類條件概率取對數,然後全部加起來,再加上該類的先驗概率的對數,對於所有類的這樣的數,去比較大小,最大的那個所對應的類就成為樸素貝葉斯判別的那個類。

文字分類demo

問題描述

書上的例子是,訓練集給出了一個文件集合和一個標籤向量。文件集合不是矩陣,每一行的單詞數目都不必一致。

文件集合中的每一行對應了一個文件,也就是說,這一行中有若干個單詞,或許是網上的某個論壇下的一條評論中的一些單詞。

標籤向量還是對應著特徵矩陣中的每一行,標籤取值0表示那一行的言論是正當言論,標籤取值1表示那一行的言論是不正當的。

而要做的事情是,對於給出的一個文件,裡面有若干個詞,要判別出它是正當言論還是不正當的。

①建立模組和載入資料集的函式

還是建立一個新的python模組,匯入必要的包:

#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
   
  • 1
  • 2
  • 3
  • 4

因為只是demo,用6個樣本作為樣本集。直接返回文件集合和標籤(實際上這不是一個矩陣,每行的單詞個數都不必一致)。

#載入資料集(一些實驗樣本)
def loadDataSet():
    #進行詞條切分後的文件集合
    postingList=[['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']]
    classVec = [0,1,0,1,0,1] #標籤向量:1是不正當言辭,0是正常言論
    return postingList,classVec #返回文件集合,和對應於每個文件的標籤組成的向量
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這裡寫圖片描述

②建立詞表的函式

建立詞表的意義是,把訓練集中的所有詞有序地排在一個列表中,這樣就為後面的詞向量打下了基礎。因為可以在後面用詞向量的每個位置上用1或者0來代表詞表中這個位置的詞有沒有在那個文件中出現了。

#建立一個包含輸入的所有文件中的詞的不重複詞表
def createVocabList(dataSet):
    vocabSet=set([]) #先建立一個空集合vocabSet
    #對於資料集中的每個記錄(文件集合中的每個詞條)
    for document in dataSet:
        #將其打散為詞的集合,然後並(|操作符)入這個集合
        vocabSet=vocabSet | set(document)
    return list(vocabSet) #返回的即是每個詞出現一次的list
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這裡寫圖片描述

③建立詞集模型的函式

建立詞集模型,也就是去建立用0/1表示不出現/出現的詞向量。設定這個函式是很有用的,因為在訓練和使用分類器的時候都要把存了詞彙的文件列表轉化成和詞表相關的詞向量,才能去做概率計算。

#[A]詞集模型
#判定詞彙表中的哪些詞出現在文件中(詞彙表vocabList,輸入文件inputSet)
#輸出一個和詞彙表等長的0/1向量,為1的位置表示詞彙表中那個詞在文件中出現了
def setOfWords2Vec(vocabList,inputSet):
    returnVec=[0]*len(vocabList) #先建立一個和詞表等長的0向量
    #對於輸入文件中的每個詞
    for word in inputSet:
        #如果這個詞在詞彙表中
        if word in vocabList:
            #將0/1向量對應位置的值設定為1
            returnVec[vocabList.index(word)]=1
        else:
            print "詞%s不在詞表中!"%word #否則要提示出現了新詞
    return returnVec #返回這個0/1向量
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如利用前面的詞表,把樣本集中的一行行文件轉化為一行行詞向量:
這裡寫圖片描述
這也就得到了特徵矩陣。

④訓練分類器的函式

訓練分類器總是需要輸入訓練集的特徵矩陣和標籤向量,在樸素貝葉斯演算法中,訓練分類器的目的是得到每個類上每個詞的類條件概率(不如把同一類的類條件概率放到一個向量裡),即得到類條件概率p(w|c)向量,還要知道各個類的先驗概率的值,因為這裡是二分類問題,所以只需要知道一個值就行了,這裡返回的是1號類的先驗概率。

#樸素貝葉斯分類器的訓練函式(特徵矩陣,標籤向量)
#得到的是針對0/1類的每個詞的類條件概率p(w|c)向量,和1類先驗概率
#而0類的先驗概率p(c0)就是1-p(c1)
def TraPsBys(dataMat,labelVec):
    m=len(dataMat) #特徵矩陣行數:樣本集的樣本數目
    n=len(dataMat[0]) #特徵矩陣列數:特徵數目
    #因為標籤只有0/1故這個值是訓練集中樣本屬於第1類的概率
    #即屬於不正當言辭的先驗概率p(c1)
    pClass1=sum(labelVec)/float(m)
    #現實改進①
    #當計算多個概率的乘積p(w0|c)p(w1|c)...p(wn|c)時
    #如果其中一個概率是0,最後的乘積也是0,為了避免這種影響
    #把所有詞的出現次數初始化為1,分母初始化為2
    p0Num=ones(n) #存第0類(正常言辭)的各詞出現頻數向量
    p1Num=ones(n) #存第1類(不正言辭)的各詞出現頻數向量
    p0Denom=1.0*n #存詞在第0類出現的總頻數,初始化為n
    p1Denom=1.0*n #存詞在第1類出現的總頻數,初始化為n
    #對訓練集中的每個記錄行,i表示其行號
    for i in range(m):
        #如果是第1類
        if labelVec[i]==1:
            #把這行各個詞出現情況加到1號類的向量上
            p1Num+=dataMat[i]
            #把這行詞的總數目加到第1類頻數上
            p1Denom+=sum(dataMat[i])
        #如果是第0類
        else:
            #把這行各個詞出現情況加到0號類的向量上
            p0Num+=dataMat[i]
            #把這行詞的總數目加到第0類頻數上
            p0Denom+=sum(dataMat[i])
    #求特徵的某值w出現於c類的類條件概率向量,即
    #P(w|c)=值w在c類中出現次數/各可能值在c類中出現總次數
    p0Vect=p0Num/p0Denom #第0類的各詞,類條件概率密度=本詞出現次數/總次數
    p1Vect=p1Num/p1Denom #第1類的各詞,類條件概率密度=本詞出現次數/總次數
    #現實改進②
    #太多很小的數相乘會下溢位,取對數來避免這種情況
    #對數和原來的數在相同的區域同時增減,而且在相同的點取極值
    #但要注意,原來數的相乘,就是取過對數數的相加
    p0Vect=log(p0Vect)
    p1Vect=log(p1Vect)
    #返回針對0/1類的每個詞的類條件概率p(w|c)向量,和1類先驗概率p(c1)
    return p0Vect,p1Vect,pClass1
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

如傳入剛才的特徵矩陣和標籤向量,這裡得到的類條件概率向量是取過對數的了。而先驗概率還沒有取對數,一方面是方便我們觀察一下先驗概率的取值;另一方面,要根據這個先驗概率去計算另一類的先驗概率,這樣暫時不取對數還是方便一些。
這裡寫圖片描述
屬於1號類的先驗概率:
這裡寫圖片描述

⑤做分類用的函式

這個函式能夠對輸入的詞向量,用訓練好的引數(類條件概率向量和先驗概率),給出預測的結果值。

具體的做法就是,對於這兩個類(1號和0號類),分別把詞向量表示的出現的各個特徵取對數後的值全加起來,再加上這一類的先驗概率取對數後的值。

對於這兩個類都這樣做,最後比較大小,哪個大就輸出哪個類的標號即可。

#樸素貝葉斯分類函式(要分類的詞向量,0/1類條件概率向量,1類先驗概率)
def CsfPsBys(vec2Classify,p0Vec,p1Vec,pClass1):
    #因為0/1類條件概率向量在TraPsBys裡取過對數了
    #所以類的條件概率在這裡也要取一下對數
    p1=sum(vec2Classify*p1Vec)+log(pClass1)
    #同樣求後驗概率,第0類的先驗概率p(c0)就是1-p(c1),因為是二分類問題
    p0=sum(vec2Classify*p0Vec)+log(1.0-pClass1)
    #用這兩個求出來的描述後驗概率的值做判別
    if p1>p0:
        return 1
    else:
        return 0
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

比如建立一個文件來測試一下:
這裡寫圖片描述
預測為1,即預測為不正當言論。

⑥測試用的函式

一個封裝好的便利函式,可以用來理解整個分類器的使用流程。

#測試這個demo,封裝了所有操作的函式可稱為'便利函式'
def TstPsBys():
    postingList,classVec=loadDataSet() #載入文件集合和標籤向量
    vocabList=createVocabList(postingList) #用文件集建立不重複詞表
    #以下要建立訓練集,給出的文件集合需要轉化成和詞表相關的0/1向量集
    trainMat=[] #訓練集初始化為空
    #對於文件集合中的每一行的文件postInDoc
    for postInDoc in postingList:
        #把這個文件用不重複詞表vocabList轉換成存在性的0/1向量
        #然後把這個表示詞表上每個位置詞出現情況的向量加入訓練集
        #顯然這個演算法裡丟失了文件中詞彙的順序資訊!
        trainMat.append(setOfWords2Vec(vocabList,postInDoc))
    #用{有關詞表的0/1式訓練集,標籤向量}做訓練
    #得到p(w|c0)向量,p(w|c1)向量,p(c1)
    #即得到了類條件概率向量和先驗概率,即貝葉斯公式的分子
    p0V,p1V,pC1=TraPsBys(trainMat,classVec)
    #測試①
    testEntry=['love','my','dalmation'] #要分類的詞向量
    #轉化為與詞表等長的0/1式存在性向量
    thisDoc=array(setOfWords2Vec(vocabList,testEntry))
    #分類並輸出結果
    print testEntry,"分類為:",CsfPsBys(thisDoc,p0V,p1V,pC1)
    #測試②
    testEntry=['stupid','garbage']
    thisDoc=array(setOfWords2Vec(vocabList,testEntry))
    print testEntry,"分類為:",CsfPsBys(thisDoc,p0V,p1V,pC1)
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

這裡寫圖片描述

⑦建立詞袋模型的函式

和建立詞集模型的函式功能上是類似的。因為樸素貝葉斯分類器有兩種實現方式,一種是基於伯努利模型實現,一種是基於多項式模型實現。前者不考慮詞在文件中出現的次數,將沒歌詞的出現與否作為一個特徵,稱為詞集模型;後者需要考慮詞在文件中出現的次數,這也就包含了更多的資訊,稱為詞袋模型

#[B]詞袋模型
#判定詞彙表中的哪些詞出現在文件中(詞彙表vocabList,輸入文件inputSet)
#輸出一個和詞彙表等長的0/k向量,非0的位置表示詞彙表中那個詞在文件中出現了k次
def bagOfWords2Vec(vocabList,inputSet):
    returnVec=[0]*len(vocabList) #先建立一個和詞表等長的0向量
    #對於輸入文件中的每個詞
    for word in inputSet:
        #如果這個詞在詞彙表中
        if word in vocabList:
            #將0/k向量對應位置的值加上1
            returnVec[vocabList.index(word)]+=1
        else:
            print "詞%s不在詞表中!"%word #否則要提示出現了新詞
    return returnVec #返回這個0/k向量
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這裡寫圖片描述