1. 程式人生 > >樸素貝葉斯演算法優化與 sklearn 實現

樸素貝葉斯演算法優化與 sklearn 實現

1. 引言

上一篇日誌中,我們主要介紹了貝葉斯演算法,並提供了 python 實踐:
樸素貝葉斯演算法的推導與實踐

但執行上一篇日誌中的示例,我們發現出現了下面的結果:

['love', 'my', 'dalmation'] 屬於非侮辱類
['stupid', 'garbage'] 屬於非侮辱類

這顯然是不正確的,本文,我們就來解決這個問題,同時對演算法進行優化並使用 sklearn 來實現演算法的實踐。

2. 拉普拉斯平滑

上一篇文章中,我們利用貝葉斯分類器對文件進行分類時,需要算多個概率的乘積以獲得文件屬於某個類別的概率,即計算 p(w0|1)  p(w1|1) 

 p(w2|1),只要有一個概率值為0,那麼最終的結果就會隨之變成 0,這就是上一篇文章中,演算法執行結果兩個測試用例都是非侮辱類的原因。
要降低這種影響,可以講所有詞的出現數初始化為 1,並將分母初始化為 2,這個做法就是拉普拉斯平滑。
我們將上一篇日誌中程式碼的 trainNB0 方法中的 p0Num、p1Num、p0Denom、p1Denom 賦值語句改為:

p0Num = np.ones(vocabularysNum)
p1Num = np.ones(vocabularysNum)
p0Denom = 2.0
p1Denom =
2.0

3. 下溢位問題的解決

進行拉普拉斯平滑運算後,我們執行程式,仍然得出了兩個測試樣本均屬於非侮辱類的結果,這是為什麼呢?
我們檢視最終計算出的 p0 和 p1 會發現,他們的結果都是 0,這又是為什麼呢?
這是因為出現了另一個問題 – 下溢位。
我們的概率運算中,所有參與運算的概率都太小了,小數相乘會使運算的積進一步減小,最終結果向下溢位超出了計算機浮點數的精度,就都會變成 0。
解決辦法很自然的可以想到 – 將乘法運算轉換為加法運算,但如何在保證演算法正確性的前提下進行轉換呢?
在代數中,ln(a * b) = ln(a) + ln(b),同時,自然對數可以保證運算趨勢的正確性:
#此處有圖片

因此我們通過對數運算優化訓練函式 trainNB0 與測試函式 classifyNB:

def trainNB0(trainMap, results):
    """
    樸素貝葉斯分類器訓練函式

    :param trainMap: 訓練文件矩陣
    :param results: 訓練類別標籤向量
    :return:
        p0Vect - 侮辱類的條件概率陣列
        p1Vect - 非侮辱類的條件概率陣列
        pAbusive - 文件屬於侮辱類的概率
    """

    dataListNum = len(trainMap)
    vocabularysNum = len(trainMap[0])

    """ 計算文件屬於侮辱詞概率 """
    pAbusive = sum(results) / float(dataListNum)

    p0Num = np.ones(vocabularysNum)
    p1Num = np.ones(vocabularysNum)
    p0Denom = 2.0
    p1Denom = 2.0

    """ 將所有行按是否是侮辱類分別疊加,統計各個詞出現的次數 """
    for i in range(dataListNum):
        if results[i] == 1:
            p1Num += trainMap[i]
            p1Denom += sum(trainMap[i])
        else:
            p0Num += trainMap[i]
            p0Denom += sum(trainMap[i])

    """ 計算概率 """
    p1Vect = np.log(p1Num / p1Denom)
    p0Vect = np.log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    樸素貝葉斯分類器分類函式

    :param vec2Classify: 待分類的詞條陣列
    :param p0Vec: 侮辱類的條件概率陣列
    :param p1Vec: 非侮辱類的條件概率陣列
    :param pClass1: 文件屬於侮辱類的概率
    :return: 是否屬於侮辱類,0. 不屬於,1. 屬於
    """
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    print("p0: ", p0)
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
    print("p1: ", p1)
    if p1 > p0:
        return 1
    else:
        return 0

最終我們得到了正確的結果:

p0:  -7.694848072384611
p1:  -9.826714493730215
['love', 'my', 'dalmation'] 屬於非侮辱類
p0:  -7.20934025660291
p1:  -4.702750514326955
['stupid', 'garbage'] 屬於侮辱類

4. 樸素貝葉斯演算法的優缺點

通過上一篇日誌的介紹和本文的優化,我們瞭解了樸素貝葉斯演算法的原理和應用,他是一種基於概率的分類器演算法,可以用來處理不相干因子的多分類問題,例如根據詞頻進行文字分類等問題。
那麼他又具有哪些優缺點呢?

4.1. 優點

  1. 演算法原理和實現簡單,通過概率分類
  2. 對小規模資料表現很好,適合多分類增量式訓練任務

4.2. 缺點

  1. 對輸入資料的表達形式很敏感
  2. 需要計算先驗概率,分類決策存在錯誤率
  3. 要求樣本之間相互獨立,這就是“樸素”的意思,這個限制有時很難做到,或使用者誤以為符合而造成錯誤的結果

5. 使用 sklearn 實現樸素貝葉斯演算法

sklearn 提供了樸素貝葉斯演算法的實現類 – sklearn.naive_bayes.MultinomialNB。
下面的列表中,我們將分類數稱為 nc,將特徵數稱為 nf。

5.1. 構造引數

sklearn.naive_bayes.MultinomialNB 類構造引數

引數名 型別 可選引數 預設值 說明
alpha float 非負浮點數 1 拉普拉斯平滑係數
fit_prior boolean True/False True 是否使用先驗分類概率
class_prior array None 或array(nc*1) None 如果指定 fit_prior 為 True,該引數用來提供先驗概率

5.2. 類屬性

sklearn.naive_bayes.MultinomialNB 類屬性

屬性名 型別 說明
class_log_prior_ array(nc*1) 每個分類的平滑對數先驗概率
intercept_ array(nc*1) 將多項式樸素貝葉斯理解為線性模型時,與 class_log_prior_ 相同
feature_log_prob_ array(nc*nf) 每個分類的每個特徵的對數先驗概率(P(x_i|y))
coef_ array(nc*nf) 將多項式樸素貝葉斯理解為線性模型時,與 feature_log_prob_ 相同
class_count_ array(nc*1) 在擬合過程中每個分類的樣本數
feature_count_ array(nc*nf) 在擬合過程中每個分類的每個特徵的樣本數

5.3. 類方法

  • fit(X, y[, sample_weight]) – 訓練樸素貝葉斯模型
  • get_params([deep]) – 獲取引數
  • set_params(**params) – 設定引數
  • partial_fit(X, y[, classes, sample_weight]) – 部分樣本上的增量擬合
  • predict(X) – 預測
  • predict_log_proba(X) – 返回測試向量X的對數概率估計
  • predict_proba(X) – 返回測試向量X的概率估計
  • score(X, y[, sample_weight]) – 返回模型的平均精度

5.4. 示例

import numpy as np
from sklearn.naive_bayes import MultinomialNB

if __name__ == '__main__':
    X = np.random.randint(5, size=(6, 100))
    y = np.array([1, 2, 3, 4, 5, 6])
    clf = MultinomialNB()
    clf.fit(X, y)
    print(clf.predict(X[2:3]))

上面的示例,我們通過隨機數建立了一個 6*100 的矩陣,其中每個元素都是0到5的隨機數,我們用這個矩陣的每一行分別對應 1、2、3、4、5、6,最終,我們用第三行來測試這個模型,果然得到了預期的數字:3。

6. 後記

對於相互獨立的樣本來說,樸素貝葉斯是一個非常不錯的分類器,在自然語言處理和文字特徵分析、過濾等領域有著廣泛的應用。
事實上,樸素貝葉斯共有三種模型,他們的區別在於計算條件概率的公式不同:

  1. 高斯樸素貝葉斯 – 用於符合高斯分佈(正態分佈)的連續樣本資料的分類
  2. 多項式樸素貝葉斯 – 我們已經介紹的內容就是多項式樸素貝葉斯模型
  3. 伯努利樸素貝葉斯 – 每個特徵的取值為0或1,即計算特徵是否存在的概率,他是唯一將樣本中不存在的特徵也引入計算概率的樸素貝葉斯模型

7. 參考資料

Peter Harrington 《機器學習實戰》。
李航 《統計學習方法》。
https://zh.wikipedia.org/wiki/樸素貝葉斯分類器。
https://scikit-learn.org/dev/modules/generated/sklearn.naive_bayes.MultinomialNB.html。