機器學習算法及實戰——朴素貝葉斯代碼實現


朴素貝葉斯是經典的機器學習算法之一,也是為數不多的基於概率論的分類算法。朴素貝葉斯原理簡單,也很容易實現,多用於文本分類,比如垃圾郵件過濾。


1.算法思想——基於概率的預測

      邏輯回歸通過擬合曲線(或者學習超平面)實現分類,決策樹通過尋找最佳划分特征進而學習樣本路徑實現分類,支持向量機通過尋找分類超平面進而最大化類別間隔實現分類。相比之下,朴素貝葉斯獨辟蹊徑,通過考慮特征概率來預測分類。

      舉個可能不太恰當的例子:眼前有100個人,好人和壞人個數差不多,現在要用他們來訓練一個“壞蛋識別器”。怎么辦呢?咱們不管他們干過啥事,只看他們長啥樣(這確實不是個恰當的例子)。也就是說,我們在區分好壞人時,只考慮他們的樣貌特征。比如說“笑”這個特征,它的取值可能是“甜美的笑”、“儒雅的笑”、“憨厚的笑”、“沒心沒肺的笑”、“微微一笑”,等等——這都是“好人的笑”;也可以是“陰險的笑”、“不屑的笑”、“色眯眯的笑”、“任我行似的笑”、“冷笑”、“皮笑肉不笑”,等等——這很可能是“壞人的笑”。單就“笑”這個特征來說,一個好人發出“好人的笑”的概率更大,而且頻率更高;而壞人則發出“壞人的笑”的概率更大,頻率更高(電視上總能看見作奸犯科的人在暗地里發出挨千刀的笑)。當然,好人也有發出壞笑的時候(那種偶像劇里面男豬腳“壞壞的笑”),壞人也有發出好人的笑的時候(想想《不要和陌生人說話》里面的馮遠征),這些就都是噪聲了。

      除了笑之外,這里可用的特征還有紋身,性別等可以考慮。朴素貝葉斯把類似“笑”這樣的特征概率化,構成一個“人的樣貌向量”以及對應的“好人/壞人標簽”,訓練出一個標准的“好人模型”和“壞人模型”,這些模型都是各個樣貌特征概率構成的。這樣,當一個品行未知的人來以后,我們迅速獲取ta的樣貌特征向量,分布輸入“好人模型”和“壞人模型”,得到兩個概率值。如果“壞人模型”輸出的概率值大一些,那這個人很有可能就是個大壞蛋了。

      決策樹是怎么辦的呢?決策樹可能先看性別,因為它發現給定的帶標簽人群里面男的壞蛋特別多,這個特征眼下最能區分壞蛋和好人,然后按性別把一撥人分成兩撥;接着看“笑”這個特征,因為它是接下來最有區分度的特征,然后把兩撥人分成四撥;接下來看紋身,,,,最后發現好人要么在田里種地,要么在山上砍柴,要么在學堂讀書。而壞人呢,要么在大街上溜達,要么在地下買賣白粉,要么在海里當海盜。這些個有次序的特征就像路上的一個個墊腳石(樹的節點)一樣,構成通往不同地方的路徑(樹的枝丫),這些不同路徑的目的地(葉子)就是一個類別容器,包含了一類人。一個品行未知的人來了,按照其樣貌特征順序及其對應的特征值,不斷走啊走,最后走到了農田或山上,那就是好人;走到了地下或大海,那就是大壞蛋。(這是個看臉的例子,但重點不是“臉”,是“例子”,這真的只是個沒有任何偏見的例子)。可以看出來,兩種分類模型的原理是很不相同。


2.理論基礎——條件概率,詞集模型、詞袋模型

    條件概率:朴素貝葉斯最核心的部分是貝葉斯法則,而貝葉斯法則的基石是條件概率。貝葉斯法則如下: 
這里寫圖片描述 
    這里的C表示類別,輸入待判斷數據,式子給出要求解的某一類的概率。我們的最終目的是比較各類別的概率值大小,而上面式子的分母是不變的,因此只要計算分子即可。仍以“壞蛋識別器”為例。我們用C0表示好人,C1表示壞人,現在100個人中有60個好人,則P(C0)=0.6,那么P(x,y|C0)怎么求呢?注意,這里的(x,y)是多維的,因為有60個好人,每個人又有“性別”、“笑”、“紋身”等多個特征,這些構成X,y是標簽向量,有60個0和40個1構成。這里我們假設X的特征之間是獨立的,互相不影響,這就是朴素貝葉斯中“朴素”的由來。在假設特征間獨立的假設下,很容易得到P(x,y|C0)=P(x0,y0|C0)P(x1,y1|C0)…P(xn,yn|C0)。然而,P(xn,yn|C0),n=0,1,…,n如何求呢?有兩種情況,涉及到詞集模型和詞袋模型。接下來我們舉個更合適的例子,那就是文本分類。我們的訓練集由正常的文檔和侮辱性的文檔組成,能反映侮辱性文檔的是侮辱性詞匯的出現與否以及出現頻率。

  • 詞集模型:對於給定文檔,只統計某個侮辱性詞匯(准確說是詞條)是否在本文檔出現
  • 詞袋模型:對於給定文檔,統計某個侮辱性詞匯在本文當中出現的頻率,除此之外,往往還需要剔除重要性極低的高頻詞和停用詞。因此,詞袋模型更精煉,也更有效。

    需要解釋的是,為了高效計算,求解P(x,y|C0)時是向量化操作的,因此不會一個個的求解P(xn,yn|C0)。


3.數據預處理——向量化

    向量化、矩陣化操作是機器學習的追求。從數學表達式上看,向量化、矩陣化表示更加簡潔;在實際操作中,矩陣化(向量是特殊的矩陣)更高效。仍然以侮辱性文檔識別為例:

首先 ,我們需要一張詞典,該詞典囊括了訓練文檔集中的所有必要詞匯(無用高頻詞和停用詞除外),還需要把每個文檔剔除高頻詞和停用詞; 
其次,根據詞典向量化每個處理后的文檔。具體的,每個文檔都定義為詞典大小,分別遍歷某類(侮辱性和非侮辱性)文檔中的每個詞匯並統計出現次數; 
然后,得到一個個跟詞典一樣大小的向量,這些向量有一個個整數組成,每個整數代表了詞典上一個對應位置的詞在當下文檔中的出現頻率; 
最后,統計每一類處理過的文檔中詞匯總個數,某一個文檔的詞頻向量除以相應類別的詞匯總個數,即得到相應的條件概率,如P(x,y|C0)。有了P(x,y|C0)和P(C0),就可以得到P(C0|x,y)了,用完全一樣的方法可以獲得P(C1|x,y)。比較它們的大小,即可知道某人是不是大壞蛋,某篇文檔是不是侮辱性文檔了。


4.Python代碼解讀

 1 def loadDataSet():
 2     postingList=[['my','dog','has','flea','problem','help','please'],
 3                  ['maybe','not','take','him','to','dog','park','stupid'],
 4                  ['my','dalmation','is','so','cute','I','love','him'],
 5                  ['stop','posting','ate','my','steak','how','to','stop','him'],
 6                  ['mr','licks','ate','my','steak','how','to','stop','him'],
 7                  ['quit','buying','worthless','dog','food','stupid']]
 8     classVec=[0,1,0,1,0,1]
 9     return postingList,classVec
10 #定義一個簡單的文本數據集,由6個簡單的文本以及對應的標簽構成。1表示侮辱性文檔,0表示正常文檔。
11 def createVocabList(dataSet):
12     vocabSet=set([])
13     for document in dataSet:
14         vocabSet=vocabSet|set(document)
15     return list(vocabSet)
16 def setOfWords2Vec(vocabList,inputSet):
17     returnVec=[0]*len(vocabList)               #每個文檔的大小與詞典保持一致,此時returnVec是空表
18     for word in inputSet:
19         if word in vocabList:
20             returnVec[vocabList.index(word)]=1 #當前文檔中有某個詞條,則根據詞典獲取其位置並賦值1
21         else:print "the word :%s is not in my vocabulary" %word
22     return returnVec        
23 def bagOfWords2Vec(vocabList,inputSet):
24     returnVec=[0]*len(vocabList)
25     for word in inputSet:
26         if word in vocabList:
27             returnVec[vocabList.index(word)]+=1 # 與詞集模型的唯一區別就表現在這里
28         else:print "the word :%s is not in my vocabulary" %word
29     return returnVec
30 #### 文檔向量化,這里是詞袋模型,不知關心某個詞條出現與否,還考慮該詞條在本文檔中的出現頻率
31 
32 def trainNB(trainMatrix,trainCategory):                                        
33     numTrainDocs=len(trainMatrix)     
34     numWords=len(trainMatrix[0])        
35     pAbusive=sum(trainCategory)/float(numTrainDocs) #統計侮辱性文檔的總個數,然后除以總文檔個數  
36     #p0Num=zeros(numWords);p1Num=zeros(numWords)    # 把屬於同一類的文本向量加起來
37     #p0Denom=0.0;p1Denom=0.0
38     p0Num=ones(numWords);p1Num=ones(numWords)
39     p0Denom=2.0;p1Denom=2.0
40     for i in range(numTrainDocs):
41         if trainCategory[i]==1: 
42             p1Num+=trainMatrix[i]#把屬於同一類的文本向量相加,實質是統計某個詞條在該類文本中出現頻率
43             p1Denom+=sum(trainMatrix[i]) #把侮辱性文檔向量的所有元素加起來
44         else:
45             p0Num+=trainMatrix[i]
46             p0Denom+=sum(trainMatrix[i]) 
47     #p1Vec=p1Num/float(p1Denom) 
48     #p0Vec=p0Num/float(p0Denom)
49     p1Vec=log(p1Num/p1Denom) #統計詞典中所有詞條在侮辱性文檔中出現的概率
50     p0Vec=log(p0Num/p0Denom) #統計詞典中所有詞條在正常文檔中出現的概率
51     return pAbusive,p1Vec,p0Vec
52 #### 訓練生成朴素貝葉斯模型,實質上相當於是計算P(x,y|Ci)P(Ci)的權重。
53 ### 注意:被注釋掉的代碼代表不太好的初始化方式,在那種情況下某些詞條的概率值可能會非常非常小,甚至約
54 ###等於0,那么在不同詞條的概率在相乘時結果就近似於0
55 def classifyNB(vec2classify,p0Vec,p1Vec,pClass1):  # 參數1是測試文檔向量,參數2和參數3是詞條在各個
56                                                     #類別中出現的概率,參數4是P(C1)
57     p1=sum(vec2classify*p1Vec)+log(pClass1)   # 這里沒有直接計算P(x,y|C1)P(C1),而是取其對數
58                                                  #這樣做也是防止概率之積太小,以至於為0
59     p0=sum(vec2classify*p0Vec)+log(1.0-pClass1) #取對數后雖然P(C1|x,y)和P(C0|x,y)的值變了,但是
60                                                  #不影響它們的大小關系。
61     if p1>p0:
62         return 1
63     else:
64         return 0

 

 


5.總結

  • 不同於其它分類器,朴素貝葉斯是一種基於概率理論的分類算法;
  • 特征之間的條件獨立性假設,顯然這種假設顯得“粗魯”而不符合實際,這也是名稱中“朴素”的由來。然而事實證明,朴素貝葉斯在有些領域很有用,比如垃圾郵件過濾;
  • 在具體的算法實施中,要考慮很多實際問題。比如因為“下溢”問題,需要對概率乘積取對數;再比如詞集模型和詞袋模型,還有停用詞和無意義的高頻詞的剔除,以及大量的數據預處理問題,等等;
  • 總體上來說,朴素貝葉斯原理和實現都比較簡單,學習和預測的效率都很高,是一種經典而常用的分類算法。

【來自】 
深入理解朴素貝葉斯(Naive Bayes)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM