聲明:本篇博文是學習《機器學習實戰》一書的方式路程,系原創,若轉載請標明來源。
1 貝葉斯定理的引入
概率論中的經典條件概率公式:

公式的理解為,P(X ,Y)= P(Y,X)<=> P(X | Y)P(Y)= P(Y | X)P (X),即 X 和 Y 同時發生的概率與 Y 和 X 同時發生的概率一樣。
2 朴素貝葉斯定理
朴素貝葉斯的經典應用是對垃圾郵件的過濾,是對文本格式的數據進行處理,因此這里以此為背景講解朴素貝葉斯定理。設D 是訓練樣本和相關聯的類標號的集合,其中訓練樣本的屬性集為 X { X1,X2, ... , Xn }, 共有n 個屬性;類標號為 C{ C1,C2, ... ,Cm }, 有m 中類別。朴素貝葉斯定理:

其中,P(Ci | X)為后驗概率,P(Ci)為先驗概率,P(X | Ci)為條件概率。朴素貝葉斯的兩個假設:1、屬性之間相互獨立。2、每個屬性同等重要。通過假設1 知,條件概率P(X | Ci)可以簡化為:

3 朴素貝葉斯算法
朴素貝葉斯算法的核心思想:選擇具有最高后驗概率作為確定類別的指標。下面是以過濾有侮辱性的評論為例,介紹朴素貝葉斯利用Python 語言實現的過程,其本質是利用詞和類別的聯合概率來預測給定文檔屬於某個類別。
4 使用Python對文本分類
4.1 建立文本數據
文本數據用一個個對象組成,一個對象是由若干單詞組成,每個對象對應一個確定的類別。
代碼如下:
1 # 文本數據集 2 def loadDataList(): 3 postingList = [ 4 ['my','dog','has','flea','problems','help','please'], 5 ['maybe','not','take','him','to','dog','park','stupid'], 6 ['my','dalmation','is','so','cute','I','love','him'], 7 ['stop','posting','stupid','worthless','garbage'], 8 ['mr','licks','ate','my','steak','how','to','stop','him'], 9 ['quit','buying','worthless','dog','food','stupid']] 10 classVec = [0,1,0,1,0,1] 11 return postingList ,classVec
4.2 對文本數據的處理
從文本數據中提取出訓練樣本的屬性集,這里是屬性集是由單詞組成的詞匯集。
代碼如下:
1 # 提取訓練集的所有詞 2 def createVocabList(dataSet): 3 vocabSet = set([]) 4 for document in dataSet : 5 vocabSet = vocabSet | set(document) # 兩個集合的並集 6 return list(vocabSet)
這里利用集合的性質對數據集提取不同的單詞,函數 createVocabList() 返回值是列表類型。
4.3 對詞匯集轉化成數值類型
因為單詞的字符串類型無法參與到數值的計算,因此把一個對象的數據由詞匯集中的哪些單詞組成表示成:0 該對象沒有這個詞,1 該對象有這個詞。
代碼如下:
1 # 根據類別對詞進行划分數值型的類別 2 def setOfWords2Vec(vocabList, inputSet): 3 returnVec = [0]*len(vocabList) 4 for word in inputSet: 5 if word in vocabList: 6 returnVec[vocabList.index(word)] = 1 7 else : 8 print "the word : %s is not in my Vocabulary!" % word 9 return returnVec
參數 vocabList 是詞匯集,inputSet 是對象的數據,而返回值是由詞匯集的轉換成 0 和 1 組成的對象單詞在詞匯集的標記。
4.4 朴素貝葉斯分類器的訓練函數
這里說明一下,訓練樣本是postingList 列表數據,屬性集是詞匯集,類標號是classVec 列表數據。在編寫代碼時考慮到對象的單詞在詞匯集中占有率比較低,會造成詞匯集轉化時有大量的 0 組成,同時又會造成條件概率大量為 0 ;又有計算真實概率值普遍偏小,容易造成下溢出。因此,代碼對計算條件概率時進行轉換,但不影響條件概率的大小排序,也就不會影響朴素貝葉斯的使用。
代碼如下:
1 ''' 2 求貝葉斯公式中的先驗概率 pAbusive ,條件概率 p0Vect、p1Vect;函數中所求的概率值 3 是變形值,不影響貝葉斯的核心思想:選擇具有最高概率的決策 4 ''' 5 def trainNB0(trainMatrix, trainCategory): 6 numTrainDocs = len(trainMatrix) # 樣本中對象的個數 7 numWords = len(trainMatrix[0]) # 樣本中所有詞的集合個數 8 pAbusive = sum(trainCategory) / float(numTrainDocs) # 對類別只有兩種的先驗概率計算 9 # 對所有詞在不同的類別下出現次數的初始化為1,為了防止計算條件概率出現為0 10 p0Num = ones(numWords) 11 p1Num = ones(numWords) 12 # 對不同類別出現次數的初始化為2,詞的出現數初始數為1的情況下,增加分母值避免概率值大於1 13 p0Denom = 2.0 14 p1Denom = 2.0 15 for i in range(numTrainDocs): # 遍歷所有對象 16 if trainCategory[i] == 1: # 類別類型的判斷 17 p1Num += trainMatrix[i] # 對所有詞在不同的類別下出現次數的計算 18 p1Denom += sum(trainMatrix[i]) # 對不同類別出現次數的計算 19 else: 20 p0Num += trainMatrix[i] # 對所有詞在不同的類別下出現次數的計算 21 p0Denom += sum(trainMatrix[i]) # 對不同類別出現次數的計算 22 p1Vect = log ( p1Num / p1Denom) # 條件概率,用對數的形式計算是為避免概率值太小造成下溢出 23 p0Vect = log (p0Num / p0Denom) # 條件概率,用對數的形式計算是為避免概率值太小造成下溢出 24 return p0Vect, p1Vect, pAbusive
4.5 朴素貝葉斯的分類函數
根據先驗概率和條件概率對不同類別的后驗概率進行計算,並選取后驗概率最大的類別作為朴素貝葉斯預測結果值。
代碼如下:
1 # 計算后驗概率,並選擇最高概率作為預測類別 2 def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): 3 p1 = sum(vec2Classify * p1Vec ) + log(pClass1) # 對未知對象的單詞的每一項的條件概率相加(對數相加為條件概率的相乘) 4 p0 = sum(vec2Classify * p0Vec ) + log(1.0-pClass1 ) # 后面加上的一項是先驗概率 5 if p1 > p0: 6 return 1 7 else : 8 return 0
4.6 測試樣本的預測
通過朴素貝葉斯算法給出兩個未知類別的對象預測其類別。
代碼如下:
1 # 對侮辱性語言的測試 2 def testingNB(): 3 listOposts, listClasses = loadDataList() # 訓練樣本的數據,listOposts 為樣本,listClasses 為樣本的類別 4 myVocabList = createVocabList(listOposts ) # 樣本的詞匯集 5 trainMat = [] # 對樣本的所有對象相關的單詞轉化為數值 6 for postinDoc in listOposts : 7 trainMat.append(setOfWords2Vec(myVocabList ,postinDoc ) ) 8 p0V, p1V, pAb = trainNB0(array(trainMat),array(listClasses)) # 樣本的先驗概率和條件概率 9 10 testEntry = ['love','my','dalmation','love'] # 未知類別的對象 11 thisDoc = array(setOfWords2Vec(myVocabList ,testEntry ) ) # 對未知對象的單詞轉化為數值 12 print testEntry ,'classified as : ',classifyNB(thisDoc, p0V,p1V,pAb) # 對未知對象的預測其類別 13 14 testEntry = ['stupid','garbage'] # 未知類別的對象 15 thisDoc = array(setOfWords2Vec(myVocabList ,testEntry )) # 對未知對象的單詞轉化為數值 16 print testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb) # 對未知對象的預測其類別
其運行結果圖:

對象 ['love','my','dalmation','love'] 由直觀可知,其類別是非侮辱性詞匯,與預測結果(0 代表正常語言)相同;對象 ['stupid','garbage'] 類別是侮辱性詞匯,與預測結果(1 代表侮辱性語言)相同,說明朴素貝葉斯算法對預測類別有效。
5 例子:對垃圾郵件的識別
這里給出朴素貝葉斯算法最經典的應用實例,對垃圾郵件的過濾識別。由於郵件是以文件的形式保存,因此我們要對郵件的內容進行提取並處理成符合算法可用的類型。
5.1 郵件文件解析
利用正則語言對郵件的內容進行單詞的划分。
代碼如下:
1 # 郵件文件解析 2 def textParse(bigString): 3 import re 4 listOfTokens = re.split(r'\w*', bigString) # 利用正則語言對郵件文本進行解析 5 return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 限定單詞的字母大於2
第5 行代碼解釋: lower() 方法轉換字符串中所有大寫字符為小寫
5.2 垃圾郵件測試函數
代碼如下
1 # 完整的垃圾郵件測試函數 2 def spamTest(): 3 docList=[];classList = []; fullText = [] 4 for i in range(1,26): 5 wordList = textParse(open('email/spam/%d.txt' %i ).read()) 6 docList.append(wordList) # 把解析后的郵件作為訓練樣本 7 fullText.extend(wordList) 8 classList.append(1) # 郵件所對應的類別 9 wordList = textParse(open('email/ham/%d.txt' % i).read()) 10 docList.append(wordList) # 把解析后的郵件作為訓練樣本 11 fullText.extend(wordList ) 12 classList .append(0) # 郵件所對應的類別 13 vocabList = createVocabList(docList) # 樣本生成的詞匯集 14 # 隨機產生十個測試樣本和四十個訓練樣本 15 trainingSet = range(50);testSet = [] 16 for i in range(10): 17 randIndex = int (random.uniform(0,len(trainingSet ))) 18 testSet.append(trainingSet [randIndex ]) 19 del[trainingSet[randIndex]] 20 # 對訓練樣本進行詞的轉化成數值類型 21 trainMat = [] 22 trainClasses = [] 23 for docIndex in trainingSet : 24 trainMat.append(setOfWords2Vec(vocabList, docList [docIndex ]) ) 25 trainClasses.append(classList[docIndex ]) 26 p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 訓練樣本的先驗概率及條件概率 27 errorCount = 0 # 測試樣本的出錯數初始化 28 for docIndex in testSet: 29 wordVector = setOfWords2Vec(vocabList ,docList[docIndex ]) # 測試對象的詞的數值轉化 30 if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex ]: # 預測的類別與真實類別的對比 31 errorCount += 1 32 print 'the error rate is : ', float (errorCount )/ len(testSet) # 測試樣本的出錯率
第17 行代碼解釋:
uniform() 函數是在random模塊里,將隨機生成下一個實數,它在 [x, y) 范圍內。
x -- 隨機數的最小值,包含該值。
y -- 隨機數的最大值,不包含該值。
返回值是一個浮點數
運行結果圖

結果顯示測試集的出錯比例是10%,由於訓練集是隨機組合的,因此每次運行的結果會有所不同。在《機器學習實戰》一書中給出這個算法的錯誤率在6%左右,說明朴素貝葉斯算法在嚴苛的條件下也有較好的效果。嚴苛條件是指我們對屬性都是獨立的,這在現實中很難找到符合這樣的條件。對垃圾郵件的過濾也是不例外的,如bacon(培根) 出現在unhealthy (不健康的)后面與出現在delicious(美味的)后面的概率是不同的,bacon(培根)常常與delicious (美味的)搭配。
附 完整代碼
# -*- coding:utf-8 -*- from numpy import * # 文本數據集 def loadDataList(): 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] 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) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] = 1 else : print "the word : %s is not in my Vocabulary!" % word return returnVec # 文檔詞袋模型,可以對重復的單詞計數 def bagOfWords2Vec(vocabList, inputSet): returnVec = [0]*len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 return returnVec ''' 求貝葉斯公式中的先驗概率 pAbusive ,條件概率 p0Vect、p1Vect;函數中所求的概率值 是變形值,不影響貝葉斯的核心思想:選擇具有最高概率的決策 ''' def trainNB0(trainMatrix, trainCategory): numTrainDocs = len(trainMatrix) # 樣本中對象的個數 numWords = len(trainMatrix[0]) # 樣本中所有詞的集合個數 pAbusive = sum(trainCategory) / float(numTrainDocs) # 對類別只有兩種的先驗概率計算 # 對所有詞在不同的類別下出現次數的初始化為1,為了防止計算條件概率出現為0 p0Num = ones(numWords) p1Num = ones(numWords) # 對不同類別出現次數的初始化為2,詞的出現數初始數為1的情況下,增加分母值避免概率值大於1 p0Denom = 2.0 p1Denom = 2.0 for i in range(numTrainDocs): # 遍歷所有對象 if trainCategory[i] == 1: # 類別類型的判斷 p1Num += trainMatrix[i] # 對所有詞在不同的類別下出現次數的計算 p1Denom += sum(trainMatrix[i]) # 對不同類別出現次數的計算 else: p0Num += trainMatrix[i] # 對所有詞在不同的類別下出現次數的計算 p0Denom += sum(trainMatrix[i]) # 對不同類別出現次數的計算 p1Vect = log ( p1Num / p1Denom) # 條件概率,用對數的形式計算是為避免概率值太小造成下溢出 p0Vect = log (p0Num / p0Denom) # 條件概率,用對數的形式計算是為避免概率值太小造成下溢出 return p0Vect, p1Vect, pAbusive # 計算后驗概率,並選擇最高概率作為預測類別 def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): p1 = sum(vec2Classify * p1Vec ) + log(pClass1) # 對未知對象的單詞的每一項的條件概率相加(對數相加為條件概率的相乘) p0 = sum(vec2Classify * p0Vec ) + log(1.0-pClass1 ) # 后面加上的一項是先驗概率 if p1 > p0: return 1 else : return 0 # 對侮辱性語言的測試 def testingNB(): listOposts, listClasses = loadDataList() # 訓練樣本的數據,listOposts 為樣本,listClasses 為樣本的類別 myVocabList = createVocabList(listOposts ) # 樣本的詞匯集 trainMat = [] # 對樣本的所有對象相關的單詞轉化為數值 for postinDoc in listOposts : trainMat.append(setOfWords2Vec(myVocabList ,postinDoc ) ) p0V, p1V, pAb = trainNB0(array(trainMat),array(listClasses)) # 樣本的先驗概率和條件概率 testEntry = ['love','my','dalmation','love'] # 未知類別的對象 thisDoc = array(setOfWords2Vec(myVocabList ,testEntry ) ) # 對未知對象的單詞轉化為數值 print testEntry ,'classified as : ',classifyNB(thisDoc, p0V,p1V,pAb) # 對未知對象的預測其類別 testEntry = ['stupid','garbage'] # 未知類別的對象 thisDoc = array(setOfWords2Vec(myVocabList ,testEntry )) # 對未知對象的單詞轉化為數值 print testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb) # 對未知對象的預測其類別 # 郵件文件解析 def textParse(bigString): import re listOfTokens = re.split(r'\w*', bigString) # 利用正則語言對郵件文本進行解析 return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 限定單詞的字母大於2 # 完整的垃圾郵件測試函數 def spamTest(): docList=[];classList = []; fullText = [] for i in range(1,26): wordList = textParse(open('email/spam/%d.txt' %i ).read()) docList.append(wordList) # 把解析后的郵件作為訓練樣本 fullText.extend(wordList) classList.append(1) # 郵件所對應的類別 wordList = textParse(open('email/ham/%d.txt' % i).read()) docList.append(wordList) # 把解析后的郵件作為訓練樣本 fullText.extend(wordList ) classList .append(0) # 郵件所對應的類別 vocabList = createVocabList(docList) # 樣本生成的詞匯集 # 隨機產生十個測試樣本和四十個訓練樣本 trainingSet = range(50);testSet = [] for i in range(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(array(trainMat), array(trainClasses)) # 訓練樣本的先驗概率及條件概率 errorCount = 0 # 測試樣本的出錯數初始化 for docIndex in testSet: wordVector = setOfWords2Vec(vocabList ,docList[docIndex ]) # 測試對象的詞的數值轉化 if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex ]: # 預測的類別與真實類別的對比 errorCount += 1 print 'the error rate is : ', float (errorCount )/ len(testSet) # 測試樣本的出錯率 if __name__ == '__main__': #testingNB() # 對侮辱性評價的測試 spamTest() # 對垃圾郵件的測試
