机器学习实战教程(四):朴素贝叶斯基础篇之言论过滤器
?
?
一、前言
? ? ? ? 樸素貝葉斯算法是有監督的學習算法,解決的是分類問題,如客戶是否流失、是否值得投資、信用等級評定等多分類問題。該算法的優點在于簡單易懂、學習效率高、在某些領域的分類問題中能夠與決策樹、神經網絡相媲美。但由于該算法以自變量之間的獨立(條件特征獨立)性和連續變量的正態性假設為前提,就會導致算法精度在某種程度上受影響。
? ? ? 本篇文章將從樸素貝葉斯推斷原理開始講起,通過實例進行輔助講解。最后,使用Python3編程實現一個簡單的言論過濾器。
?
二、樸素貝葉斯理論
樸素貝葉斯是貝葉斯決策理論的一部分,所以在講述樸素貝葉斯之前有必要快速了解一下貝葉斯決策理論。
1、貝葉斯決策理論
假設現在我們有一個數據集,它由兩類數據組成,數據分布如下圖所示:
我們現在用p1(x,y)表示數據點(x,y)屬于類別1(圖中紅色圓點表示的類別)的概率,用p2(x,y)表示數據點(x,y)屬于類別2(圖中藍色三角形表示的類別)的概率,那么對于一個新數據點(x,y),可以用下面的規則來判斷它的類別:
- 如果p1(x,y)>p2(x,y),那么類別為1
- 如果p1(x,y)<p2(x,y),那么類別為2
也就是說,我們會選擇高概率對應的類別。這就是貝葉斯決策理論的核心思想,即選擇具有最高概率的決策。已經了解了貝葉斯決策理論的核心思想,那么接下來,就是學習如何計算p1和p2概率。
2、條件概率
在學習計算p1?和p2概率之前,我們需要了解什么是條件概率(Conditional probability),就是指在事件B發生的情況下,事件A發生的概率,用P(A|B)來表示。
? ? ? ? ? ? ? ? ? ? ?
根據文氏圖,可以很清楚地看到在事件B發生的情況下,事件A發生的概率就是P(A∩B)除以P(B)。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
因此,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
同理可得
? ? ? ? ? ? ? ? ? ? ? ??
所以,
? ? ? ? ? ? ? ? ? ??
即
? ? ? ? ? ? ? ??
這就是條件概率的計算公式。
3、全概率公式
除了條件概率以外,在計算p1和p2的時候,還要用到全概率公式,因此,這里繼續推導全概率公式。
假定樣本空間S,是兩個事件A與A'的和。
? ? ? ? ??
上圖中,紅色部分是事件A,綠色部分是事件A',它們共同構成了樣本空間S。
在這種情況下,事件B可以劃分成兩個部分。
? ? ? ??
即
? ? ? ? ? ? ? ? ? ? ?
在上一節的推導當中,我們已知
? ? ? ? ? ? ? ? ??
所以
? ? ? ? ? ? ? ?
這就是全概率公式。它的含義是,如果A和A'構成樣本空間的一個劃分,那么事件B的概率,就等于A和A'的概率分別乘以B對這兩個事件的條件概率之和。
將這個公式代入上一節的條件概率公式,就得到了條件概率的另一種寫法:
? ? ? ? ? ??
4、貝葉斯推斷
對條件概率公式進行變形,可以得到如下形式:
? ? ? ? ?
我們把P(A)稱為"先驗概率"(Prior probability),即在B事件發生之前,我們對A事件概率的一個判斷。
P(A|B)稱為"后驗概率"(Posterior probability),即在B事件發生之后,我們對A事件概率的重新評估。
P(B|A)/P(B)稱為"可能性函數"(Likelyhood),這是一個調整因子,使得預估概率更接近真實概率。
所以,條件概率可以理解成下面的式子:
后驗概率 = 先驗概率 x 調整因子
這就是貝葉斯推斷的含義。我們先預估一個"先驗概率",然后加入實驗結果,看這個實驗到底是增強還是削弱了"先驗概率",由此得到更接近事實的"后驗概率"。
在這里,如果"可能性函數"P(B|A)/P(B)>1,意味著"先驗概率"被增強,事件A的發生的可能性變大;如果"可能性函數"=1,意味著B事件無助于判斷事件A的可能性;如果"可能性函數"<1,意味著"先驗概率"被削弱,事件A的可能性變小。
為了加深對貝葉斯推斷的理解,我們舉一個例子。
? ? ? ?
兩個一模一樣的碗,一號碗有30顆水果糖和10顆巧克力糖,二號碗有水果糖和巧克力糖各20顆?,F在隨機選擇一個碗,從中摸出一顆糖,發現是水果糖。請問這顆水果糖來自一號碗的概率有多大?
我們假定,H1表示一號碗,H2表示二號碗。由于這兩個碗是一樣的,所以P(H1)=P(H2),也就是說,在取出水果糖之前,這兩個碗被選中的概率相同。因此,P(H1)=0.5,我們把這個概率就叫做"先驗概率",即沒有做實驗之前,來自一號碗的概率是0.5。
再假定,E表示水果糖,所以問題就變成了在已知E的情況下,來自一號碗的概率有多大,即求P(H1|E)。我們把這個概率叫做"后驗概率",即在E事件發生之后,對P(H1)的修正。
根據條件概率公式,得到
? ? ? ??
已知,P(H1)等于0.5,P(E|H1)為一號碗中取出水果糖的概率,等于30÷(30+10)=0.75,那么求出P(E)就可以得到答案。根據全概率公式,
? ? ? ?
所以,
? ? ?
將數字代入原方程,得到
? ? ??
這表明,來自一號碗的概率是0.6。也就是說,取出水果糖之后,H1事件的可能性得到了增強。
同時再思考一個問題,在使用該算法的時候,如果不需要知道具體的類別概率,即上面P(H1|E)=0.6,只需要知道所屬類別,即來自一號碗,我們有必要計算P(E)這個全概率嗎?要知道我們只需要比較 P(H1|E)和P(H2|E)的大小,找到那個最大的概率就可以。既然如此,兩者的分母都是相同的,那我們只需要比較分子即可。即比較P(E|H1)P(H1)和P(E|H2)P(H2)的大小,所以為了減少計算量,全概率公式在實際編程中可以不使用。
5、樸素貝葉斯推斷
理解了貝葉斯推斷,那么讓我們繼續看看樸素貝葉斯。貝葉斯和樸素貝葉斯的概念是不同的,區別就在于“樸素”二字,樸素貝葉斯對條件個概率分布做了條件獨立性的假設。 比如下面的公式,假設有n個特征:
? ? ??
由于每個特征都是獨立的,我們可以進一步拆分公式 :
? ? ??
這樣我們就可以進行計算了。如果有些迷糊,讓我們從一個例子開始講起,你會看到貝葉斯分類器很好懂,一點都不難。
某個醫院早上來了六個門診的病人,他們的情況如下表所示:
現在又來了第七個病人,是一個打噴嚏的建筑工人。請問他患上感冒的概率有多大?
根據貝葉斯定理:
? ? ? ??
可得:
? ? ? ?
根據樸素貝葉斯條件獨立性的假設可知,"打噴嚏"和"建筑工人"這兩個特征是獨立的,因此,上面的等式就變成了
這里可以計算:
因此,這個打噴嚏的建筑工人,有66%的概率是得了感冒。同理,可以計算這個病人患上過敏或腦震蕩的概率。比較這幾個概率,就可以知道他最可能得什么病。
這就是貝葉斯分類器的基本方法:在統計資料的基礎上,依據某些特征,計算各個類別的概率,從而實現分類。
同樣,在編程的時候,如果不需要求出所屬類別的具體概率,P(打噴嚏) = 0.5和P(建筑工人) = 0.33的概率是可以不用求的。
?
三、動手實戰
說了這么多,沒點實踐編程怎么行?
以在線社區留言為例。為了不影響社區的發展,我們要屏蔽侮辱性的言論,所以要構建一個快速過濾器,如果某條留言使用了負面或者侮辱性的語言,那么就將該留言標志為內容不當。過濾這類內容是一個很常見的需求。對此問題建立兩個類型:侮辱類和非侮辱類,使用1和0分別表示。
我們把文本看成單詞向量或者詞條向量,也就是說將句子轉換為向量??紤]出現所有文檔中的單詞,再決定將哪些單詞納入詞匯表或者說所要的詞匯集合,然后必須要將每一篇文檔轉換為詞匯表上的向量。簡單起見,我們先假設已經將本文切分完畢,存放到列表中,并對詞匯向量進行分類標注。編寫代碼如下:
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,classVecif __name__ == '__main__':postingLIst, classVec = loadDataSet()for each in postingLIst:print(each)print(classVec)從運行結果可以看出,我們已經將postingList是存放詞條列表中,classVec是存放每個詞條的所屬類別,1代表侮辱類 ,0代表非侮辱類。
繼續編寫代碼,前面我們已經說過我們要先創建一個詞匯表,并將切分好的詞條轉換為詞條向量。
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 def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #創建一個其中所含元素都為0的向量for word in inputSet: #遍歷每個詞條if word in vocabList: #如果詞條存在于詞匯表中,則置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec def createVocabList(dataSet):vocabSet = set([]) #創建一個空的不重復列表for document in dataSet: vocabSet = vocabSet | set(document) #取并集return list(vocabSet)if __name__ == '__main__':postingList, classVec = loadDataSet()print('postingList:\n',postingList)myVocabList = createVocabList(postingList)print('myVocabList:\n',myVocabList)trainMat = []for postinDoc in postingList:trainMat.append(setOfWords2Vec(myVocabList, postinDoc))print('trainMat:\n', trainMat)?從運行結果可以看出,postingList是原始的詞條列表,myVocabList是詞匯表。myVocabList是所有單詞出現的集合,沒有重復的元素。詞匯表是用來干什么的?沒錯,它是用來將詞條向量化的,一個單詞在詞匯表中出現過一次,那么就在相應位置記作1,如果沒有出現就在相應位置記作0。trainMat是所有的詞條向量組成的列表。它里面存放的是根據myVocabList向量化的詞條向量
我們已經得到了詞條向量。接下來,我們就可以通過詞條向量訓練樸素貝葉斯分類器。
我們已經得到了詞條向量。接下來,我們就可以通過詞條向量訓練樸素貝葉斯分類器。
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 def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #創建一個其中所含元素都為0的向量for word in inputSet: #遍歷每個詞條if word in vocabList: #如果詞條存在于詞匯表中,則置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec def createVocabList(dataSet):vocabSet = set([]) #創建一個空的不重復列表for document in dataSet: vocabSet = vocabSet | set(document) #取并集return list(vocabSet)def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #計算訓練的文檔數目numWords = len(trainMatrix[0]) #計算每篇文檔的詞條數pAbusive = sum(trainCategory)/float(numTrainDocs) #文檔屬于侮辱類的概率p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #創建numpy.zeros數組,詞條出現數初始化為0p0Denom = 0.0; p1Denom = 0.0 #分母初始化為0for i in range(numTrainDocs):if trainCategory[i] == 1: #統計屬于侮辱類的條件概率所需的數據,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i])else: #統計屬于非侮辱類的條件概率所需的數據,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = p1Num/p1Denom p0Vect = p0Num/p0Denom return p0Vect,p1Vect,pAbusive #返回屬于侮辱類的條件概率數組,屬于非侮辱類的條件概率數組,文檔屬于侮辱類的概率if __name__ == '__main__':postingList, classVec = loadDataSet()myVocabList = createVocabList(postingList)print('myVocabList:\n', myVocabList)trainMat = []for postinDoc in postingList:trainMat.append(setOfWords2Vec(myVocabList, postinDoc))p0V, p1V, pAb = trainNB0(trainMat, classVec)print('p0V:\n', p0V)print('p1V:\n', p1V)print('classVec:\n', classVec)print('pAb:\n', pAb)?運行結果如下,p0V存放的是每個單詞屬于類別0,也就是非侮辱類詞匯的概率。比如p0V的倒數第6個概率,就是stupid這個單詞屬于非侮辱類的概率為0。同理,p1V的倒數第6個概率,就是stupid這個單詞屬于侮辱類的概率為0.15789474,也就是約等于15.79%的概率。我們知道stupid的中文意思是蠢貨,難聽點的叫法就是傻逼。顯而易見,這個單詞屬于侮辱類。pAb是所有侮辱類的樣本占所有樣本的概率,從classVec中可以看出,一用有3個侮辱類,3個非侮辱類。所以侮辱類的概率是0.5。因此p0V存放的就是P(him | 非侮辱類) = 0.0833,P(is | 非侮辱類) = 0.0417,一直到P(dog | 非侮辱類) = 0.0417,這些單詞的條件概率。同理,p1V存放的就是各個單詞屬于侮辱類的條件概率。pAb就是先驗概率
?
已經訓練好分類器,接下來,使用分類器進行分類。
# -*- coding: UTF-8 -*- import numpy as np from functools import reducedef 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 #返回實驗樣本切分的詞條和類別標簽向量def createVocabList(dataSet):vocabSet = set([]) #創建一個空的不重復列表for document in dataSet:vocabSet = vocabSet | set(document) #取并集return list(vocabSet)def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #創建一個其中所含元素都為0的向量for word in inputSet: #遍歷每個詞條if word in vocabList: #如果詞條存在于詞匯表中,則置1returnVec[vocabList.index(word)] = 1else:print("the word: %s is not in my Vocabulary!" % word)return returnVec #返回文檔向量def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #計算訓練的文檔數目numWords = len(trainMatrix[0])#計算每篇文檔的詞條數pAbusive = sum(trainCategory)/float(numTrainDocs) #文檔屬于侮辱類的概率p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #創建numpy.zeros數組,p0Denom = 0.0; p1Denom = 0.0 #分母初始化為0.0for i in range(numTrainDocs):if trainCategory[i] == 1: #統計屬于侮辱類的條件概率所需的數據,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i]) ## 該詞條的總的詞數目 這壓樣求得每個詞條出現的概率 P(w1),P(w2), P(w3)...else: #統計屬于非侮辱類的條件概率所需的數據,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = p1Num/p1Denom #相除 p0Vect = p0Num/p0Denom return p0Vect,p1Vect,pAbusive #返回屬于侮辱類的條件概率數組,屬于非侮辱類的條件概率數組,文檔屬于侮辱類的概率def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1 #對應元素相乘 這里需要好好理解一下 p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1)print('p0:',p0)print('p1:',p1)if p1 > p0:return 1else: return 0def testingNB():listOPosts,listClasses = loadDataSet() #創建實驗樣本myVocabList = createVocabList(listOPosts) #創建詞匯表trainMat=[]for postinDoc in listOPosts:trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #將實驗樣本向量化p0V,p1V,pAb = trainNB0(np.array(trainMat),np.array(listClasses)) #訓練樸素貝葉斯分類器testEntry = ['love', 'my', 'dalmation'] #測試樣本1thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化if classifyNB(thisDoc,p0V,p1V,pAb):print(testEntry,'屬于侮辱類') #執行分類并打印分類結果else:print(testEntry,'屬于非侮辱類') #執行分類并打印分類結果testEntry = ['stupid', 'garbage'] #測試樣本2thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化if classifyNB(thisDoc,p0V,p1V,pAb):print(testEntry,'屬于侮辱類') #執行分類并打印分類結果else:print(testEntry,'屬于非侮辱類') #執行分類并打印分類結果if __name__ == '__main__':testingNB()我們測試了兩個詞條,在使用分類器前,也需要對詞條向量化,然后使用classifyNB()函數,用樸素貝葉斯公式,計算詞條向量屬于侮辱類和非侮辱類的概率。運行結果如下:
你會發現,這樣寫的算法無法進行分類,p0和p1的計算結果都是0,顯然結果錯誤。這是為什么呢?下一篇文章繼續講解~
總結
以上是生活随笔為你收集整理的机器学习实战教程(四):朴素贝叶斯基础篇之言论过滤器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++之类型萃取技巧
- 下一篇: 浅析AVL树