機器學習實戰刻意練習 —— Task 02. 樸素貝葉斯

機器學習實戰刻意練習

第 1 周任務
  分類問題:K-鄰近算法
  分類問題:決策樹

第 2 周任務
  分類問題:樸素貝葉斯
  分類問題:邏輯迴歸

第 3 周任務
  分類問題:支持向量機

第 4 周任務
  分類問題:AdaBoost

第 5 周任務
  迴歸問題:線性迴歸、嶺迴歸、套索方法、逐步迴歸等
  迴歸問題:樹迴歸

第 6 周任務
  聚類問題:K均值聚類
  相關問題:Apriori

第 7 周任務
  相關問題:FP-Growth

第 8 周任務
  簡化數據:PCA主成分分析
  簡化數據:SVD奇異值分解
    



樸素貝葉斯



1.簡介

  樸素貝葉斯法是基於貝葉斯定理與特徵條件獨立假設的分類方法 。
  最爲廣泛的兩種分類模型是決策樹模型(Decision Tree Model)和樸素貝葉斯模型(Naive Bayesian Model,NBM)。和決策樹模型相比,樸素貝葉斯分類器(Naive Bayes Classifier 或 NBC)發源於古典數學理論,有着堅實的數學基礎,以及穩定的分類效率。同時,NBC模型所需估計的參數很少,對缺失數據不太敏感,算法也比較簡單。理論上,NBC模型與其他分類方法相比具有最小的誤差率。但是實際上並非總是如此,這是因爲NBC模型假設屬性之間相互獨立,這個假設在實際應用中往往是不成立的,這給NBC模型的正確分類帶來了一定影響。


 1.1.貝葉斯定理

  條件概率 (conditional probability) 是指在事件 B 發生的情況下,事件 A 發生的概率。通常記爲 P(A | B)。
在這裏插入圖片描述
  因此
在這裏插入圖片描述
  可得
在這裏插入圖片描述
  由此可以推出貝葉斯公式
在這裏插入圖片描述
  這也是條件概率的計算公式。
  此外,由全概率公式,可得條件概率的另一種寫法
在這裏插入圖片描述
  其中樣本空間由A和A’構成,由此求得事件B的概率。


 1.2.貝葉斯推斷

  貝葉斯公式中,P(A)稱爲"先驗概率"(Prior probability),即在B事件發生之前,對A事件概率的一個判斷。
  P(A|B)稱爲"後驗概率"(Posterior probability),即在B事件發生之後,對A事件概率的重新評估。
  P(B|A)/P(B)稱爲"可能性函數"(Likelyhood),這是一個調整因子,使得預估概率更接近真實概率。
  所以,條件概率可以理解成下面的式子:後驗概率=先驗概率 x 調整因子
  這就是貝葉斯推斷的含義。我們先預估一個"先驗概率",然後加入實驗結果,看這個實驗到底是增強還是削弱了"先驗概率",由此得到更接近事實的"後驗概率"。因爲在分類中,只需要找出可能性最大的那個選項,而不需要知道具體那個類別的概率是多少,所以爲了減少計算量,全概率公式在實際編程中可以不使用。

  而樸素貝葉斯推斷,是在貝葉斯推斷的基礎上,對條件概率分佈做了條件獨立性的假設。因此可得樸素貝葉斯分類器的表達式。因爲以自變量之間的獨立(條件特徵獨立)性和連續變量的正態性假設爲前提,就會導致算法精度在某種程度上受影響。
在這裏插入圖片描述


 1.3.模型概述

  樸素(指特徵條件獨立)貝葉斯方法,是指根據貝葉斯定理,對一個分類問題,給定樣本特徵x,樣本屬於類別y的概率是

在這裏插入圖片描述

  在這裏,x是一個特徵向量,將設x維度爲M。因爲樸素的假設,即特徵條件獨立,根據全概率公式展開,公式(1)可以表達爲
在這裏插入圖片描述
  這裏,只要分別估計出,特徵xi在每一類的條件概率就可以了。類別y的先驗概率可以通過訓練集算出,同樣通過訓練集上的統計,可以得出對應每一類上的,條件獨立的特徵對應的條件概率向量。


 1.4.舉個栗子~

  某個醫院早上來了六個門診的病人,他們的情況如下表所示:

症狀 職業 疾病
打噴嚏 護士 感冒
打噴嚏 農夫 過敏
頭痛 建築工人 腦震盪
頭痛 建築工人 感冒
打噴嚏 教師 感冒
頭痛 教師 腦震盪

   問:現在又來了第七個病人,是一個打噴嚏的建築工人。請問他患上感冒的概率有多大?

  根據貝葉斯定理:
  P()=P()P()P(感冒|打噴嚏*建築工人)=\frac{P(打噴嚏*建築工人|感冒)}{P(打噴嚏*建築工人)}

根據樸素貝葉斯條件獨立性的假設可知,"打噴嚏"和"建築工人"這兩個特徵是獨立的,因此,上面的等式就變成了
P()=P()P()P()P()P(感冒|打噴嚏*建築工人)=\frac{P(打噴嚏|感冒)*P(建築工人|感冒)*P(感冒)}{P(打噴嚏*建築工人)}

  通過計算得出
P()=0.660.330.50.50.33=0.66P(感冒|打噴嚏*建築工人)=\frac{0.66*0.33*0.5}{0.5*0.33}=0.66

  因此,這個打噴嚏的建築工人,有66%的概率是得了感冒。同理,可以計算這個病人患上過敏或腦震盪的概率。比較這幾個概率,就可以知道他最可能得什麼病。

  這就是貝葉斯分類器的基本方法:在統計資料的基礎上,依據某些特徵,計算各個類別的概率,從而實現分類。

  同樣,在編程的時候,如果不需要求出所屬類別的具體概率,P(打噴嚏) = 0.5和P(建築工人) = 0.33的概率是可以不用求的。





2. 動手實戰

2.1. 項目案例1:屏蔽社區留言板的侮辱性言論

項目概述
  爲了不影響社區的發展,我們要屏蔽侮辱性的言論,所以要構建一個快速過濾器,如果某條留言使用了負面或者侮辱性的語言,那麼就將該留言標誌爲內容不當。過濾這類內容是一個很常見的需求。對此問題建立兩個類型:侮辱類和非侮辱類,使用1和0分別表示。

開發流程
  我們把文本看成單詞向量或者詞條向量,也就是說將句子轉換爲向量。考慮出現所有文檔中的單詞,再決定將哪些單詞納入詞彙表或者說所要的詞彙集合,然後必須要將每一篇文檔轉換爲詞彙表上的向量。簡單起見,我們先假設已經將本文切分完畢,存放到列表中,並對詞彙向量進行分類標註。編寫代碼如下:

"""
函數說明:創建實驗樣本

Parameters:
    無
Returns:
    postingList - 實驗樣本切分的詞條
    classVec - 類別標籤向量
"""
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

if __name__ == '__main__':
    postingLIst, classVec = loadDataSet()
    for each in postingLIst:
        print(each)
    print(classVec)

  運行結果:

['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']
[0, 1, 0, 1, 0, 1]

  從運行結果可以看出,我們已經將postingList是存放詞條列表中,classVec是存放每個詞條的所屬類別,1代表侮辱類 ,0代表非侮辱類。


   繼續編寫代碼,前面我們已經說過我們要先創建一個詞彙表,並將切分好的詞條轉換爲詞條向量。

"""
函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每個元素爲1或0

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的詞條列表
Returns:
    returnVec - 文檔向量,詞集模型
"""
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    #創建一個其中所含元素都爲0的向量
    for word in inputSet:                                                #遍歷每個詞條
        if word in vocabList:                                            #如果詞條存在於詞彙表中,則置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文檔向量

"""
函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表

Parameters:
    dataSet - 整理的樣本數據集
Returns:
    vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
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:
 [['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']]
myVocabList:
 ['maybe', 'him', 'steak', 'has', 'buying', 'stop', 'problems', 'how', 'quit', 'food', 'licks', 'flea', 'dog', 'my', 'please', 'take', 'so', 'ate', 'dalmation', 'to', 'garbage', 'mr', 'cute', 'I', 'help', 'worthless', 'is', 'posting', 'park', 'not', 'stupid', 'love']
trainMat:
 [[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0], [0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]]

  從運行結果可以看出,postingList是原始的詞條列表,myVocabList是詞彙表。myVocabList是所有單詞出現的集合,沒有重複的元素。詞彙表是用來幹什麼的?沒錯,它是用來將詞條向量化的,一個單詞在詞彙表中出現過一次,那麼就在相應位置記作1,如果沒有出現就在相應位置記作0。trainMat是所有的詞條向量組成的列表。它裏面存放的是根據myVocabList向量化的詞條向量。

  我們已經得到了詞條向量。接下來,我們就可以通過詞條向量訓練樸素貝葉斯分類器。

"""
函數說明:樸素貝葉斯分類器訓練函數

Parameters:
    trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
    trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱類的條件概率數組
    p1Vect - 非侮辱類的條件概率數組
    pAbusive - 文檔屬於侮辱類的概率
"""
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數組,詞條出現數初始化爲0
    p0Denom = 0.0; p1Denom = 0.0                            #分母初始化爲0
    for 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)

  得到結果:

myVocabList:
 ['ate', 'is', 'mr', 'garbage', 'how', 'steak', 'take', 'help', 'maybe', 'dog', 'cute', 'please', 'flea', 'I', 'buying', 'quit', 'so', 'food', 'dalmation', 'park', 'worthless', 'stop', 'posting', 'love', 'my', 'to', 'problems', 'him', 'not', 'stupid', 'has', 'licks']
p0V:
 [0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
 0.         0.04166667 0.         0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.         0.         0.04166667 0.
 0.04166667 0.         0.         0.04166667 0.         0.04166667
 0.125      0.04166667 0.04166667 0.08333333 0.         0.
 0.04166667 0.04166667]
p1V:
 [0.         0.         0.         0.05263158 0.         0.
 0.05263158 0.         0.05263158 0.10526316 0.         0.
 0.         0.         0.05263158 0.05263158 0.         0.05263158
 0.         0.05263158 0.10526316 0.05263158 0.05263158 0.
 0.         0.05263158 0.         0.05263158 0.05263158 0.15789474
 0.         0.        ]
classVec:
 [0, 1, 0, 1, 0, 1]
pAb:
 0.5

  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就是先驗概率。

  已經訓練好分類器,接下來,使用分類器進行分類。


"""
函數說明:樸素貝葉斯分類器分類函數

Parameters:
	vec2Classify - 待分類的詞條數組
	p0Vec - 侮辱類的條件概率數組
	p1Vec -非侮辱類的條件概率數組
	pClass1 - 文檔屬於侮辱類的概率
Returns:
	0 - 屬於非侮辱類
	1 - 屬於侮辱類
"""
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 1
	else: 
		return 0

"""
函數說明:測試樸素貝葉斯分類器

Parameters:
	無
Returns:
	無
"""
def 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']									#測試樣本1
	thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				#測試樣本向量化
	if classifyNB(thisDoc,p0V,p1V,pAb):
		print(testEntry,'屬於侮辱類')										#執行分類並打印分類結果
	else:
		print(testEntry,'屬於非侮辱類')										#執行分類並打印分類結果
	testEntry = ['stupid', 'garbage']										#測試樣本2

	thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				#測試樣本向量化
	if classifyNB(thisDoc,p0V,p1V,pAb):
		print(testEntry,'屬於侮辱類')										#執行分類並打印分類結果
	else:
		print(testEntry,'屬於非侮辱類')										#執行分類並打印分類結果

if __name__ == '__main__':
	testingNB()

  運行結果:

p0: 0.0
p1: 0.0
['love', 'my', 'dalmation'] 屬於非侮辱類
p0: 0.0
p1: 0.0
['stupid', 'garbage'] 屬於非侮辱類

  你會發現,這樣寫的算法無法進行分類,p0和p1的計算結果都是0,這裏顯然存在問題。



2.2. 項目案例2:過濾垃圾郵件

  使用樸素貝葉斯解決一些現實生活中的問題時,需要先從文本內容得到字符串列表,然後生成詞向量。下面這個例子中,我們將瞭解樸素貝葉斯的一個最著名的應用:電子郵件垃圾過濾。首先看一下使用樸素貝葉斯對電子郵件進行分類的步驟:

  • 收集數據:提供文本文件。
  • 準備數據:將文本文件解析成詞條向量。
  • 分析數據:檢查詞條確保解析的正確性。
  • 訓練算法:使用我們之前建立的trainNB0()函數。
  • 測試算法:使用classifyNB(),並構建一個新的測試函數來計算文檔集的錯誤率。
  • 使用算法:構建一個完整的程序對一組文檔進行分類,將錯分的文檔輸出到屏幕上。

  數據下載點擊這裏,有兩個文件夾ham和spam,spam文件下的txt文件爲垃圾郵件。

import numpy as np
import random
import re

"""
函數說明:將切分的實驗樣本詞條整理成不重複的詞條列表,也就是詞彙表

Parameters:
    dataSet - 整理的樣本數據集
Returns:
    vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
def createVocabList(dataSet):
    vocabSet = set([])                      #創建一個空的不重複列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取並集
    return list(vocabSet)

"""
函數說明:根據vocabList詞彙表,將inputSet向量化,向量的每個元素爲1或0

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的詞條列表
Returns:
    returnVec - 文檔向量,詞集模型
"""
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)                                    #創建一個其中所含元素都爲0的向量
    for word in inputSet:                                                #遍歷每個詞條
        if word in vocabList:                                            #如果詞條存在於詞彙表中,則置1
            returnVec[vocabList.index(word)] = 1
        else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec                                                    #返回文檔向量


"""
函數說明:根據vocabList詞彙表,構建詞袋模型

Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的詞條列表
Returns:
    returnVec - 文檔向量,詞袋模型
"""
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)                                        #創建一個其中所含元素都爲0的向量
    for word in inputSet:                                                #遍歷每個詞條
        if word in vocabList:                                            #如果詞條存在於詞彙表中,則計數加一
            returnVec[vocabList.index(word)] += 1
    return returnVec                                                    #返回詞袋模型

"""
函數說明:樸素貝葉斯分類器訓練函數

Parameters:
    trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
    trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱類的條件概率數組
    p1Vect - 非侮辱類的條件概率數組
    pAbusive - 文檔屬於侮辱類的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #計算訓練的文檔數目
    numWords = len(trainMatrix[0])                            #計算每篇文檔的詞條數
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文檔屬於侮辱類的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #創建numpy.ones數組,詞條出現數初始化爲1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化爲2,拉普拉斯平滑
    for 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 = np.log(p1Num/p1Denom)                            #取對數,防止下溢出         
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回屬於侮辱類的條件概率數組,屬於非侮辱類的條件概率數組,文檔屬於侮辱類的概率

"""
函數說明:樸素貝葉斯分類器分類函數

Parameters:
    vec2Classify - 待分類的詞條數組
    p0Vec - 侮辱類的條件概率數組
    p1Vec -非侮辱類的條件概率數組
    pClass1 - 文檔屬於侮辱類的概率
Returns:
    0 - 屬於非侮辱類
    1 - 屬於侮辱類
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)        #對應元素相乘。logA * B = logA + logB,所以這裏加上log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

"""
函數說明:樸素貝葉斯分類器訓練函數

Parameters:
    trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
    trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
    p0Vect - 侮辱類的條件概率數組
    p1Vect - 非侮辱類的條件概率數組
    pAbusive - 文檔屬於侮辱類的概率
"""
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #計算訓練的文檔數目
    numWords = len(trainMatrix[0])                            #計算每篇文檔的詞條數
    pAbusive = sum(trainCategory)/float(numTrainDocs)        #文檔屬於侮辱類的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)    #創建numpy.ones數組,詞條出現數初始化爲1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                            #分母初始化爲2,拉普拉斯平滑
    for 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 = np.log(p1Num/p1Denom)                            #取對數,防止下溢出         
    p0Vect = np.log(p0Num/p0Denom)         
    return p0Vect,p1Vect,pAbusive                            #返回屬於侮辱類的條件概率數組,屬於非侮辱類的條件概率數組,文檔屬於侮辱類的概率


"""
函數說明:接收一個大字符串並將其解析爲字符串列表

Parameters:
    無
Returns:
    無
"""
def textParse(bigString):                                                   #將字符串轉換爲字符列表
    listOfTokens = re.split(r'\W*', bigString)                              #將特殊符號作爲切分標誌進行字符串切分,即非字母、非數字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            #除了單個字母,例如大寫的I,其它單詞變成小寫

"""
函數說明:測試樸素貝葉斯分類器

Parameters:
    無
Returns:
    無
"""
def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1, 26):                                                  #遍歷25個txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #讀取每個垃圾郵件,並字符串轉換成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)                                                 #標記垃圾郵件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #讀取每個非垃圾郵件,並字符串轉換成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(0)                                                 #標記非垃圾郵件,1表示垃圾文件   
    vocabList = createVocabList(docList)                                    #創建詞彙表,不重複
    trainingSet = list(range(50)); testSet = []                             #創建存儲訓練集的索引值的列表和測試集的索引值的列表                       
    for i in range(10):                                                     #從50個郵件中,隨機挑選出40個作爲訓練集,10個做測試集
        randIndex = int(random.uniform(0, len(trainingSet)))                #隨機選取索索引值
        testSet.append(trainingSet[randIndex])                              #添加測試集的索引值
        del(trainingSet[randIndex])                                         #在訓練集列表中刪除添加到測試集的索引值
    trainMat = []; trainClasses = []                                        #創建訓練集矩陣和訓練集類別標籤系向量             
    for docIndex in trainingSet:                                            #遍歷訓練集
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))       #將生成的詞集模型添加到訓練矩陣中
        trainClasses.append(classList[docIndex])                            #將類別添加到訓練集類別標籤系向量中
    p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))  #訓練樸素貝葉斯模型
    errorCount = 0                                                          #錯誤分類計數
    for docIndex in testSet:                                                #遍歷測試集
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])           #測試集的詞集模型
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:    #如果分類錯誤
            errorCount += 1                                                 #錯誤計數加1
            print("分類錯誤的測試集:",docList[docIndex])
    print('錯誤率:%.2f%%' % (float(errorCount) / len(testSet) * 100))


if __name__ == '__main__':
    spamTest()

  函數spamTest()會輸出在10封隨機選擇的電子郵件上的分類錯誤概率。既然這些電子郵件是隨機選擇的,所以每次的輸出結果可能有些差別。如果發現錯誤的話,函數會輸出錯誤的文檔的此表,這樣就可以瞭解到底是哪篇文檔發生了錯誤。如果想要更好地估計錯誤率,那麼就應該將上述過程重複多次,比如說10次,然後求平均值。相比之下,將垃圾郵件誤判爲正常郵件要比將正常郵件歸爲垃圾郵件好。





參考資料

  • https://www.cnblogs.com/geo-will/p/10468401.html
  • https://blog.csdn.net/tanhongguang1/article/details/45016421
  • https://blog.csdn.net/LSGO_MYP/article/details/103111698
  • https://blog.csdn.net/c406495762/article/details/77341116
  • https://blog.csdn.net/c406495762/article/details/77500679
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章