1. 程式人生 > >《機器學習實戰》之四——樸素貝葉斯

《機器學習實戰》之四——樸素貝葉斯

這裡寫自定義目錄標題

《機器學習實戰》之四——樸素貝葉斯

knn和決策樹要求分類器對輸入給出分類的結果。而『樸素貝葉斯』是給出一個最優的類別猜測結果,同時給出這個猜測的概率估計值,選擇具有高概率的決策值。

一. 數學部分

樸素貝葉斯的核心還是貝葉斯公式,只不過加上了一些假設條件:

以二分類問題為背景:
要判斷物體屬於哪一類,我們可以通過比較p(c1|x)與p(c2|x)的大小,哪個大,我們就認為屬於哪一類!
1. 根據貝葉斯公式

在這裡插入圖片描述
問題轉換為比較:

在這裡插入圖片描述

對於p(c1), p(c2),利用訓練樣本,我們可以通過已知分類,或者手動分類來獲得一個大的樣本中,有多少輸入C1,多少輸入C2,從而計算出p(c1), p(c2),那麼,現在的問題就是計算p(x|c1), p(x|c2)
注意:對於二分類問題來說p(c2)=1-p(c1)

2. 樸素貝葉斯
對於一個物品x,我們可以用x1,x2,…,xn特徵來代表它。即
在這裡插入圖片描述
樸素貝葉斯的假設:假設每個特徵都是相互獨立的。於是有
在這裡插入圖片描述


那這樣問題就簡單多了,利用訓練樣本,我們只需要計算每個類別下每個特徵的條件概率就可以了。
注意:樸素貝葉斯分類器有兩種實現方法:一種是基於貝努力模型實現(該方法任務特徵是同等重要的,所以在實現過程中並不考慮詞在文件中出現的次數);另一種是基於多項式模型實現的(它考慮詞在文件中的出現次數)

二. 準備資料階段

依照機器學習的步驟,首先是準備輸入資料。我們同樣生成一個簡單的訓練資料。新建檔案bayes.py,編輯程式碼如下:

  1. 轉換為可接受的資料格式
##詞表到向量的轉換函式
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'], ['qiut','buying','worthless','dog','food','stupid']] classVec = [0,1,0,1,0,1] #1 代表侮辱性文字, 0 代表正常言論 return postingList, classVec
  1. 接下來我們就要統計文件中的單詞種類了,也就是資料集中的特徵。編輯如下程式碼:
##建立一個不重複的詞彙表
def createVocabList(dataSet):
    vocabSet = set([])  #建立空集
    for document in dataSet:
        vocabSet = vocabSet | set(document) #建立兩個集合的並集
    return list(vocabSet)
  1. 有了詞彙表,我們就可以通過詞彙表對輸入的資料進行分析了。我們構建一個函式分析輸入資料。該函式的輸入引數為:詞彙表及資料詞條。輸出為何詞彙條同樣大小的向量,其中向量中的值非0即1,表示詞條中是否出現該單詞。
    處理過程:建立和等長的向量,遍歷文件中的所有單詞,如果文件在那個出現了詞彙表中的單詞,則將輸出的文件向量中的對應值設為1,具體程式碼如下:
#將輸入的一個文件生成詞向量,當所輸入的文章中的詞在vocablist中出現時,
#就將其對應的向量設定為1,否則維持原來的不變
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

三. NB訓練函式

##樸素貝葉斯分類器的訓練函式
##輸入引數:trainMatrix表示輸入的文件矩陣,trainCategory表示每篇文件類別標籤 所構成的向量
def trainNB(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) #計算樣本集的樣本數
    numWords = len(trainMatrix[0]) #計算出詞彙表中的總詞數
    
    #計算p(c)的概率,這裡指的是分類為1的概率,因為求和之後為0的不考慮
    pAbusive = sum(trainCategory)/float(numTrainDocs) 
    p0Num = np.ones(numWords) #建立包含numWords個0的向量
    p1Num = np.ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        #統計每類單詞的數目,注意我們這裡討論的是一個二分問題  
        #所以可以直接用一個if...else...即可,如果分類較多,則需要更改程式碼 
        if trainCategory[i] == 1:
            #如果說歸為1類,那麼把這個訓練樣本的向量和原先已有的向量求和,
            #保證了同一位置(表示同一單詞)的數量的疊加,起到計數的作用
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom) #p1Num是一個向量,而怕p1Denom是一個數,除了之後計算得到的是每一個單詞出現在不同類別的概率
    p0Vect = np.log(p0Num/p0Denom)
    return p0Vect, p1Vect, pAbusive

針對演算法部分的改進

  1. 計算概率時,需要計算多個概率的乘積以獲得文件屬於某個類別的概率,即計算p(w0|ci)p(w1|ci)…p(wN|ci),然後當其中任意一項的值為0,那麼最後的乘積也為0.為降低這種影響,
    採用拉普拉斯平滑,在分子上新增a(一般為1),分母上新增ka(k表示類別總數),即在這裡將所有詞的出現數初始化為1,並將分母初始化為2*1=2

p0Num=ones(numWords);p1Num=ones(numWords)
p0Denom=2.0;p1Denom=2.0

  1. 解決下溢位問題

正如上面所述,由於有太多很小的數相乘。計算概率時,由於大部分因子都非常小,最後相乘的結果四捨五入為0,造成下溢位或者得不到準確的結果,所以,我們可以對成績取自然對數,
即求解對數似然概率。這樣,可以避免下溢位或者浮點數舍入導致的錯誤。同時採用自然對數處理不會有任何損失。
p0Vect=log(p0Num/p0Denom);p1Vect=log(p1Num/p1Denom)

四. NB分類函式

#樸素貝葉斯分類函式,對輸入的待分類的文件詞向量vec2Classify,給出分類結果
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0

五. 測試NB分類函式

#測試樸素貝葉斯分類函式
def testingNB():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts) #生成詞彙表
    trainMat = []
    for postingDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postingDoc))
    p0V, p1V, pAb = trainNB(np.array(trainMat),np.array(listClasses)) #訓練NB模型,並且得到p0V,p1V,pAb
    
    testEntry = ['love','my','dalmation']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, "classified as:", classifyNB(thisDoc,p0V,p1V,pAb))
    
    testEntry = ['stupid','garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, "classified as:", classifyNB(thisDoc,p0V,p1V,pAb))

之前的演算法我們只考慮了單詞出現與否,使用的是一種詞集模型。貝葉斯有兩種實現方式,另一種多項式模型,需要考慮每個單詞出現的次數,就是所謂的詞袋模型。為了適應這種詞袋模型,我們需要對函式setOfWords2Vec作一下修改:

#構造詞袋模型,每當遇到一個詞,會增加詞向量中的對應值,而不只是將對應的數值設為1
def bagOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

好了,至此我們完成了整個分類器的實現.接下來我們同樣適用一個例項,來測試我們的分類器。

六. 使用樸素貝葉斯進行垃圾郵件過濾

  1. 準備輸入資料:我們讀取兩個郵件資料夾中的內容,並 把其中的每行都切分成可處理的單詞。在檔案中繼續編輯如下程式碼,實現切分文字。
#檔案解析
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\\W*',bigString)
    return [tok.lower() for tok in listOfTokens if len(tok)>2]

  1. 有了可以使用的輸入資料型別,接下來我們就可以利用我們之前的演算法進行郵件過濾了,繼續編輯程式碼:
#垃圾郵件測試函式
def spamTest():
    docList=[]
    classList=[]
    fullText=[]
    for i in range(1,26): #每個資料夾下都是有25個文字文件,迴圈讀取25次
        wordList = textParse(open('email/spam/%d.txt' % i,encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        
        wordList = textParse(open('email/ham/%d.txt' %i,encoding="ISO-8859-1").read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList = createVocabList(docList)
     #range(50)表示從0到50,不包括50 
    trainingSet = range(50)
    testSet=[]
    #隨機選取10個作為測試集,剩下的作為訓練集
    for i in range(10):
        randIndex = int(np.random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(list(trainingSet)[randIndex])
    #訓練NB模型
    trainMat=[]
    trainClasses=[]
    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
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(np.array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:
            errorCount += 1
#            print("classification error:",docList[docIndex])
    print(errorCount)
    print("the error rate is:",float(errorCount)/len(testSet))
#    return vocabList, fullText
        

後記: