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