3. 樸素貝葉斯分類

樸素貝葉斯分類算法的核心是貝葉斯公式:


貝葉斯公式

‘樸素’二字的含義是指,某一特征取值的概率相對(duì)于其他特征的取值獨(dú)立,也就是實(shí)例的各個(gè)特征的分布相對(duì)獨(dú)立。
樸素貝葉斯分類器的另一個(gè)假設(shè)是,每個(gè)特征同等重要。

關(guān)于樸素貝葉斯講的清晰易懂的文章可見這里

樸素貝葉斯是用于文檔分類的常用算法,本章也是使用文檔分類來講解的。

  • 從文本中構(gòu)建詞向量
  • 使用詞向量訓(xùn)練分類器
  • 使用分類器來分類

1. 從文本中構(gòu)建詞向量

想要對(duì)文本進(jìn)行分類,一個(gè)問題是不同文本的長(zhǎng)度并不相同,該如何操作呢?可以先構(gòu)建一個(gè)詞典(詞匯表),其中包含n個(gè)詞。對(duì)于一篇文章將其表示為一個(gè)長(zhǎng)為n的詞典向量,其中值為1表示詞典中的對(duì)應(yīng)詞在文章中出現(xiàn)過(不論次數(shù)),0表示未出現(xiàn)。這樣對(duì)于每篇文章都可以得到一個(gè)對(duì)應(yīng)的詞向量,可用于訓(xùn)練分類器,以及進(jìn)行分類。

那么該如何構(gòu)造詞典呢?可以使用訓(xùn)練集,或者通過對(duì)大量文章的統(tǒng)計(jì)來構(gòu)建詞典。注意詞典中的詞都是唯一的。書中使用了一個(gè)小小的訓(xùn)練集來講解構(gòu)造詞典的過程。

"""
實(shí)驗(yàn)樣本,注意這里給的例子只是二分類問題。
"""
def loadDataSet():
    # 每一行為一個(gè)進(jìn)行了詞條切分的句子
    # 可用正則表達(dá)式來處理文本。
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is' , 'so', 'sute', '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
"""
根據(jù)輸入數(shù)據(jù)集構(gòu)造詞典,注意這里沒有排序
"""
# dataSet即為上面postingList的形式。
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        # 或,即為集合的并。。。
        vocabSet = vocabSet | set(document)
    return list(vocabSet)
>>> listOPosts, listClasses = loadDataSet()
# myVocabList就是根據(jù)數(shù)據(jù)集構(gòu)造好的詞典
# 其中的單詞是隨機(jī)排列的
>>> myVocabList = createVocabList(listOPosts)

下面代碼根據(jù)上面構(gòu)造好的詞典對(duì)于屬于文章構(gòu)造對(duì)應(yīng)的詞向量:

"""
根據(jù)構(gòu)造好的字典,對(duì)于輸入文檔,構(gòu)造該文檔的詞向量
"""
# vocabList為構(gòu)造好的詞典
# inputSet為進(jìn)行了詞條切分的文章,形式如postingList的各個(gè)行。
# 返回returnVec為對(duì)應(yīng)的詞向量。
def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            pirnt("the word: %s is not in my Vocabulary!"%word)
    return returnVec
# 得到第一個(gè)句子對(duì)應(yīng)的詞向量
>>> setOfWords2Vec(myVocabList, listOPosts[0])

2. 使用詞向量訓(xùn)練分類器

對(duì)于樸素貝葉斯分類器,訓(xùn)練分類器時(shí)到底是使分類器學(xué)習(xí)什么呢?
已知輸入實(shí)例為w,w=[w_1, w_2, w_3, ..., w_n](有n個(gè)特征,w_j為特征wj的取值),要預(yù)測(cè)其類別,即找到類別Ci,使P(Ci | w)最大。

而根據(jù)貝葉斯公式可得到:
P(Ci | w) = P(w | Ci ) * P(Ci) / P(w)。

又由于樸素貝葉斯的假設(shè),即各個(gè)特征的分布獨(dú)立,則可得到:
P(w | Ci) = P(w1 | Ci) * P(w2 | Ci) * P(w3 | Ci) * ... * P(wn | Ci)
也就是對(duì)于各個(gè)類別Ci,在該類別中,實(shí)例w各特征取值的概率。

P(w) = P(w1) * P(w2) * P(w3) * ... * P(wn)
也就是實(shí)例各特征取值的概率。

所以,學(xué)習(xí)的就是三個(gè)東西,對(duì)應(yīng)公式中的三項(xiàng)(注意,都是根據(jù)訓(xùn)練集來學(xué)習(xí)的這些概率,如果訓(xùn)練集分布越全面,那么學(xué)到的效果也就越好),知道了這三項(xiàng)就可以對(duì)新實(shí)例進(jìn)行分類:

  • P(wj | Ci) :j為特征索引(1<= j <= n),i即為類別索引。該概率也就是指,對(duì)于各個(gè)類別i,在該類別中,求出各特征j取各個(gè)值的概率。
  • P(Ci) :求訓(xùn)練集中各類別出現(xiàn)的概率,很簡(jiǎn)單,用各類別數(shù)量除以樣本數(shù)量即可。
  • P(wj) :就是特征j的各個(gè)取值,在樣本中出現(xiàn)的頻率。注意,該項(xiàng)與類別無關(guān),訓(xùn)練集確定了,該項(xiàng)的值也就確定了,所以在進(jìn)行預(yù)測(cè)時(shí)不需要計(jì)算該項(xiàng),只需計(jì)算上面兩項(xiàng)即可。

下面為書中給的訓(xùn)練分類器的代碼,只適用于二分類的問題,如果要用于多分類則需要重寫代碼嘍:

"""
該代碼針對(duì)應(yīng)于兩種特殊情況寫的:
1. 這是一個(gè)二分類問題,label為0或1.
2. 由于要對(duì)文本分類,且使用的是詞向量,那么每個(gè)特征的取值只有兩種:
1或0(該詞出現(xiàn)與否)。
由于都是0或1的取值,所有代碼中有取巧的部分。
如果是特征值有多種取值情況,或多分類,那么該代碼不適用了。
"""
import numpy as np
# trianMatrix為文檔矩陣,每一行都是詞向量
# trainCategory對(duì)應(yīng)于trainMatrix中每一行的類別
# 這里代碼為二分類,所以trainCategory為0、1組成的向量。
def trainNB0(trainMatrix, trainCategory):
    # 樣本數(shù)量
    numTrainDocs = len(trainMatrix)
    # 特征個(gè)數(shù)
    numWords = len(trainMatrix[0])
    # 類別1的數(shù)量/總樣本數(shù)量=類別1出現(xiàn)的概率
    pAbusive = np.sum(trainCategory) / float(numTrainDocs)
    # 用于存儲(chǔ)兩個(gè)類別下各個(gè)特征出現(xiàn)的次數(shù)。
    p0Num = np.zeros(numWords); p1Num = np.zeros(numWords)
    # 按理來說P(wj | Ci)為類別Ci時(shí),特征wj取各個(gè)值的概率
    # 應(yīng)等于wj為某一特征值的數(shù)量處理該類別Ci中樣本數(shù)量的。
    # 但這里的做法是除以所有單詞出現(xiàn)的次數(shù)了,這樣條件概率的值就小得很多了。
    p0Denom = 0.0; p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 由于詞向量為0、1組成的向量,所以直接向量相加可得該
            # 類別下某一特征值出現(xiàn)的次數(shù)(詞出現(xiàn)的次數(shù))。
            p1Num += trainMatrix[i]
            # 這里p1Denom為類別1時(shí)所有詞出現(xiàn)的次數(shù)的和。
            # 然后用pqNum / p1Denom。
            # 我感覺這是不對(duì)的,應(yīng)改為p1Denom += 1
            # 使p1Denom統(tǒng)計(jì)類別1時(shí)的樣本數(shù)量,因?yàn)楦魈卣髦抵挥?            # 兩種取值,所以這樣pqNum / p1Denom就為類別1時(shí)
            # 特征值為1的頻率(也就是該詞出現(xiàn)的頻率)。
            p1Denom += np.sum(trainMatrix[i])
        else:
            # 同上,不過是類別0
            p0Num += trainMatrix[i]
            p0Denom += np.sum(trainMatrix[i])
    
    # 如上注釋所示。
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    # p0Vect各列應(yīng)為類別0時(shí),各特征為1的概率(也就是各詞出現(xiàn)的概率)
    return p0Vect, p1Vect, pAbusive

文中對(duì)上述代碼的改進(jìn)有下面兩點(diǎn):

  • 由于在進(jìn)行預(yù)測(cè)時(shí),進(jìn)行的計(jì)算P(w | Ci) = P(w1 | Ci) * P(w2 | Ci) * P(w3 | Ci) * ... * P(wn | Ci),當(dāng)特征過多時(shí),很容易造成下溢出問題。所以這里應(yīng)對(duì)p0Vect和p1Vect取log,那么就變?yōu)?
    log(P(w | Ci) * P(Ci)) = log(P(w | Ci) ) + log(Ci))
    log(P(w | Ci) )= log(P(w1 | Ci)) + log(P(w2 | Ci)) + log(P(w3 | Ci)) + ... + log(P(wn | Ci))

  • 另一點(diǎn),對(duì)于某一i和j,P(wj | Ci)可能為0,也就是在該類別中該詞從未出現(xiàn)過。這很正常,如罵人的詞就只能出現(xiàn)在臟話中。而對(duì)0取log會(huì)得到負(fù)無窮,這可不行,所以修改了上面代碼,假設(shè)每個(gè)詞在每一類中都出現(xiàn)過一次:p0Num = np.ones(numWords); p1Num = np.ones(numWords)
    文中的另一項(xiàng)修改是p0Denom = 2.0; p1Denom = 2.0,這我就沒看懂了,感覺改的毫無意義。。。

以上就為訓(xùn)練過程了,很簡(jiǎn)單,對(duì)于文本的二分類是適用的。更復(fù)雜的分類就要重寫代碼了。過程不重要,重要的是思路。

3. 使用分類器來分類

"""
很簡(jiǎn)單,思路就如上所示。
"""
# vec2Classify為待分類的詞向量
# p0Vec和p1Vec各列為條件概率log(P(wj | Ci))
# pClass1為P(C1)
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # log(P(w | C1) * P(C1))
    p1 = np.sum(vec2Classify * p1Vec) + np.log(pClass1)
    # log(P(w | C2) * P(C2))
    p0 = np.sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容