1. 程式人生 > >我的第一篇學習筆記——使用樸素貝葉斯演算法對文件分類詳解

我的第一篇學習筆記——使用樸素貝葉斯演算法對文件分類詳解

樸素貝葉斯演算法可以實現對文件的分類,其中最著名的應用之一就是過濾垃圾郵件。先做一個簡單的分類,以論壇的留言為例,構建一個快速的過濾器,來區分哪些留言是負面言論,哪些是正面言論。

我對演算法思路的理解:首先計算訓練集中每個詞語分別在正面(負面)文件中出現的概率以及正面(負面)文件的概率,再計算待分類樣本中的每個詞語屬於正面(負面)文件的概率和正面(負面)文件概率的乘積,即為該樣本屬於正面(負面)樣本的概率,樣本屬於哪一類文件的概率較大就歸為哪類文件(讀著有點繞),下面詳細介紹分類的過程。

1. 條件概率

首先來學習一下基於條件概率的分類思想。對於樣本A,它屬於類別c_1的概率為P(c_1 |A),屬於樣本c_2的概率為P(c_2 |A)

,定義貝葉斯分類準則為:

  • 如果P(c_1 |A)>P(c_2 |A),那麼樣本A屬於類別c_1
  • 如果P(c_1 |A)<P(c_2 |A),那麼樣本A屬於類別c_2

完整的貝葉斯公式如下:

P(B_i |A)=P(A|B_i )P(B_i )/(\sum_{j=1}^{n}P(A|B_j )P(B_j ) ) \: \:\: \: \, \, \, \, i=1,2,...,n

在此分類演算法中,我們用它的簡化形式:

P(c_1 |A)=P(A|c_1 )P(c_1 )/P(A)

用分類的思想可以這樣理解這個公式:A是待分類樣本的特徵集合,那麼要求得A屬於類別c_1的概率,就轉化為求訓練集中,類別c_1的樣本集中特徵集A的概率、類別c_1的概率、特徵集A的概率,這3個值通過訓練集資料可以更容易計算得出。

2. 準備資料——構建詞袋模型

對每個文字,構建一個向量,向量的維度與詞條列表的維度相同,向量的值是詞條列表中每個詞條在該文字中出現的次數,這種模型叫做詞袋模型。使用Python程式設計實現,程式碼來源《機器學習實戰》,建立bayes.py檔案,加入如下程式碼:

# 載入資料集
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 bagOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            return Vec[vocabList.index(word)] += 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)

    return returnVec

看一下執行效果:

>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> myVocabList
['steak', 'dalmation', 'garbage', 'love', 'cute', 'please', 'help', 'stop', 'buying', 'worthless', 
'ate', 'dog', 'to', 'I', 'food', 'is', 'how', 'not', 'mr', 'quit', 'flea', 'licks', 'problems', 
'take', 'so', 'park', 'has', 'my', 'posting', 'stupid', 'maybe', 'him']
>>> bayes.bagOfWords2Vec(myVocabList, listOPosts[0])
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0]
>>> bayes.bagOfWords2Vec(myVocabList, listOPosts[1])
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1]

詞條列表中索引為5的詞語是please,它在第一個文件中出現過1次,最後一個詞語是him,它在第二個文件中出現過1次。

3. 訓練演算法——從詞向量計算概率

現在我們知道一個詞是否出現在一篇文件中,也知道文件所屬的類別。為了方便理解,把條件概率公式中的A替換為\boldsymbol{w}(代表詞向量)

P(c_i |\boldsymbol{w})=P(\boldsymbol{w}|c_i )P(c_i )/P(\boldsymbol{w})

\boldsymbol{w}=[w_0,w_1,w_2,...,w_n]

之所以稱為“樸素”貝葉斯,是因為假設樣本中的所有特徵是相互獨立的,那麼就有如下簡化的計算方法:

P(\boldsymbol{w}|c_i )=P(w_0 |c_i )P(w_1 |c_i )P(w_2 |c_i )...P(w_n |c_i )

其中,P(w_k |c_i )表示在第i類樣本中,第k個詞語出現的概率;

P(c_{i})表示第i類樣本所佔的概率,如示例中有6個樣本,正負樣本各3個,因此P(c_0 )P(c_1 )均為0.5;

P(\boldsymbol{w})同樣可以按P(\boldsymbol{w}|c_i )的方法求得,但是因為我們只需比較P(c_0 |\boldsymbol{w})P(c_1 |\boldsymbol{w})的大小來進行分類,作為相同的分母,P(\boldsymbol{w})在程式碼中沒有計算。

因為要使用數值計算類庫Numpy的一些函式,需要在檔案的最上面新增引用語句:from numpy import *

# 樸素貝葉斯分類器訓練函式,引數為樣本矩陣和類別矩陣
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # 樣本數
    numWords = len(trainMatrix[0])  # 詞條數
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 計算類別為1 的概率
    p0Num = zeros(numWords)
    p1Num = zeros(numWords)
    p0Denom = 0.0
    p1Denom = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:  # 類別為1 的文件
            p1Num += trainMatrix[i]  # 類別為1 的詞條矩陣
            p1Denom += sum(trainMatrix[i])  # 類別為1 的文件的總詞數
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num / p1Denom  # 類別為1 的文件中,每個詞出現的概率
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive

測試一下效果:

>>> import bayes
>>> listOPosts, listClasses = bayes.loadDataSet()
>>> myVocabList = bayes.createVocabList(listOPosts)
>>> trainMat = []
>>> for postinDoc in listOPosts:
...     trainMat.append(bayes.bagOfWords2Vec(myVocabList, postinDoc))
...     
>>> p0V, p1V, pAb = bayes.trainNB0(trainMat, listClasses)
>>> p0V
array([0.04166667, 0.04166667, 0.        , 0.04166667, 0.04166667,
       0.04166667, 0.04166667, 0.04166667, 0.        , 0.        ,
       0.04166667, 0.04166667, 0.04166667, 0.04166667, 0.        ,
       0.04166667, 0.04166667, 0.        , 0.04166667, 0.        ,
       0.04166667, 0.04166667, 0.04166667, 0.        , 0.04166667,
       0.        , 0.04166667, 0.125     , 0.        , 0.        ,
       0.        , 0.08333333])
>>> p1V
array([0.        , 0.        , 0.05263158, 0.        , 0.        ,
       0.        , 0.        , 0.05263158, 0.05263158, 0.10526316,
       0.        , 0.10526316, 0.05263158, 0.        , 0.05263158,
       0.        , 0.        , 0.05263158, 0.        , 0.05263158,
       0.        , 0.        , 0.        , 0.05263158, 0.        ,
       0.05263158, 0.        , 0.        , 0.05263158, 0.15789474,
       0.05263158, 0.05263158])
>>> pAb
0.5

負面樣本的概率pAb為0.5,與前面計算的相同。正面樣本中共有23個詞語,負面樣本中共有19個詞語,詞條列表中的第一個詞為steak,它在正面樣本中出現一次,在負面樣本中沒有出現,該詞的條件概率為分別為0.04166667和0,計算正確;倒數第3個詞語是stupid,它在正面樣本中沒有出現,在負面樣本中出現3次,該詞的條件概率分別是0和0.15789474,計算正確。

至此,我們已經求得每個詞語在某類文件中的條件概率:

P(w_i |c_0 )=p0V[i]P(w_i |c_1 )=p1V[i]

P(c_0 )=1- pAbP(c_1 )= pAb

在分類之前,有兩個地方需要優化:

(1)根據公式P(\boldsymbol{w}|c_i )=P(w_0 |c_i )P(w_1 |c_i )P(w_2 |c_i )...P(w_n |c_i ),如果其中有一個詞語的條件概率為0,那麼計算結果就是0,為了避免這種情況,將所有詞出現的次數初始化為1,分母初始化為2。

(2) 多個概率非常小的值相乘,最後四捨五入可能會得到0,可以採用求對數的方法來避免這種現象。

修改trainNB0()方法的第6到9行,和return前的2行,修改後的程式碼:

# 樸素貝葉斯分類器訓練函式,引數為樣本矩陣和類別矩陣
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # 樣本數
    numWords = len(trainMatrix[0])  # 詞條數
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 計算類別為1 的概率
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:  # 類別為1 的文件
            p1Num += trainMatrix[i]  # 類別為1 的詞條矩陣
            p1Denom += sum(trainMatrix[i])  # 類別為1 的文件的總詞數
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)  # 類別為1 的文件中,每個詞出現的概率
    p0Vect = log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive

4. 測試演算法——利用分類器對樣本分類

有了前面的準備工作,現在可以構建分類器了,分類器classifyNB有4個引數,依次是待分類的向量,以及trainNB0計算的3個概率值。再寫一個分類的測試函式testingNB,避免每次測試都要輸入測試程式碼。

# 樸素貝葉斯分類函式
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 postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    p0v, p1v, pAb = trainNB0(trainMat, listClasses)

    testEntry = ['love', 'my', 'dalmation']
    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))

看看實際效果,很明顯‘love’一類的文字被歸為正面言論,‘stupid’、‘garbage’被歸為負面言論。

>>> import bayes
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1

以上就是我對樸素貝葉斯演算法的學習和理解,理論和程式碼主要來自《機器學習實戰》,並嘗試用更容易理解的表述和公式加以說明,不足之處歡迎探討。