一個Python例子帶你理解樸素貝葉斯算法原理
前言
前面我們在我們學習過KNN和決策樹後,我們提出了一個問題,就是我們希望算法能夠給我們一個分類概率,並不希望直接告訴我們分類情況。樸素貝葉斯將爲我們解決這個問題。
源代碼已上傳GitHub,有需要的夥伴們自行去取,地址:樸素貝葉斯源代碼
樸素貝葉斯原理解析
一、條件概率
概率我們都瞭解過吧,就是在某一件事情發生的可能性。
那條件概率呢,就是在概率的基礎上我給它一個前置條件,舉個例子:
現在有二個黑箱子,裏面有一些形狀大小相同的小球,分佈情況如下圖所示:
那麼,我此時問,從箱子A中隨機選一個小球,是紅顏色的概率是多少?
很簡單,答案是2/3.
也就是P(紅|箱子A)=2/3 這裏的箱子A 就是前置條件。
同理可得,P(紅|箱子B)=1/4
如果我們將兩個箱子的球合併在一起,此時不存在什麼條件了,那麼P(紅) 就是一個普通的概率。
二、貝葉斯公式
貝葉斯公式是概率論中一個非常重要而且有用的公式,先看一下公式吧:
*P(A|B)=P(B|A)P(A)/P(B) (1)
如何去理解呢?咱們先看下面這個:
*P(A∩B) = P(A)P(B|A) (2)
P(A∩B) 表示事件A和事件B同時發生的概率,就等於事件A發生的概率乘以在事件A已發生的條件下,事件B發生的概率。
還是拿盒子小球作爲例子,假設事件A爲從箱子A中取球,事件B爲取到紅顏色的球。
那麼,P(A∩B) 表示爲從箱子A中取到紅顏色球的概率
再看等號右邊,P(A) 表示從箱子A中取球的概率,這裏因爲只有兩個箱子,且形狀大小無異,所以概率值爲1/2;P(B|A) 表示從箱子A中取到紅色球的概率,值爲2/3.
這下大家能夠明白上面式子(2)爲什麼相等了吧。
如果此時我們將A和B變量互換,此時有:
*P(B∩A) = P(B)P(A|B)
顯然,P(B∩A)=P(A∩B) 均表示事件A和事件B同時發生的概率
所以有:
**P(B)P(A|B) = P(A)P(B|A) (3)
公式(3)就被稱爲貝葉斯公式,它揭示了條件概率中條件與結果的關係,簡單的來說,這個公式讓我們得以去根據條件去判斷最終結果,也就是我們說的分類問題。
三、如何去應用貝葉斯公式?
到第二部分的時候,我會給大家使用源代碼進行分析,這裏主要是通過文字講述一下如何應用。
我們要解決的問題是垃圾郵箱的分類問題,通過郵件內容,我們去判斷這個郵箱是否是垃圾郵箱。
下圖爲正常郵箱內容:
這個爲垃圾郵箱內容:
那麼我們如何去分辨該郵件是否是垃圾郵件呢,聽聽這個思路
垃圾郵件中總是有一些詞彙出現的頻率比較高,如果我們針對郵件中的單詞建立一個詞向量(顯示郵件中出現單詞的數量),根據訓練數據計算不同類型郵箱單詞出現的概率,就能夠利用這些數據,使用貝葉斯公式去預測郵件的歸屬問題。
上面不明白沒關係,咱們將貝葉斯公式稍微改裝一下:
圖中Ci 表示郵件分類 i=0時,表示郵件是非垃圾郵件 i=1 表示郵件是垃圾郵件
d表示數據,也就是我們將要構建的每個郵件的詞向量
詞向量,就是用來記錄單詞出現次數的一個列表
是一個條件概率,表示在郵件類型爲i的條件下,數據d的概率(這裏理解爲各個單詞出現的概率)
同樣也是一個條件概率,表示在數據d的情況下,郵件的屬於i類的概率
表示郵件屬於i的概率,就是一個普通的概率問題
好了,基本上我們就是通過這樣一個公式來寫我們代碼,不過,寫代碼前,我們先說一下流程:
- 處理郵件數據,去除掉標點符號,將每個郵箱的單詞放入列表中
- 統計郵件中所出現過的單詞,存儲到一個列表中,長度L就是我們所要創建詞向量的大小
- 對每個郵件創建一個長度爲L的列表(稱之爲詞向量),初始值爲1,遍歷每個郵件的單詞列表,統計單詞出現次數.
- 分別對不同類型的郵件的詞向量進行相加,再處理他們各自的單詞總數,此時結果爲
- 再將詞向量的各個數相乘,最後再乘以數據集中郵件各個類別的概率
貝葉斯算法源代碼解析
一、 加載處理數據
def textParse(text):
text1 = re.split(r'\W+',text)
text2 = [textfilter.lower() for textfilter in text1 if len(textfilter)>2]#返回字符長度大於2的字符
# to do -這裏可以添加自定義過濾字符串的規則
return text2
def loadDataSet():
fileurl1 = 'datasets/email/ham'
fileurl2 = 'datasets/email/spam'#垃圾郵件
postingList=[]
classVec = []
fulltext=[]
for i in range(1,26):
f1 = open(fileurl1+'/{}.txt'.format(i),errors='ignore')
text1 = textParse(f1.read())
postingList.append(text1)
fulltext.extend(text1)
classVec.append(1)
f2 = open(fileurl2+'/{}.txt'.format(i),'r')
text2 = textParse(f2.read())
postingList.append(text2)
fulltext.extend(text2)
classVec.append(0)
return postingList,classVec,fulltext
這裏一共有兩個函數,textParse()和loadDataSet()
textParse()方法用來去篩選掉郵件中的標點符號和長度小於2的單詞
loadDataSet() 加載不同文件夾下的txt文件,裏面存放着郵件的內容。文件ham表示文件下面的郵件爲非垃圾郵件;文件spam表示文件下面的郵件爲垃圾郵件。
垃圾郵件和非垃圾郵件各有25個,一共有50個郵件
二、創建詞向量
def setOfWords2Vec(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]+=1
else:
print("the word is not in my vocabulary:",word)
return returnVec
vocabList 就是步驟中咱們說到的所有郵件出現的單詞列表
inputSet 輸入數據,也就是經過咱們處理過後的郵件單詞列表
這裏主要就一個循環判斷語句,用來統計inputSet 輸入的數據中,單詞出現的次數,以此來構建詞向量
三、訓練函數
def trainNB0(trainMatrix,trainCategory):
#print("trainMatrix",trainMatrix)
numTrainDocs = len(trainMatrix)#共有幾個文本
numWords = len(trainMatrix[0])#共有多少單詞
pAbusive = sum(trainCategory)/float(numTrainDocs)#文本中有多少是侮辱性文檔
p0Num = np.ones(numWords)#創建全爲1的數組
p1Num = np.ones(numWords)#
p0Denom = 1.0*numWords#
p1Denom = 1.0*numWords
for i in range(numTrainDocs):
print("trainCategory[i]",trainCategory[i])
if trainCategory[i]==1:#文檔的分類
p1Num+=trainMatrix[i]#計算每個單詞出現的次數
p1Denom+=sum(trainMatrix[i])#統計文檔類型屬於1的所有文檔單詞個數
else:
p0Num+=trainMatrix[i]
p0Denom+=sum(trainMatrix[i])
p1Vect = np.log(p1Num/p1Denom)
p0Vect = np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
輸入數據:
trainMatrix :測試數據集的詞向量列表
trainCategory:測試數據集的郵件分類情況,垃圾郵件爲1 非垃圾郵件爲0
處理過程:
這裏通過一個for循環,根據測試數據的郵件分類情況,對各個類別的詞向量進行相加,並統計所有的單詞數量。
然後將相加後的向量除以單詞總數,計算出單詞出現的頻率
最後使用log函數進行轉化
返回數據:
p0Vect:類型爲0的經log函數轉化後的詞向量頻率
p1Vect:類型爲1的經log函數轉化後的詞向量頻率
pAbusive:數據集中垃圾郵件的概率
四、分類函數
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p0 = np.sum(vec2Classify*p0Vec)+np.log(pClass1)
p1 = np.sum(vec2Classify*p1Vec)+np.log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
輸入數據:
vec2Classify 輸入數據的詞向量
p0Vec、p1Vec、pClass1 均爲trainNB0()函數的返回值
處理過程:
先拓展一個知識點:
這個是咱們前面提到的一個條件概率,d是詞向量。
我們直到,在代碼中,詞向量是使用列表來表示,但是這裏要得出一個數,怎麼計算呢
概率論中有一個這樣的公式:
當然前提假設,詞向量中的各個值之間是相互獨立的。
如果這裏不明白的,可以去看一下概率論基礎補一下
由於前面咱們使用了log函數,所以有
log(axbxcxd) = log(a)+log(b)+log©+log(d)
所以在代碼中使用求和進行計算
最後對p0和p1進行概率比較,值較大的就是輸入數據的所屬類
五、測試函數
def testingNB():
listOPosts,listClasses,fulltext = loadDataSet()
# myVocabList = createVocabList(listOPosts)
myVocabList = list(set(fulltext))
#print("單詞列表:",myVocabList)
#隨機取10個郵件作爲測試使用
#print("listOPosts.length:",len(listOPosts))
trainsetnum = list(range(50))
testset = []
for i in range(10):
index = int(random.uniform(0,len(trainsetnum)))
testset.append(trainsetnum[index])
del(trainsetnum[index])
print("testset:",testset)
#重新組裝訓練數據和測試數據
traindata = []
trainclass=[]
for j in trainsetnum:
traindata.append(setOfWords2Vec(myVocabList,listOPosts[j]))#這裏直接轉化爲詞向量
trainclass.append(listClasses[j])
testdata=[]
testclass=[]
for k in testset:
testdata.append(setOfWords2Vec(myVocabList,listOPosts[k]))
testclass.append(listClasses[k])
p0V,p1V,pSpam = trainNB0(traindata,trainclass)
errorcount=0
for i in range(len(testdata)):
wordVector = testdata[i]
print("輸出最終分類結果:",classifyNB(wordVector,p0V,p1V,pSpam))
print("輸出原本結果-:",testclass[i])
if testclass[i]!=classifyNB(wordVector,p0V,p1V,pSpam):
errorcount+=1
print(" the error rate is:{}".format(errorcount))
這個函數只是進行測試模型的精準度,我測試了大概10次,一共出現錯誤沒超過5個,也就是相當於正確率在95%以上,還是挺準的。
這裏將訓練數據爲40個郵件,測試數據爲10個郵件。
總結
這裏咱們僅僅是貝葉斯公式的一個小小應用,我們實驗的前提是這些單詞之間沒有什麼聯繫,都是獨立的個體。但是在實際中,有些單詞總是會出現在某些單詞後面或者前面,比如 am 總是出現在I後面,這些信息咱們沒有充分利用到。
代碼中,我們使用對數函數來解決下溢出問題(很多非常小的數相乘會失準),使用了將所有詞出現的次數初始化爲1,解決了一個概率爲0總體乘積爲0的情況。
總的來說,樸素貝葉斯公式還是一個比較優良的分類器,後面等我們學到更多分類算法後,再於其進行對比。