機器學習之樸素貝葉斯算法

聲明:本篇博文是學習《機器學習實戰》一書的方式路程,系原創,若轉載請標明來源。算法

1 貝葉斯定理的引入

機率論中的經典條件機率公式:app

公式的理解爲,P(X ,Y)= P(Y,X)<=> P(X | Y)P(Y)= P(Y | X)P (X),即 X 和 Y 同時發生的機率與 Y 和 X 同時發生的機率同樣。less

2 樸素貝葉斯定理

樸素貝葉斯的經典應用是對垃圾郵件的過濾,是對文本格式的數據進行處理,所以這裏以此爲背景講解樸素貝葉斯定理。設D 是訓練樣本和相關聯的類標號的集合,其中訓練樣本的屬性集爲          X { X1,X2, ... , X}, 共有n 個屬性;類標號爲 C{ C1,C2, ... ,C}, 有m 中類別。樸素貝葉斯定理:dom

其中,P(Ci | X)爲後驗機率,P(Ci)爲先驗機率,P(X | Ci)爲條件機率。樸素貝葉斯的兩個假設:一、屬性之間相互獨立。二、每一個屬性同等重要。經過假設1 知,條件機率P(X | Ci)能夠簡化爲:機器學習

3 樸素貝葉斯算法

 樸素貝葉斯算法的核心思想:選擇具備最高後驗機率做爲肯定類別的指標。下面是以過濾有侮辱性的評論爲例,介紹樸素貝葉斯利用Python 語言實現的過程,其本質是利用詞和類別的聯合機率來預測給定文檔屬於某個類別。ide

4 使用Python對文本分類

 4.1 創建文本數據

 文本數據用一個個對象組成,一個對象是由若干單詞組成,每一個對象對應一個肯定的類別。函數

代碼以下:post

 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()  # 對垃圾郵件的測試
完整代碼
相關文章
相關標籤/搜索