1. 程式人生 > >第3章 樸素貝葉斯演算法 (二 演算法實戰)

第3章 樸素貝葉斯演算法 (二 演算法實戰)

3.6樸素貝葉斯實踐

3.6.1樸素貝葉斯之微博評論篩選

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

3.6.1.1樸素貝葉斯之微博評論篩選實現

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

# -*- coding: utf-8 -*-

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

從執行結果可以看出,我們已經將postingList是存放詞條列表中,classVec是存放每個詞條的所屬類別,1代表低俗類 ,0代表非低俗類。 在這裡插入圖片描述 繼續編寫程式碼,前面我們已經說過我們要先建立一個詞彙表,並將切分好的詞條轉換為詞條向量。

# -*- coding: utf-8 -*-

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

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):
    #建立一個其中所含元素都為0的向量
    returnVec = [0] * len(vocabList)									
   
    #遍歷每個詞條
    for word in inputSet:			
        #如果詞條存在於詞彙表中,則置1									
        if word in vocabList:											
            returnVec[vocabList.index(word)] = 1
        else: 
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec		#返回文件向量		
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向量化的詞條向量。

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

# -*- coding: utf-8 -*-
import numpy as np

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

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):
    #建立一個其中所含元素都為0的向量
    returnVec = [0] * len(vocabList)									
   
    #遍歷每個詞條
    for word in inputSet:			
        #如果詞條存在於詞彙表中,則置1									
        if word in vocabList:											
            returnVec[vocabList.index(word)] = 1
        else: 
            print("the word: %s is not in my Vocabulary!" % word)
    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)	
    #建立numpy.zeros陣列,
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)	
    #分母初始化為0.0
    p0Denom = 0.0; p1Denom = 0.0                        	
    
    for i in range(numTrainDocs):
        #統計屬於低俗類的條件概率所需的資料,即P(w0|1),P(w1|1),P(w2|1)···
        if trainCategory[i] == 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		#返回屬於低俗類的條件概率陣列,屬於非低俗類的條件概率陣列,文件屬於低俗類的概率

執行結果如下,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 reduce

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

Parameters:
	dataSet - 整理的樣本資料集
Returns:
	vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
#建立一個包含在所有文件中出現的不重複的列表 |用於求兩個集合並集,詞集
def createVocabList(dataSet):
    #建立一個空的不重複列表
    vocabSet = set([]) 
    #遍歷資料集
    for document in dataSet:				
        vocabSet = vocabSet | set(document) #取並集
    #print(vocabSet)	
    return list(vocabSet)#生成一個包含所有單詞的列表

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

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


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

Parameters:
	trainMatrix - 訓練文件矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
	trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
	p0Vect - 非低俗的條件概率陣列
	p1Vect - 低俗類的條件概率陣列
	pAbusive - 文件屬於低俗類的概率

"""
def trainNB(trainMatrix,trainCategory):

    #計算訓練的文件條數
    numTrainDocs = len(trainMatrix)							
    #print("numTrainDocs:" + str(numTrainDocs))
    
    #計算每篇文件的詞條數
    numWords = len(trainMatrix[0])							
    #print("numWords:" + str(numWords))
    
    #文件屬於低俗類的概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)	
    
    #建立numpy.zeros陣列,
    p0Num = np.zeros(numWords); 
    p1Num = np.zeros(numWords)	
    
    #分母初始化為0.0
    p0Denom = 0.0; 
    p1Denom = 0.0                        	
    
    for i in range(numTrainDocs):
        #統計屬於低俗類的條件概率所需的資料,即P(w0|1),P(w1|1),P(w2|1)···
        if trainCategory[i] == 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   
    
    #print(p1Num)
    #print(p0Num)
    #print(p1Denom)
    #print(p0Denom)
      
    return p0Vect,p1Vect,pAbusive		#返回屬於低俗類的條件概率陣列,屬於非低俗類的條件概率陣列,文件屬於低俗類的概率

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

Parameters:
	vec2Classify - 待分類的詞條陣列
	p0Vec - 非低俗類的條件概率陣列
	p1Vec -低俗類的條件概率陣列
	pClass1 - 文件屬於低俗類的概率
Returns:
	0 - 屬於非低俗類
	1 - 屬於低俗類

"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):

    #從左到右對一個序列的項累計地應用有兩個引數的函式,以此合併序列到一個單一值。
    #例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])  計算的就是((((1+2)+3)+4)+5)。
    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():
    
	## Step 1: load data
    print("Step 1: load data...")
    #建立資料
    #建立實驗樣本
    listPosts,listClasses = loadDataSet()									
    #建立詞彙表
    myVocabList = createVocabList(listPosts)

    #向量化樣本
    trainMat=[]
    for postinDoc in listPosts: 
        #將實驗樣本向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))				
    #print(trainMat)
    
	## Step 2: training...
    print("Step 2: training...")
    #訓練樸素貝葉斯分類器
    p0V,p1V,pAb = trainNB(np.array(trainMat),np.array(listClasses))		
    
	## Step 3: testing
    print("Step 3: testing...")
    #測試樣本1
    testEntry = ['love', 'my', 'dalmation']									
    
    #測試樣本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				
    
	## Step 4: show the result
    print("Step 4: show the result...")
	if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於低俗類')										
        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非低俗類')										
        #執行分類並列印分類結果

    #測試樣本2
    testEntry = ['stupid', 'garbage']										
    #測試樣本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於低俗類')										
        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非低俗類')										
        #執行分類並列印分類結果

if __name__ == '__main__':
	testingNB()

我們測試了兩個詞條,在使用分類器前,也需要對詞條向量化,然後使用classifyNB()函式,用樸素貝葉斯公式,計算詞條向量屬於低俗類和非低俗類的概率。執行結果如下:

在這裡插入圖片描述 你會發現,這樣寫的演算法無法進行分類,p0和p1的計算結果都是0,這裡顯然存在問題。那麼怎麼解決呢?

在計算的時候已經出現了概率為0的情況。如果新例項文字,包含這種概率為0的分詞,那麼最終的文字屬於某個類別的概率也就是0了。顯然,這樣是不合理的,為了降低這種影響,可以將所有詞的出現數初始化為1,並將分母初始化為2。這種做法就叫做拉普拉斯平滑(Laplace Smoothing)又被稱為加1平滑,是比較常用的平滑方法,它就是為了解決0概率問題。

除此之外,另外一個遇到的問題就是下溢位,這是由於太多很小的數相乘造成的。學過數學的人都知道,兩個小數相乘,越乘越小,這樣就造成了下溢位。在程式中,在相應小數位置進行四捨五入,計算結果可能就變成0了。為了解決這個問題,對乘積結果取自然對數。通過求對數可以避免下溢位或者浮點數舍入導致的錯誤。同時,採用自然對數進行處理不會有任何損失。下圖給出函式f(x)和ln(f(x))的曲線。

在這裡插入圖片描述

圖1
檢查這兩條曲線,就會發現它們在相同區域內同時增加或者減少,並且在相同點上取到極值。它們的取值雖然不同,但不影響最終結果。最終的程式碼如下。
# -*- coding: utf-8 -*-
import numpy as np

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

Parameters:
	dataSet - 整理的樣本資料集
Returns:
	vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
#建立一個包含在所有文件中出現的不重複的列表 |用於求兩個集合並集,詞集
def createVocabList(dataSet):
    #建立一個空的不重複列表
    vocabSet = set([]) 
    #遍歷資料集
    for document in dataSet:				
        vocabSet = vocabSet | set(document) #取並集
    #print(vocabSet)	
    return list(vocabSet)#生成一個包含所有單詞的列表

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

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


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

Parameters:
	trainMatrix - 訓練文件矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
	trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
	p0Vect - 非低俗的條件概率陣列
	p1Vect - 低俗類的條件概率陣列
	pAbusive - 文件屬於低俗類的概率

"""
def trainNB(trainMatrix,trainCategory):

    #計算訓練的文件條數
    numTrainDocs = len(trainMatrix)							
    #print("numTrainDocs:" + str(numTrainDocs))
    
    #計算每篇文件的詞條數
    numWords = len(trainMatrix[0])							
    #print("numWords:" + str(numWords))
    
    #文件屬於低俗類的概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)	
    
    #建立numpy.ones陣列,
	#建立numpy.ones陣列,詞條出現數初始化為1,拉普拉斯平滑
    p0Num = np.ones(numWords); 
    p1Num = np.ones(numWords)	
    
    #分母初始化為2.0
    p0Denom = 2.0; 
    p1Denom = 2.0                        	
    
    for i in range(numTrainDocs):
        #統計屬於低俗類的條件概率所需的資料,即P(w0|1),P(w1|1),P(w2|1)···
        if trainCategory[i] == 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)   
    
    #print(p1Num)
    #print(p0Num)
    #print(p1Denom)
    #print(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)    			#對應元素相乘
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
	
    print('p0:',p0)
    print('p1:',p1)
    
    if p1 > p0:
        return 1
    else: 
        return 0

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

Parameters:
	無
Returns:
	無
"""
def testingNB():
    ## Step 1: load data
    print("Step 1: load data...")
    #建立資料
    #建立實驗樣本
    listPosts,listClasses = loadDataSet()									
    #建立詞彙表
    myVocabList = createVocabList(listPosts)

    #向量化樣本
    trainMat=[]
    for postinDoc in listPosts: 
        #將實驗樣本向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))				
    #print(trainMat)
    
    ## Step 2: training...
    print("Step 2: training...")
    #訓練樸素貝葉斯分類器
    p0V,p1V,pAb = trainNB(np.array(trainMat),np.array(listClasses))		
    
    ## Step 3: testing
    print("Step 3: testing...")
    #測試樣本1
    testEntry = ['love', 'my', 'dalmation']									
    
    #測試樣本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				
    
    ## Step 4: show the result
    print("Step 4: show the result...")
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於低俗類')										
        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非低俗類')										
        #執行分類並列印分類結果

    #測試樣本2
    testEntry = ['stupid', 'garbage']										
    #測試樣本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於低俗類')										
        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非低俗類')										
        #執行分類並列印分類結果

if __ name__ == \' __main__ \':
	testingNB()

結果如下。 在這裡插入圖片描述 【完整程式碼參考1.NB_word2vec的NB_word2vec_v1.1.py】

3.6.1.2樸素貝葉斯之微博評論篩選實現-呼叫sklearn庫

呼叫sklearn庫的資料輸入和前面一節相同,主要是呼叫庫的過程,直接看程式碼,程式碼很詳細,筆者不想在細講了。

# -*- coding: utf-8 -*-
import numpy as np
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split

"""
函式說明:建立實驗樣本

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']]
	
    #類別標籤向量,1代表低俗性詞彙,0代表不是
    classVec = [0,1,0,1,0,1]   																
   
    #返回實驗樣本切分的詞條和類別標籤向量 
    return postingList,classVec																

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

Parameters:
	dataSet - 整理的樣本資料集
Returns:
	vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
#建立一個包含在所有文件中出現的不重複的列表 |用於求兩個集合並集,詞集
def createVocabList(dataSet):
    #建立一個空的不重複列表
    vocabSet = set([]) 
    #遍歷資料集
    for document in dataSet:				
        vocabSet = vocabSet | set(document) #取並集
    #print(vocabSet)	
    return list(vocabSet)#生成一個包含所有單詞的列表

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

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


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

Parameters:
	無
Returns:
	無
"""
def testingNB():
    ## Step 1: load data
    print("Step 1: load data...")
    #建立資料#建立實驗樣本
    listPosts,listClasses = loadDataSet()									
    #建立詞彙表
    myVocabList = createVocabList(listPosts)

    #向量化樣本
    trainMat=[]
    
    for postinDoc in listPosts: 
        #將實驗樣本向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))				
    #print(trainMat)
    
    ## Step 3: init NB
    print("Step 3: init NB...")
    #初始化貝葉斯分類器
    gnb = GaussianNB()
 
    ## Step 4: training...
    print("Step 4: training...")
    #訓練資料
    gnb.fit(trainMat, listClasses)

    ## Step 5: testing
    print("Step 5: testing...")
    testEntry = ['love', 'my', 'dalmation']
    #testEntry = ['stupid', 'garbage']	
    
    #測試樣本向量化
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))	
    #print(thisDoc)

    #預測資料
    predictedLabel =  gnb.predict([thisDoc])
    #predictedLabel = gnb.fit(trainMat, listClasses).predict(thisDoc)

    ## Step 6: show the result
    print("Step 6: show the result...")
    print(predictedLabel)
    if (predictedLabel == 0):
        print("屬於非低俗類")
    else:
        print("屬於低俗類")
    
if __name__ == '__main__':
	testingNB()

執行結果如下。 在這裡插入圖片描述 【完整程式碼參考1.NB_word2vec的NB_word2vec_v2.py】

3.6.2樸素貝葉斯之鳶尾花卉分類

在上一章,筆者使用了KNN對鳶尾花卉進行了分類,那麼在這一章還可以不?答案是可以的。我們還是用兩種方法完成,一種是所有的演算法都自己編寫,另外一種是呼叫sklearn的API來實現。

3.6.2.1 樸素貝葉斯實戰之鳶尾花卉分類實現

直接上程式碼吧.

# -*- coding: utf-8 -*-
import numpy as np
import csv#用於處理csv檔案
import random#用於隨機數

"""
函式說明:載入資料

Parameters:
  filename - 檔名
  split - 分隔符
  trainingSet - 訓練集
  testSet - 測試集
Returns:
	無
"""
def loadDataset(filename, split, trainSet = [], testSet = []):
    with open(filename, 'rt') as csvfile:
        
        #從csv中讀取資料並返回行數
        lines = csv.reader(csvfile)
        
        dataset = list(lines)
        for x in range(len(dataset)-1):
            for y in range(4):
                dataset[x][y] = float(dataset[x][y])
            #儲存資料集到訓練集和測試集#random.random()返回隨機浮點數
            if random.random() < split:
                trainSet.append(dataset[x])
            else:
                #將獲得的測試資料放入測試集中
                testSet.append(dataset[x])														
"""
函式說明:分割資料

Parameters:
    dataSet - 資料集
Returns:
    data_X - 特徵資料集
    data_Y - 標籤資料集
"""
def segmentation_Data(dataSet):
    
    #得到檔案行數
    Lines = len(dataSet)
    
    #返回的NumPy矩陣,解析完成的資料:4列
    data_X = np.zeros((Lines,4))
    data_Y = []
    for x in range(Lines):
        data_X[x,:] = dataSet[x][0:4]
        if dataSet[x][-1] == 'Iris-setosa':
            data_Y.append(1)
        elif dataSet[x][-1] == 'Iris-versicolor':
            data_Y.append(2)
        elif dataSet[x][-1] == 'Iris-virginica':
            data_Y.append(3)
    return data_X, data_Y

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

Parameters:
	dataSet - 整理的樣本資料集
Returns:
	vocabSet - 返回不重複的詞條列表,也就是詞彙表
"""
#建立一個包含在所有文件中出現的不重複的列表 |用於求兩個集合並集,詞集
def createVocabList(dataSet):
    #建立一個空的不重複列表
    vocabSet = set([]) 
    #遍歷資料集
    for document in dataSet:				
        vocabSet = vocabSet | set(document) #取並集
    #print(vocabSet)	
    return list(vocabSet)#生成一個包含所有單詞的列表

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

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


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

Parameters:
	trainMatrix - 訓練文件矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
	trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
	p1Vect,p2Vect,p3Vect,pAbusive,pBbusive,pCbusive

"""
def trainNB(trainMatrix,trainCategory):

    #計算訓練的文件條數
    numTrainDocs = len(trainMatrix)							
    #print("numTrainDocs:" + str(numTrainDocs))
    
    #計算每篇文件的詞條數
    numWords = len(trainMatrix[0])							
    #print("numWords:" + str(numWords))
    
    count = np.full(3, 0.0)
    
    for i in range(len(trainCategory)):
        if trainCategory[i] == 1:
            count[0] += 1
        elif trainCategory[i] == 2:
            count[1] += 1
        else:
            count[2] += 1
    
    pbusive = []
    #計算先驗概率   
    for i in range(3):
        pb = count[i] /float(numTrainDocs)
        pbusive.append(pb)
    #print(pbusive)
 
    #建立numpy.ones陣列,詞條出現數初始化為1,拉普拉斯平滑
    pNum = np.ones((3,numWords))
    #print(pNum)
    
    #分母初始化為0.0#避免其中一項為0的影響 
    pDenom =  np.full(3, 2.0)
    #print(pDenom)

    for i in range(numTrainDocs):
        #統計屬於低俗類的條件概率所需的資料,即P(w0|1),P(w1|1),P(w2|1)···
        if trainCategory[i] == 1:							
            pNum[0] += trainMatrix[i]
            pDenom[0] += sum(trainMatrix[i])
        elif trainCategory[i] == 2:							
            pNum[1] += trainMatrix[i]
            pDenom[1] += sum(trainMatrix[i])
        else:							
            pNum[2] += trainMatrix[i]
            pDenom[2] += sum(trainMatrix[i])
    
    pVect = []
    #避免下溢位問題
    for i in range(3):
        pV = np.log(pNum[i]/pDenom[i])								#相除        
        pVect.append(pV)
        
    return pVect, pbusive		#返回條件概率陣列

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

Parameters:
    vec2Classify - 待分類的詞條陣列
    pVec
    pClass
    lables - 標籤
Returns:
    最大概率的標籤
"""
def classifyNB(vec2Classify, pVec, pClass,lables):
    
    #概率列表
    p = []
    
    #從左到右對一個序列的項累計地應用有兩個引數的函式,以此合併序列到一個單一值
    for i in range(len(lables)):
        result = sum(vec2Classify * pVec[i]) + np.log(pClass[i])
        p.append(result)
    
    #返回p中元素從小到大排序後的索引值
    # 按照升序進行快速排序,返回的是原陣列的下標。
    # 比如,x = [30, 10, 20, 40]
    # 升序排序後應該是[10,20,30,40],他們的原下標是[1,2,0,3]
    # 那麼,numpy.argsort(x) = [1, 2, 0, 3]
    sortedpIndices = np.argsort(p)
  
    #返回最大概率標籤
    return lables[sortedpIndices[-1]]


"""
函式說明:計算準確率

Parameters:
  testSet - 測試集
  predictions - 預測值 
Returns:
	返回準確率
"""
#計算準確率
def getAccuracy(testSet, predictions):
    correct = 0
    for x in range(len(testSet)):
        if testSet[x] == predictions[x]:
            correct += 1
    return (correct/float(len(testSet)))*100.0


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

Parameters:
	無
Returns:
	無
"""
def testingNB():

    ## Step 1: load data
    print("Step 1: load data...")
    #建立資料
    #prepare data
    trainSet = []#訓練資料集
    testSet = []#測試資料集
    split = 0.8#分割的比例
    
    #lables = ['Iris-setosa','Iris-versicolor','Iris-virginica']
    
    lables = [1, 2, 3]
    
    loadDataset('C:/TensorFlow/irisdata.txt', split, trainSet, testSet)
    
    #資料集分割
    train_X,train_Y = segmentation_Data(trainSet)
    test_X,test_Y = segmentation_Data(testSet)
    
    print('Train set: ' + repr(len(trainSet)))
    print('Test set: ' + repr(len(testSet)))
    
    #print(train_X)
    #print(train_Y)

    #建立實驗樣本
    #listPosts,listClasses = loadDataSet()									
    #建立詞彙表
    myVocabList = createVocabList(train_X)

    #向量化樣本
    trainMat=[]
    for postinDoc in train_X: 
        #將實驗樣本向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))				
    #print(trainMat)

    ## Step 2: training...
    print("Step 2: training...")
    #訓練樸素貝葉斯分類器
    pV,pb = trainNB(np.array(trainMat),np.array(train_Y))	
    
    ## Step 3: testing
    print("Step 3: testing...")
    #測試樣本
    #testEntry = [5.1,3.5,1.4,0.2]									
    #testEntry = [6.8,2.8,4.8,1.4]
    
    thisDoc = []
    predictedLabel = []
    
    for postinDoc in test_X: 
        #將實驗樣本向量化
        thisDoc.append(setOfWords2Vec(myVocabList, postinDoc))				
        
    #測試樣本向量化
    #thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))				
   
    for i in range(len(thisDoc)):
        result = classifyNB(thisDoc[i],pV,pb,lables)
        predictedLabel.append(result)
    
    ## Step 4: show the result
    print("Step 4: show the result...")
    #print(predictedLabel)
    
    #print(test_Y)
    #準確率
    accuracy = getAccuracy(test_Y, predictedLabel)
    print('\nAccuracy: ' + repr(accuracy) + '%')
    
if __name__ == '__main__':
	testingNB()	

結果如下所示。 在這裡插入圖片描述 【完整程式碼參考2.NB_Iris_ Classify下的NB_Iris_Classify_v1】

3.6.2.2樸素貝葉斯實戰之鳶尾花卉分類實現-呼叫sklearn庫

接下來,我們使用sklearn來進行分類,程式碼如下。

# -*- coding: utf-8 -*-
from sklearn import datasets
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

## Step 1: load data
print("Step 1: load data...")
#匯入資料
iris = datasets.load_iris()

## Step 2: split data
print("Step 2: split data...")
#分離資料
# X = features
X = iris.data
# Y = label
Y = iris.target
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=.6)

## Step 3: init NB
print("Step 3: init NB...")
#初始化貝葉斯分類器
gnb = GaussianNB()

## Step 4: training...
print("Step 4: training...")
#訓練資料
gnb.fit(X_train, Y_train)

## Step 5: testing
print("Step 5: testing...")
#預測資料
predictedLabel =  gnb.predict(X_test)
#predictedLabel = gnb.fit(X_train, Y_train).predict(X_test)

## Step 6: show the result
print("Step 6: show the result...")
#求準確率
# http://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html
print(accuracy_score(Y_test, predictedLabel))
print("predictedLabel is :")
print(predictedLabel)

結果如下所示。 在這裡插入圖片描述 【完整程式碼參考2.NB_Iris_ Classify下的NB_Iris_Classify_v2.py】

3.6.3樸素貝葉斯之垃圾郵件過濾

3.6.3.1樸素貝葉斯之垃圾郵件過濾實現

樸素貝葉斯的一個最著名的應用:電子郵件垃圾過濾。 對於英文文字,我們可以以非字母、非數字作為符號進行切分,使用split函式即可。編寫程式碼如下:

# -*- coding: UTF-8 -*-
import re

"""
函式說明:接收一個大字串並將其解析為字串列表

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

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

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

if __name__ == '__main__':
    docList = []; classList = []
    for i in range(1, 26):                                                  #遍歷25個txt檔案
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())     #讀取每個垃圾郵件,並字串轉換成字串列表
        docList.append(wordList)
        classList.append(1)                                                 #標記垃圾郵件,1表示垃圾檔案
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      #讀取每個非垃圾郵件,並字串轉換成字串列表
        docList.append(wordList)
        classList.append(0)                                                 #標記非垃圾郵件,1表示垃圾檔案   
    vocabList = createVocabList(docList)                                    #建立詞彙表,不重複
    print(vocabList)

根據詞彙表,我們就可以將每個文字向量化。我們將資料集分為訓練集和測試集,使用交叉驗證的方式測試樸素貝葉斯分類器的準確性。編寫程式碼如下:
# -*- coding: utf-8 -*-
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):
    #建立一個其中所含元素都為0的向量
    returnVec = [0] * len(vocabList)									
   
    #遍歷每個詞條
    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:
	trainMatrix - 訓練文件矩陣,即setOfWords2Vec返回的returnVec構成的矩陣
	trainCategory - 訓練類別標籤向量,即loadDataSet返回的classVec
Returns:
	p0Vect
	p1Vect
	pAbusive 
"""
def trainNB(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,拉普拉斯平滑
    #分母初始化為2,拉普拉斯平滑
    p0Denom = 2.0
    p1Denom = 2.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 = np.log(p1Num/p1Denom)							
              
    p0Vect = np.log(p0Num/p0Denom)     
    
    #返回條件概率陣列     
    return p0Vect,p1Vect,pAbusive							


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

Parameters:
	vec2Classify - 待分類的詞條陣列
	p0Vec 條件概率陣列
	p1Vec
	pClass1 
Returns:

"""
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:
    無
Returns:
    無
"""
def textParse(bigString):   #將字串轉換為字元列表
    #將特殊符號作為切分標誌進行字串切分,即非字母、非數字
    listOfTokens = re.split(r'\W*', bigString) 
	
    #除了單個字母,例如大寫的I,其它單詞變成小寫
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]  
	
"""
函式說明:測試樸素貝葉斯分類器

Parameters:
    無
Returns:
    無
"""
def spamTest():
    ## Step 1: load data
    print("Step 1: load data...")

    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)
        #標記垃圾郵件,1表示垃圾檔案
        classList.append(1)                                                 
		   #讀取每個非垃圾郵件,並字串轉換成字串列表
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read())      
		
        docList.append(wordList)
        fullText.append(wordList)
        #標記非垃圾郵件,1表示垃圾檔案  
        classList.append(0)                                                 
		  
	  #建立詞彙表,不重複
    vocabList = createVocabList(docList)                                    

    trainingSet = list(range(50))
                                
    #建立儲存訓練集的索引值的列表和測試集的索引值的列表                        
    testSet = []
    
	  #從50個郵件中,隨機挑選出40個作為訓練集,10個做測試集
    for i in range(10):                                                     
    		 #隨機選取索索引值
        randIndex = int(random.uniform(0, len(trainingSet)))                
        #新增測試集的索引值
        testSet.append(trainingSet[randIndex])                              
        #在訓練集列表中刪除新增到測試集的索引值
        del(trainingSet[randIndex])                                         
		
    
    #建立訓練集矩陣和訓練集類別標籤系向量 
    trainMat = []
    trainClasses = []                                        
                 
    ## Step 2: training...
    print("Step 2: training...")

    #遍歷訓練集
    for docIndex in trainingSet:                                            
        #將生成的詞集模型新增到訓練矩陣中
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))       
        #將類別新增到訓練集類別標籤系向量中
        trainClasses.append(classList[docIndex])                            
    #訓練樸素貝葉斯模型
    p0V, p1V, pSpam = trainNB(np.array(trainMat), np.array(trainClasses))  
    #錯誤分類計數
    errorCount = 0                                                          
    
    ## Step 3: testing
    print("Step 3: testing...")
    #遍歷測試集
    for docIndex in testSet:                                                
        #測試集的詞集模型
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])           
       
        #如果分類錯誤
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:    
            #錯誤計數加1
            errorCount += 1                                                
            print("分類錯誤的測試集:",docList[docIndex])
    
    ## Step 4: show the result
    print("Step 4: show the result...")
    print('錯誤率:%.2f%%' % (float(errorCount) / len(testSet) * 100))

if __name__ == '__main__':
    spamTest()

【完整程式碼參考3.NB_email_Classify下的NB_email_Classify_v1】

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

3.6.3.2樸素貝葉斯之垃圾郵件過濾實現-呼叫sklearn庫

和前面的例子一樣,呼叫sklearn庫。

# -*- coding: utf-8 -*-
import random
import re
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


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

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):
    #建立一個其中所含元素都為0的向量
    returnVec = [0] * len(vocabList)									
   
    #遍歷每個詞條
    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:
    無
Returns:
    無
"""
def textParse(bigString):    
    #將特殊符號作為切分標誌進行字串切分,即非字母、非數字                                               #將字串轉換為字元列表
    listOfTokens = re.split(r'\W*', bigString)  
    
    #print(listOfTokens)
    
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]            #除了單個字母,例如大寫的I,其它單詞變成小寫

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

Parameters:
    無
Returns:
    無
"""
def spamTest():
    ## Step 1: load data
    print("Step 1: load data...")

    docList = []
    classList = []
    fullText = []
    
    for i in range(1, 26):                                                  #遍歷25個txt檔案
        wordList = textParse(open('email/spam/%d.txt' % i, 'rt').read())     #讀取每個垃圾郵件,並字串轉換成字串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)                                                 #標記垃圾郵件,1表示垃圾檔案
        wordList = textParse(open('email/ham/%d.txt' % i, 'rt').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 = []                                        
                 
    ## Step 2: training...
    print("Step 2: training...")

    #遍歷訓練集
    for docIndex in trainingSet:                                            
        #將生成的詞集模型新增到訓練矩陣中
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))       
        #將類別新增到訓練集類別標籤系向量中
        trainClasses.append(classList[docIndex])                            
    
    X_train, X_test, Y_train, Y_test = train_test_split(trainMat, trainClasses, test_size=.6)

    ## Step 3: init NB
    print("Step 3: init NB...")
    #初始化貝葉斯分類器
    gnb = GaussianNB()
    
    ## Step 4: training...
    print("Step 4: training...")
    #訓練資料
    gnb.fit(X_train, Y_train)
    
    ## Step 5: testing
    print("Step 5: testing...")
    #預測資料
    predictedLabel =  gnb.predict(X_test)
    #predictedLabel = gnb.fit(X_train, Y_train).predict(X_test)

    ## Step 6: show the result
    print("Step 6: show the result...")
    #求準確率
    # http://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html
    print(accuracy_score(Y_test, predictedLabel))
    print("predictedLabel is :")
    print(predictedLabel)

if __name__ == '__main__':
spamTest()

結果如下所示。 在這裡插入圖片描述 【完整程式碼參考3.NB_email_Classify下的NB_email_Classify_v2】

3.6.4樸素貝葉斯sklearn總結

樸素貝葉斯是一類比較簡單的演算法,scikit-learn中樸素貝葉斯類庫的使用也比較簡單。相對於決策樹,KNN之類的演算法,樸素貝葉斯需要關注的引數是比較少的,這樣也比較容易掌握。在scikit-learn中,一共有3個樸素貝葉斯的分類演算法類。分別是GaussianNB,MultinomialNB和BernoulliNB。其中GaussianNB就是先驗為高斯分佈的樸素貝葉斯,MultinomialNB就是先驗為多項式分佈的樸素貝葉斯,而BernoulliNB就是先驗為伯努利分佈的樸素貝葉斯。上篇文章講解的先驗概率模型就是先驗概率為多項式分佈的樸素貝葉斯。一般來說,如果樣本特徵的分佈大部分是連續值,使用GaussianNB會比較好。如果如果樣本特徵的分大部分是多元離散值,使用MultinomialNB比較合適。而如果樣本特徵是二元離散值或者很稀疏的多元離散值,應該使用BernoulliNB。

在這裡插入圖片描述  GaussianNB類

class sklearn.naive_bayes.GaussianNB(priors=None)

引數說明:

  • priors : 可選引數,預設為None。如果指定了先驗資訊,則不會根據資料進行調整。

在這裡插入圖片描述 GaussianNB假設特徵的先驗概率為正態分佈,即如下式: 在這裡插入圖片描述 其中CkC_kYY的第kk類類別。μk\mu_kσk2\sigma_k^2為需要從訓練集估計的值。 GaussianNB會根據訓練集求出μk\mu_kσk2\sigma_k^2μk\mu_k