1. 程式人生 > >《機器學習實戰》學習筆記之樸素貝葉斯(Naive Bayes)

《機器學習實戰》學習筆記之樸素貝葉斯(Naive Bayes)

原理

假如郵箱中有n個單詞,

如果returnVec[i]=0代表這個單詞在這封郵件中不出現,

returnVec[i]=1代表這個單詞在郵件中出現了。

設訓練集中每個郵件都有標記為是垃圾郵件和不是垃圾郵件,是垃圾郵件的分類為1,不是垃圾郵件的分類為0。

演算法原理:

提取郵件內單詞,改寫為小寫單詞輸入字典,過濾長度不大於2的單詞。

利用詞彙表計算出單詞屬於正常詞彙的概率p0V=(正常郵件中該單詞出現次數)/(正常郵件數量)。

辱罵性詞彙的概率p1V=(垃圾郵件中該單詞出現次數)/(垃圾郵件數量)。

後比較p0V和p1V的概率大小,如果p0V大於p1V就認為他屬於正常郵件,如果p1V大於p0V就認為他屬於垃圾郵件。

pb為(對應類別的郵件數)/(所有郵件總數)。

文章中的改進:設定初始一行的單詞數計數為2,是為了防止下溢,避免因為某個特性下概率為0。計算概率使用了log函式簡化乘除法。

對原文章中的程式碼進行些許改進,添加了中文註釋,加入了主函式,能夠直接執行出結果。因為能力有限,有部分可能解釋不清,希望大牛批評指正!

程式碼如下:

# -*- coding: utf-8 -*-
from numpy import *
def loadDataSet():
    #記錄資料集,使用python的list,一共是6條,每條又包含了若干條
    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,0] 
    #返回的第一個變數是進行詞條切分後的文件集合,第二個變數是一個類別標籤的集合  
    return postingList,classVec

#建立一個包含在所有文件中出現的不重複詞的列表
def createVocabList(dataSet):
    #建立一個空集
    vocabSet = set([])
    #迴圈實現如果這個詞出現在資料集中,就建立兩個集合的並集。重複的詞只會被加入進去一次。也就實現了只儲存不重複詞的功能
    for document in dataSet:
        vocabSet = vocabSet | set(document)#建立兩個集合的並集
    #返回這個詞彙列表
    return list(vocabSet)

#這個函式實現的功能:將兩個詞彙表進行對比,如果輸入的資料集在詞彙表中,就讓向量值為1,否則輸出該詞不在文件中。
#輸入的引數為詞彙表及某個文件,輸出的是文件向量。
def wordstoVec(vocabList, inputSet):
    #建立一個其中所含元素都為0的向量,向量長度與詞彙表長度一致
    returnVec = [0]*len(vocabList)
    #對輸入集中每一個單詞執行,如果單詞在詞彙表中就使這個詞的索引位置相應的的文件向量值為1,否則輸出這個詞不是詞彙表中的
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else: print "這個單詞: %s 不是我詞彙表裡的!" % word
    #返回這個轉化後的向量
    return returnVec
    
#輸入的引數為文件矩陣trainMatrix,以及由每篇文件類別標籤所構成的向量trainCategory。
#首先計算文件屬於侮辱性文件的概率,即P(1)。因為是二類分類問題,所以可以通過1-P(1)得到p(0)。
def trainNB0(trainMatrix,trainCategory):
    #訓練文字單詞數為訓練矩陣的長度。
    TrainDocsNum = len(trainMatrix)
    #單詞數為訓練矩陣第一行的長度,這只是為了初始化
    Wordsnum = len(trainMatrix[0])
    #辱罵性詞彙出現的概率為所有訓練類別相加除以訓練文字單詞總數,因為辱罵性詞彙的類別都是1.
    pbad = sum(trainCategory)/float(TrainDocsNum)
    #計算p(wi|c1)和p(wi|c0),p(w|c)指在分類c下,詞語w出現的樣本數與分類c所有樣本中所有詞數
    #讓類別為0的單詞出現的次數設為全為1的向量,向量的高度與單詞數相同。
    p0 = ones(Wordsnum)
    #讓類別為1的單詞出現的次數設為全為1的向量,向量的高度與單詞數相同。
    p1 = ones(Wordsnum)
    #設定初始一行的單詞數計數為2,這也是一處改進,為了防止下溢,避免因為某個特性下概率為0 就導致整體概率為0。把計數從1改為2
    p0Denom = 2.0; p1Denom = 2.0
    #讓所有的向量對應元素相加,一旦某個詞在文件出現,對該詞對應的個數就加1。而且總詞數也加1。
    #range代表從0到TrainDocsNum的整數列表
    for i in range(TrainDocsNum):
        #假如訓練種類對應的值為1就使類別1單詞出現的次數加1
        if trainCategory[i] == 1:
            p1 += trainMatrix[i]
        #假如訓練種類對應的值為0就使類別0單詞出現的次數加1
        else:
            p0 += trainMatrix[i]
    p1Denom = sum(trainCategory)
    p0Denom = len(trainCategory) - p1Denom
    #對每個元素出現的次數除以該類別中對應某行的總詞數,概率進行乘法過後數值會比較小,取log方便比較
    p1Vect = log(p1/p1Denom)        
    p0Vect = log(p0/p0Denom)
    return p0Vect,p1Vect,pbad

#樸素貝葉斯分類函式
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 = loadDataSet()
    #建立詞彙表
    myVocabList = createVocabList(listOPosts)
    #建立一個空集
    trainMat = []
    #迴圈所有資料集中詞彙,轉化為詞彙向量
    for postingDoc in listOPosts:
        trainMat.append(wordstoVec(myVocabList,postingDoc))
    #呼叫函式計算0類詞出現概率和1類詞出現的概率以及辱罵性詞彙出現的概率
    p0V,p1V,pb = trainNB0(array(trainMat),array(listClasses))
    testEntry = ['I','love','you']
    thisDoc = array(wordstoVec(myVocabList,testEntry))
    print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'類\n','是正常詞的概率為:\n',p0V,'\n是辱罵性詞的概率為:\n',p1V,'\n辱罵性詞佔得比例:',pb
    testEntry = ['stupid','garbage']
    thisDoc = array(wordstoVec(myVocabList,testEntry))
    print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'類\n','是正常詞的概率為:\n',p0V,'\n是辱罵性詞的概率為:\n',p1V,'\n辱罵性詞佔得比例:',pb

#應用部分:使用樸素貝葉斯過濾垃圾郵件,使用到交叉驗證。
#資料準備會有垃圾郵件資料夾下面全部是標記的垃圾郵件,正常郵件資料夾下是正常郵件。
#textParse函式接受一個字串並將其解析為字串列表,把大寫字元轉化成小寫,並且只儲存大於兩個字母的詞。
#spamTest函式在50封郵件中選取10篇郵件隨機選擇為測試集交叉驗證。
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\W*',bigString)
    return[tok.lower() for tok in listOfTokens if len(tok) > 2]

#對貝葉斯垃圾郵件分類器進行自動化處理。匯入資料夾spam和ham下的文字檔案,並將它們解析為詞列表。
def spamTest():
    docList = []; classList = []; fullText = []
    #匯入並解析文字
    for i in range(1,26):
        wordList = textParse(open('email/垃圾郵件/%d.txt' % i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('email/正常郵件/%d.txt' % i).read())
        docList.append(wordList)
        #extend()方法只接受一個列表作為引數,並將該引數的每個元素都新增到原有的列表中。
        fullText.extend(wordList)
        #append() 方法向列表的尾部新增一個新的元素。只接受一個引數。
        classList.append(0)
    vocabList = createVocabList(docList)
    #隨機構建訓練集,進行留存交叉驗證
    trainingSet = range(50); testSet = []
    for i in range(15):
        #uniform() 方法將隨機生成下一個實數,它在[x,y]範圍內
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat = []; trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(wordstoVec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    #對測試集分類
    for docIndex in testSet:
        wordVector = wordstoVec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!= classList[docIndex]:
            errorCount += 1
            print "分類錯誤了!分類的錯誤詞是: \n",docList[docIndex],'分類錯誤的數目有: ',errorCount
    print '分類錯誤率為:',float(errorCount)/len(testSet)

if __name__ == "__main__":
    listOPosts,listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    print '       ————————————————————我的詞彙列表:——————————————————\n',myVocabList
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(wordstoVec(myVocabList, postinDoc))
    p0V,p1V,pAb = trainNB0(trainMat, listClasses)
    print '       ——————————————————————測試案例———————————————————————————\n'
    testingNB()
    print '       ———————————————第1次垃圾郵件分類自動化處理結果——————————————————\n'
    spamTest()
    print '       ———————————————第2次垃圾郵件分類自動化處理結果——————————————————\n'
    spamTest()
    print '       ———————————————第3次垃圾郵件分類自動化處理結果——————————————————\n'
    spamTest()