1. 程式人生 > >樸素貝葉斯演算法的推導與實踐

樸素貝葉斯演算法的推導與實踐

1. 概述

在此前的文章中,我們介紹了用於分類的演算法:
k 近鄰演算法
決策樹的構建演算法 – ID3 與 C4.5 演算法
但是,有時我們無法非常明確地得到分類,例如當資料量非常大時,計算每個樣本與預測樣本之間的距離或是構建決策樹都會因為運算量過大而力不從心。

2. 樸素貝葉斯理論

#此處有圖片

假設我們有上面這個資料集,那麼我們如何通過一個新的座標預測新座標應該屬於哪個類別呢?
我們有下面三種方法:

  1. 使用 KNN 演算法 – 進行 1000 次距離計算
  2. 使用決策樹演算法 – 分別沿X軸、Y軸劃分資料
  3. 計算新的點屬於每個類別的概率,並進行比較
    顯然,最佳的方法是方法3。

2.1. 貝葉斯概率

通常我們所說的概率指的是“頻數概率”,不需要進行邏輯推理。
貝葉斯概率引入先驗知識,通過邏輯推理來處理不確定性命題。

3. 條件概率

#此處有圖片

上面這幅維恩圖中,我們可以清楚的看到,在事件 B 發生的情況下,事件 A 發生的概率就是 P(A∩B) 除以 P(B):
#此處有圖片

因此:
#此處有圖片

同理:
#此處有圖片

所以:
#此處有圖片

也就是:
#此處有圖片

最後這個公式就是條件概率公式。

4. 

我們把P(A)稱為“先驗概率”,即在B事件發生之前,我們對A事件概率的一個判斷。
P(A|B)稱為“後驗概率”,即在B事件發生之後,我們對A事件概率的重新評估。
P(B|A)/P(B)稱為“可能性函式”,這是一個調整因子,使得預估概率更接近真實概率。

因此:

  • 後驗概率 = 先驗概率 * 調整因子
    這就是貝葉斯推斷。

5. 樸素貝葉斯推斷

P(A|X) 表示 X 條件下 A 事件發生的概率,那麼假設 X 具有 n 個特徵,那麼:
#此處有圖片

如果 n 個特徵相互獨立,那麼可以進一步推導:
#此處有圖片

這個公式就是樸素貝葉斯推斷,而他基於的基本假設:所有特徵相互獨立,就是條件獨立性假設。

6. 樸素貝葉斯公式的應用

假設我們統計一個門診的接診情況如下:

門診接診情況

症狀 職業 疾病
打噴嚏 護士 感冒
打噴嚏 農夫 過敏
頭痛 建築工人 腦震盪
頭痛 建築工人 感冒
打噴嚏 教師 感冒
頭痛 教師 腦震盪

現在來了第七個病人,他是一個打噴嚏的建築工人,那麼如何計算他患感冒的概率呢?

6.1. 計算

根據樸素貝葉斯公式,我們可以求得:
#此處有圖片

即:
#此處有圖片

7. 通過 python 實現樸素貝葉斯演算法

下面是一個預測一行文字是否是負面侮辱性語言的例子。
如果我們認為語句中,每個詞出現的概率都是獨立的,那麼我們就可以應用樸素貝葉斯公式來計算給定的語句的分類概率了。

7.1. 程式示例

# -*- coding: UTF-8 -*-
# {{{
import numpy as np
from functools import reduce


def loadDataSet():
    """
    建立實驗樣本
    :return:
        postingList - 實驗樣本切分的詞條
        classVec - 類別標籤向量
    """
    dataSet = [['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']]
    results = [0, 1, 0, 1, 0, 1]  # 類別標籤向量,是否是侮辱性,0. 否,1. 是
    return dataSet, results  # 返回實驗樣本切分的詞條和類別標籤向量


def createVocabList(dataSet):
    """
    獲取去重後的詞彙表

    :param dataSet:
    :return:
    """
    vocabSet = set()
    for document in dataSet:
        vocabSet |= set(document)
    return list(vocabSet)


def wordsToVector(dataList, vocabularys):
    """
    將原始資料向量化,向量的每個元素為1或0

    :param vocabularys: createVocabList返回的列表
    :param dataList: 切分的詞條列表
    :return: 文件向量,詞集模型
    """
    vector = [0] * len(vocabularys)
    for word in dataList:  # 遍歷每個詞條
        if word in vocabularys:  # 如果詞條存在於詞彙表中,則置1
            vector[vocabularys.index(word)] = 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return vector  # 返回文件向量


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.zeros(vocabularysNum)
    p1Num = np.zeros(vocabularysNum)  # 建立numpy.zeros陣列,
    p0Denom = 0.0
    p1Denom = 0.0

    """ 將所有行按是否是侮辱類分別疊加,統計各個詞出現的次數 """
    for i in range(dataListNum):
        if results[i] == 1:  # 統計屬於侮辱類的條件概率所需的資料,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMap[i]
            p1Denom += sum(trainMap[i])
        else:  # 統計屬於非侮辱類的條件概率所需的資料,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMap[i]
            p0Denom += sum(trainMap[i])

    """ 計算概率 """
    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive  # 返回屬於侮辱類的條件概率陣列,屬於非侮辱類的條件概率陣列,文件屬於侮辱類的概率


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

    :param vec2Classify: 待分類的詞條陣列
    :param p0Vec: 侮辱類的條件概率陣列
    :param p1Vec: 非侮辱類的條件概率陣列
    :param pClass1: 文件屬於侮辱類的概率
    :return: 是否屬於侮辱類,0. 不屬於,1. 屬於
    """
    p1 = reduce(lambda x, y: x * y, vec2Classify * p1Vec) * pClass1  # 對應元素相乘
    p0 = reduce(lambda x, y: x * y, vec2Classify * p0Vec) * (1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0


if __name__ == '__main__':
    """ 建立實驗樣本 """
    dataSet, results = loadDataSet()
    """ 去重,建立詞彙表 """
    vocabularys = createVocabList(dataSet)
    trainMap = []
    """ 構造向量矩陣,標記每一行出現的詞語 """
    for dataList in dataSet:
        trainMap.append(wordsToVector(dataList, vocabularys))
        
    p0V, p1V, pAb = trainNB0(np.array(trainMap), np.array(results))
    
    testEntry = ['love', 'my', 'dalmation']  # 測試樣本1
    thisDoc = np.array(wordsToVector(testEntry, vocabularys))
    if classifyNB(thisDoc, p0V, p1V, pAb):
        print(testEntry, '屬於侮辱類')  # 執行分類並列印分類結果
    else:
        print(testEntry, '屬於非侮辱類')  # 執行分類並列印分類結果
        
    testEntry = ['stupid', 'garbage']  # 測試樣本2
    thisDoc = np.array(wordsToVector(testEntry, vocabularys))
    if classifyNB(thisDoc, p0V, p1V, pAb):
        print(testEntry, '屬於侮辱類')  # 執行分類並列印分類結果
    else:
        print(testEntry, '屬於非侮辱類')  # 執行分類並列印分類結果
# }}}

7.2. 原理解讀

基本的原理其實很簡單,就是統計各個詞分別屬於侮辱類和非侮辱類的出現次數,從而就可以計算他們的概率了。

8. 參考資料

Peter Harrington 《機器學習實戰》。
https://blog.csdn.net/c406495762/article/details/77341116。