機器學習之貝葉斯分類(python實現)

swensun發表於2018-02-27

樸素貝葉斯(Naive Bayesian)是最為廣泛使用的分類方法,它以概率論為基礎,是基於貝葉斯定理和特徵條件獨立假設的分類方法。

原理

樸素貝葉斯(Naive Bayesian)是基於貝葉斯定理和特徵條件獨立假設原則的分類方法。通過給出的特徵計算分類的概率,選取概率大的情況進行分類。也是基於概率論的一種機器學習分類方法。分類目標確定,屬於監督學習。

通過概率來衡量事件發生的可能性。概率論和統計學恰好是兩個相反的概念,統計學是抽取部分樣本進行統計來估算總體的情況,而概率論是通過總體情況來估計單個事件或者部分事情的發生情況。因此,概率論需要已知的資料去預測未知的事件。 
例如,我們看到天氣烏雲密佈,電閃雷鳴並陣陣狂風,在這樣的天氣特徵(F)下,我們推斷下雨的概率比不下雨的概率大,也就是p(下雨)>p(不下雨),所以認為待會兒會下雨。這個從經驗上看對概率進行判斷。 
而氣象局通過多年長期積累的資料,經過計算,今天下雨的概率p(下雨)=85%,p(不下雨)=15%,同樣的,p(下雨)>p(不下雨),因此今天的天氣預報肯定預報下雨。這是通過一定的方法計算概率從而對下雨事件進行判斷。
複製程式碼

為什麼叫樸素貝葉斯:簡單,易於操作,基於特徵獨立性假設,也即各個特徵彼此獨立,互相不影響發生。

條件概率

某個事件已發生的情況下另外一個事件發生的概率。計算公式如下:P(A|B)=P(A∩B) / P(B) 簡單理解:畫維恩圖,兩個圓圈相交的部分就是A發生B也發生了,因為求的是B發生下A發生的概率。B相當於一個新的樣本空間。AB/B即可。

概率相乘法則:P(A∩B)=P(A)P(B|A) or P(A∩B)=P(B)P(A|B) 獨立事件的概率:P(A∩B)=P(A)P(B)

貝葉斯定理

如果有窮k個互斥事件,B1, B2,,,Bk 並且 P(B1)+P(B2)+⋅⋅⋅+P(Bk)=1和一個可以觀測到的事件A,那麼有:

image.png

分類原理

基於概率論,二分類問題如下: 如果p1 > p2, 分入類別1; 否則分入類別2。

其次,貝葉斯定理,有 p(ci|x,y) = p(x,y|ci) * p(ci) / p(x,y) x, y 表示特徵變數,如下例子中的單詞。Ci表示類別。p(ci | x, y) 即表示在特徵x, y出現的情況下,分入類別Ci的概率。結合如上: p(ci | x, y) > p(cj | x, y), 分入類別i, 否則分入類別j。

貝葉斯定理最大的好處是可以用已知的三個概率去計算未知的概率,而如果僅僅是為了比較p(ci|x,y)和p(cj|x,y)的大小,只需要已知兩個概率即可,分母相同,比較p(x,y|ci)p(ci)和p(x,y|cj)p(cj)即可。

特徵條件獨立性假設原則

樸素貝葉斯常用與對文件分類。根據文件中出現的詞彙,判斷文章屬於什麼類別。將詞彙出現的特徵條件用詞向量W表示,由多個值組成,值的個數和訓練集中的詞彙表個數相同。 上面的貝葉斯公式可以表示為: p(ci|ω)=p(ω|ci) * p(ci) / p(ω) 各個單詞的出現不會相互影響,則p(ω|ci) = p(ω0|ci)*p(ω1|ci)*...* p(ωk|ci)

演算法實現

import numpy as np
np.seterr(divide='ignore', invalid='ignore')  #消除向量中除以0的警告
# 獲取資料
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']]
    classVec = [0, 1, 0, 1, 0, 1] #1表示侮辱性言論,0表示正常
    return postingList, classVec
複製程式碼

根據文件詞彙構建詞向量:

def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 對輸入的詞彙表構建詞向量
def setOfWords2Vec(vocabList, inputSet):
    returnVec = np.zeros(len(vocabList)) #生成零向量的array
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1 #有單詞,該位置填充1
        else:
            print("the word: %s is not in my Vocabulary" % word)
            # pass
    return returnVec  #返回0,1的向量

if __name__ == '__main__':
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    print(myVocabList)
 
複製程式碼

輸出結果如下: ['flea', 'ate', 'how', 'licks', 'quit', 'problems', 'dog', 'I', 'garbage', 'help', 'is', 'cute', 'steak', 'to', 'worthless', 'please', 'has', 'posting', 'buying', 'love', 'food', 'so', 'my', 'take', 'dalmation', 'stop', 'park', 'not', 'stupid', 'him', 'mr', 'maybe'], 表示不同類別言論去重後得到的詞向量。 [ 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]: 表示詞彙集1中的單詞是否在詞向量中出現。

如上,這個方法只記錄了每個詞是否出現,並沒有記錄出現次數,成為詞集模型。如果記錄詞出現的次數,這樣的詞向量構建方法稱為詞袋模型,如下。本文只使用詞集模型。

# 詞袋模型
def bagofWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return vocabList #返回非負整數的詞向量
複製程式碼

運用詞向量計算概率:

def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  #文件數目
    numWord = len(trainMatrix[0])  #詞彙表數目
    print(numTrainDocs, numWord)
    pAbusive = sum(trainCategory) / len(trainCategory) #p1, 出現侮辱性評論的概率 [0, 1, 0, 1, 0, 1]
    p0Num = np.zeros(numWord)
    p1Num = np.zeros(numWord)

    p0Demon = 0
    p1Demon = 0

    for i in range(numTrainDocs):
        if trainCategory[i] == 0:
            p0Num += trainMatrix[i] #向量相加
            p0Demon += sum(trainMatrix[i]) #向量中1累加其和
        else:
            p1Num += trainMatrix[i]
            p1Demon += sum(trainMatrix[i])
    p0Vec = p0Num / p0Demon
    p1Vec = p1Num / p1Demon

    return p0Vec, p1Vec, pAbusive

if __name__ == '__main__':
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    trainMat = []
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    print(trainMat)
    p0Vec, p1Vec, pAbusive = trainNB0(trainMat, listClasses)
    print(p0Vec, p1Vec, pAbusive)
複製程式碼

輸出結果稍微有點多,慢慢來看: trainMat:表示資料中六個給定的特徵在詞集模型中的出現情況。

array([ 0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  1.,  1.,  0.,
        0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  1.,  1.]), array([ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,
        0.,  1.,  0.,  1.,  1.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,
        0.,  0.,  1.,  0.,  0.,  0.]), array([ 1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,
        1.,  0.,  0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,
        1.,  1.,  0.,  0.,  0.,  1.]), array([ 0.,  1.,  1.,  1.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.]), array([ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,
        0.,  0.,  0.,  1.,  0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,
        0.,  0.,  1.,  1.,  0.,  1.]), array([ 0.,  0.,  1.,  0.,  1.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  1.,
        0.,  0.,  0.,  0.,  0.,  0.])]
複製程式碼

print(numTrainDocs, numWord): 6 32 (6個文件,一共32個詞彙) print(p0Vec, p1Vec, pAbusive):pAbusive是文件中是侮辱性言論的概率,為0.5。 而p0Vec表示類別0(非侮辱言論)中的詞在詞向量中出現的概率:

[ 0.  0.04166667  0.04166667  0.04166667  0.04166667  0.
  0.08333333  0.04166667  0.          0.04166667  0.          0.04166667
  0.          0.04166667  0.          0.          0.04166667  0.04166667
  0.04166667  0.04166667  0.04166667  0.          0.          0.04166667
  0.04166667  0.04166667  0.          0.125       0.          0.04166667
  0.04166667  0.04166667] 
複製程式碼

演算法的改進:

  1. 部分概率為0,用於上面計算獨立特徵概率相乘是永遠為0.因此,將所有詞出現的次數初始化為1,某類詞項初始化為2.
  2. 由於計算得到的概率太小,不斷的相乘可能會導致結果溢位。因此對其取對數,單調性相同,不會影響最後對結果的比較。函式如下:
def trainNB1(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  #文件數目
    numWord = len(trainMatrix[0])  #詞彙表數目
    pAbusive = sum(trainCategory) / len(trainCategory) #p1, 出現侮辱性評論的概率
    p0Num = np.ones(numWord)  #修改為1
    p1Num = np.ones(numWord)

    p0Demon = 2 #修改為2
    p1Demon = 2

    for i in range(numTrainDocs):
        if trainCategory[i] == 0:
            p0Num += trainMatrix[i] #向量相加
            p0Demon += sum(trainMatrix[i]) #向量中1累加其和
        else:
            p1Num += trainMatrix[i]
            p1Demon += sum(trainMatrix[i])
    p0Vec = np.log(p0Num / p0Demon)  #求對數
    p1Vec = np.log(p1Num / p1Demon)

    return p0Vec, p1Vec, pAbusive
複製程式碼

注意:這裡得到p0Vec可能是沒有規律的,但其對最後的概率比較沒有影響。

運用分類器函式進行文件分類

def classifyNB(vec2Classify, p0Vc,  p1Vc, pClass1):
    p1 = sum(vec2Classify * p1Vc) * pClass1
    p0 = sum(vec2Classify * p0Vc) * (1-pClass1)
    # p1 = sum(vec2Classify * p1Vc) + np.log(pClass1)    #取對數,防止結果溢位
    # p0 = sum(vec2Classify * p0Vc) + np.log(1 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
複製程式碼

解釋一下:vec2Classify是所需分類文件的詞量。根據公式 p(ci|ω)=p(ω|ci)p(ci) / p(ω), 已知特徵向量求分類的概率等於 p(ω|ci)p(ci)。忽略分母:

p(ci)好求,用樣本集中,ci的數量/總樣本數即可 
p(ω|ci)由於各個條件特徵相互獨立且地位相同,`p(ω|ci)=p(w0|ci)p(w1|ci)p(w2|ci)......p(wN|ci)`,可以分別求p(w0|ci),p(w1|ci),p(w2|ci),......,p(wN|ci),從而得到p(ω|ci)。  
而求p(ωk|ci)也就變成了求在分類類別為ci的文件詞彙表集合中,單個詞項ωk出現的概率。
複製程式碼

測試分類函式

使用兩個不同的樣本來測試分類函式:


# 構造樣本測試
def testingNB():
    listPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listPosts)
    trainMat = []
    for postinDoc in listPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0v, p1v, pAb = trainNB0(trainMat, listClasses)
    # print(p0v, p1v, pAb)
    testEntry = ['love']
    thisDoc = setOfWords2Vec(myVocabList, testEntry)
    print(testEntry, 'classified as', classifyNB(thisDoc, p0v, p1v, pAb))

    testEntry = ['stupid', 'garbage']
    thisDoc = (setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as:', classifyNB(thisDoc, p0v, p1v, pAb))

if __name__ == '__main__':
    testingNB()
複製程式碼

觀察結果,可以看到將兩個文件正確的分類。 完整程式碼請檢視:

github:naive_bayes

總結

  • 樸素貝葉斯分類
  • 條件概率
  • 貝葉斯定理
  • 特徵條件獨立性假設原則
  • 根據文件構建詞向量
  • 詞集模型和詞袋模型
  • 概率為0,方便計算的改進和防止溢位的取對數改進

參考文章:
機器學習之樸素貝葉斯(NB)分類演算法與Python實現

相關文章