1. 程式人生 > >樸素貝葉斯詳解及其python實現

樸素貝葉斯詳解及其python實現

  • 簡介

         貝葉斯定理用Thomas Bayes的名字命名。早在18世紀,英國學者貝葉斯提出計算條件概率的公式用來解決如下問題:

         假設B[1]、B[2]…B[n]互斥並且構成一個完備事件組,已知他們的概率P(B[i]),i=1,2,...,n,已知某一事件A與B相伴隨機出現,並且已知條件概率P(A|B[i])的概率,求條件概率p(B[i]|A)

         貝葉斯定理具體形式為:

                                              https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D215/sign=07ef5cb4b03eb13540c7b0ba931fa8cb/1ad5ad6eddc451da062e7cb7bafd5266d1163295.jpg

其中P(A|B)是在 B 發生的情況下 A 發生的可能性。P(B|A)是在A發生的情況下B發生的概率可能性。

         P(A)是 A 的先驗概率,之所以稱為“先驗”是因為它不考慮任何 B 方面的因素。P(A|B)是已知 B 發生後 A 的條件概率,也由於得自 B 的取值而被稱作 A 的後驗概率。P(B|A)是已知 A 發生後 B 的條件概率,也由於得自 A 的取值而被稱作 B 的後驗概率。P(B)是 B 的先驗概率,也作標淮化常量(normalizing constant)。後驗概率 = (相似度 * 先驗概率)/標淮化常量,既是P(A|B) =P(AB)/P(B),比例P(B|A)/P(B)也有時被稱作標淮相似度(standardised likelihood),Bayes定理可表述為:後驗概率 = 標淮相似度 * 先驗概率        

  • 樸素貝葉斯

在資料探勘中,樸素貝葉斯是一個比較經典的演算法,給定訓練資料集A,類別B,其中A

的屬性列為Ai,i=1,2,…,n;B={B1,B2,…,Bn};貝葉斯演算法核心思想是給定待判定資料元組,根據訓練資料集進行分類預測,通過貝葉斯定理計算當前待判定元組屬於某一類別的概率,概率最大者即為該待判定元組的類別歸屬。

         所謂的樸素貝葉斯是指在給定訓練集資料元組中,每一個屬性列之間是獨立的,相互之間互不影響,A1,A2,…,An之間互相獨立:

         通過貝葉斯公式計算後驗概率:

                                                     

 

此公式是表名給定元組資料A,其在A的條件下屬於類別Bj的概率,由於A的每一列之間相互獨立的,互不影響,因此

                                                     

         在求出之後,因為P(A)對每一個類別都相同,因此可通過的數值進行比較,因此就有:得出概率最大的即為相應的預測類別。

     在進行貝葉斯演算法應用時候由於資料量較大而且較為複雜,因此經常會遇見概率為0的值,那麼概率為0的值怎麼辦吶?有一個簡單的技巧來避免該問題,可以假定訓練資料庫D很大,以至於對每個計數加1造成的估計概率的變化忽略不計,但可以方便的避免概率值為0.這種概率估計技術成為拉普拉斯校準或者拉普拉斯估計法。對元組中每一列屬性的不重複的資料項計數加1,並且在分母計算上也加1,這樣可以有效地避免概率為0的問題。

同時在運用python進行計算概率時候,我們可以通過取對數進行相應的計算,因為資料量過大的話會造成某項概率過小,但是我們按照數學的方法,y=x是單調遞增,那麼y=lnx也

是單調遞增的這個思想,即x1>x2 那麼lnx1>lnx2;通過對數之間的公式運算,如

                                                        

                                                        

                                                       

  • 樸素貝葉斯的python實現
    # -*- coding: utf-8 -*-
    
    from numpy import *
    from functools import reduce
    
    # 廣告、垃圾標識
    adClass = 1
    
    
    def loadDataSet():
        """載入資料集合及其對應的分類"""
        wordsList = [['週六', '公司', '一起', '聚餐', '時間'],
                     ['優惠', '返利', '打折', '優惠', '金融', '理財'],
                     ['喜歡', '機器學習', '一起', '研究', '歡迎', '貝葉斯', '演算法', '公式'],
                     ['公司', '發票', '稅點', '優惠', '增值稅', '打折'],
                     ['北京', '今天', '霧霾', '不宜', '外出', '時間', '在家', '討論', '學習'],
                     ['招聘', '兼職', '日薪', '保險', '返利']]
        # 1 是, 0 否
        classVec = [0, 1, 0, 1, 0, 1]
        return wordsList, classVec
    
    
    # python中的& | 是位運算子   and or是邏輯運算子 當and的運算結果為true時候返回的並不是true而是運算結果最後一位變數的值
    # 當and返回的結果是false時候,如果A AND B 返回的是第一個false的值,如果a為false 則返回a,如果a不是false,那麼返回b
    # 如果a or b 為true時候,返回的是第一個真的變數的值,如果a,b都為真時候那麼返回a 如果a為假b為真那麼返回b
    # a & b a和b為兩個set,返回結果取a和b的交集  a|b a和b為兩個set,返回結果為兩個集合的不重複並集
    def doc2VecList(docList):
        # 從第一個和第二個集合開始進行並集操作,最後返回一個不重複的並集
        a = list(reduce(lambda x, y: set(x) | set(y), docList))
        return a
    
    
    def words2Vec(vecList, inputWords):
        """把單子轉化為詞向量"""
        # 轉化成以一維陣列
        resultVec = [0] * len(vecList)
        for word in inputWords:
            if word in vecList:
                # 在單詞出現的位置上的計數加1
                resultVec[vecList.index(word)] += 1
            else:
                print('沒有發現此單詞')
    
        return array(resultVec)
    
    
    def trainNB(trainMatrix, trainClass):
        """計算,生成每個詞對於類別上的概率"""
        # 類別行數
        numTrainClass = len(trainClass)
        # 列數
        numWords = len(trainMatrix[0])
    
        # 全部都初始化為1, 防止出現概率為0的情況出現
        # 見於韓家煒的資料探勘概念與技術上的講解,避免出現概率為0的狀況,影響計算,因為在數量很大的情況下,在分子和分母同時+1的情況不會
        # 影響主要的資料
        p0Num = ones(numWords)
        p1Num = ones(numWords)
        # 相應的單詞初始化為2
        # 為了分子分母同時都加上某個數λ
        p0Words = 2.0
        p1Words = 2.0
        # 統計每個分類的詞的總數
        # 訓練資料集的行數作為遍歷的條件,從1開始
        # 如果當前類別為1,那麼p1Num會加上當前單詞矩陣行資料,依次遍歷
        # 如果當前類別為0,那麼p0Num會加上當前單詞矩陣行資料,依次遍歷
        # 同時統計當前類別下單詞的個數和p1Words和p0Words
        for i in range(numTrainClass):
            if trainClass[i] == 1:
                # 陣列在對應的位置上相加
                p1Num += trainMatrix[i]
                p1Words += sum(trainMatrix[i])
            else:
                p0Num += trainMatrix[i]
                p0Words += sum(trainMatrix[i])
        # 計算每種型別裡面, 每個單詞出現的概率
        # 樸素貝葉斯分類中,y=x是單調遞增函式,y=ln(x)也是單調的遞增的
        # 如果x1>x2 那麼ln(x1)>ln(x2)
        # 在計算過程中,由於概率的值較小,所以我們就取對數進行比較,根據對數的特性
        # ln(MN) = ln(M)+ln(N)
        # ln(M/N) = ln(M)-ln(N)
        # ln(M**n)= nln(M)
        # 注:其中ln可替換為log的任意對數底
        p0Vec = log(p0Num / p0Words)
        p1Vec = log(p1Num / p1Words)
        # 計算在類別中1出現的概率,0出現的概率可通過1-p得到
        pClass1 = sum(trainClass) / float(numTrainClass)
        return p0Vec, p1Vec, pClass1
    
    
    def classifyNB(testVec, p0Vec, p1Vec, pClass1):
        # 樸素貝葉斯分類, max(p0, p1)作為推斷的分類
        # y=x 是單調遞增的, y=ln(x)也是單調遞增的。 , 如果x1 > x2, 那麼ln(x1) > ln(x2)
        # 因為概率的值太小了,所以我們可以取ln, 根據對數特性ln(ab) = lna + lnb, 可以簡化計算
        # sum是numpy的函式,testVec是一個數組向量,p1Vec是一個1的概率向量,通過矩陣之間的乘機
        # 獲得p(X1|Yj)*p(X2|Yj)*...*p(Xn|Yj)*p(Yj)
        # 其中pClass1即為p(Yj)
        # 此處計算出的p1是用對數表示,按照上面所說的,對數也是單調的,而貝葉斯分類主要是通過比較概率
        # 出現的大小,不需要確切的概率資料,因此下述表述完全正確
        p1 = sum(testVec * p1Vec) + log(pClass1)
        p0 = sum(testVec * p0Vec) + log(1 - pClass1)
        if p0 > p1:
            return 0
        return 1
    
    
    def printClass(words, testClass):
        if testClass == adClass:
            print(words, '推測為:廣告郵件')
        else:
            print(words, '推測為:正常郵件')
    
    
    def tNB():
        # 從訓練資料集中提取出屬性矩陣和分類資料
        docList, classVec = loadDataSet()
        # 生成包含所有單詞的list
        # 此處生成的單詞向量是不重複的
        allWordsVec = doc2VecList(docList)
        # 構建詞向量矩陣
        # 計算docList資料集中每一行每個單詞出現的次數,其中返回的trainMat是一個數組的陣列
        trainMat = list(map(lambda x: words2Vec(allWordsVec, x), docList))
        # 訓練計算每個詞在分類上的概率, p0V:每個單詞在非分類出現的概率, p1V:每個單詞在是分類出現的概率
        # 其中概率是以ln進行計算的
        # pClass1為類別中是1的概率
        p0V, p1V, pClass1 = trainNB(trainMat, classVec)
        # 測試資料集
        testWords = ['公司', '聚餐', '討論', '貝葉斯']
        # 轉換成單詞向量,32個單詞構成的陣列,如果此單詞在陣列中,陣列的項值置1
        testVec = words2Vec(allWordsVec, testWords)
        # 通過將單詞向量testVec代入,根據貝葉斯公式,比較各個類別的後驗概率,判斷當前資料的分類情況
        testClass = classifyNB(testVec, p0V, p1V, pClass1)
        # 打印出測試結果
        printClass(testWords, testClass)
    
        testWords = ['公司', '保險', '金融']
        # 轉換成單詞向量,32個單詞構成的陣列,如果此單詞在陣列中,陣列的項值置1
        testVec = words2Vec(allWordsVec, testWords)
        # 通過將單詞向量testVec代入,根據貝葉斯公式,比較各個類別的後驗概率,判斷當前資料的分類情況
        testClass = classifyNB(testVec, p0V, p1V, pClass1)
        # 打印出測試結果
        printClass(testWords, testClass)
    
    
    if __name__ == '__main__':
        tNB()