1. 程式人生 > >機器學習樸素貝葉斯演算法

機器學習樸素貝葉斯演算法

樸素貝葉斯屬於監督學習的生成模型,實現簡單,沒有迭代,學習效率高,在大樣本量下會有較好表現。但因為假設太強——特徵條件獨立,在輸入向量的特徵條件有關聯的場景下,並不適用。

樸素貝葉斯演算法:主要思路是通過聯合概率P(x,y)=P(x|y)P(y)建模,運用貝葉斯定理求解後驗概率P(y|x);將後驗概率最大者對應的類別作為預測類別。

首先定義訓練集T=[(x1,y1),(x2,y2),(x3,y3)...,(xN,yN)],其類別yi\in (c1,c2,c3,...cK)

輸入待預測的資料,預測類別:

                                                  

由貝葉斯定理可知:

                                    

由於p(x)是恆等的,因此預測類別等價於:

                                         

最後,貝葉斯將分類問題轉化成了求條件概率先驗概率的最大乘積問題。由於做了條件獨立性的假設,求解公式可以轉化為:

                                   

選取後驗概率醉的的類別作為預測類別,是因為後驗概率最大化,可以使得期望風險最小化。

極大似然估計

在樸素貝葉斯學習中,需要估計先驗概率與條件概率,一般時採用極大似然估計。先驗概率的極大似然估計:

                                             

其中,I是指示函式,滿足括號內條件時為1否則為0;可以看作為計數

設第 j 維特徵的取值空間為,且輸入變數的第 j 維x^{(j)}=a_{j}l

,則條件概率的極大似然估計為:

                     

貝葉斯估計

在估計先驗概率與條件概率時,有可能出現為0的情況,則計算得到的後驗概率亦為0,從而影響分類的效果。因此,需要在估計時做平滑,這種方法被稱為貝葉斯估計。先驗概率的貝葉斯估計:

                                          

後驗概率的貝葉斯估計為:

                           

\lambda =1的時候,被稱為拉普拉斯平滑

以上就是樸素貝葉斯演算法的數學原理。接下來附上案例程式碼。(詳細過程見註釋)

import numpy as np
from functools import reduce


#建立資料樣本
#postingList的每一行,代表一個樣本資料
#classVec是每個樣本資料所對應的標籤,0是非侮辱類,1是侮辱類
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]
    return postingList,classVec



#根據vocabList詞彙表,將inputSet向量化,向量的每個元素為1或0
#引數裡,vocabList就是createVocabList方法最後返回的元素不重複的陣列(詞彙表),
#inputSet是取的原始資料集裡的某一行,比如:['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)                        #建立一個所有元素都是0的陣列,長度和vocabList一樣,returnVec=[0,0,0,0..........0,0,0]
    for word in inputSet:                                 #將['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']裡的每個元素遍歷
        if word in vocabList:                             #如果某個元素在詞彙表裡
            returnVec[vocabList.index(word)] = 1          #那就找到該元素在詞彙表中的對應索引,並在returnVec裡的同樣索引位置賦值,值為1
       # else: print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

# 該方法最終返回的結果如下:
# [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0]
# 其中"1"代表的索引位置,就是['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']裡每個元素在我的詞彙表vocabList裡的索引位置




#將切分的實驗樣本詞條整理成不重複的此條例列表,也就是詞彙表
#dataSet是最初的資料樣本,也就是loadDataSet方法裡的postingList資料集
def createVocabList(dataSet):
    vocabSet = set([])                        #先建立一個空的字典(set建立字典,注意字典是不能有重複元素的)
    for document in dataSet:                  #將dataSet的每一行都進行遍歷
        vocabSet = vocabSet | set(document)   #set(document)表示取每行的不重複資料,每次迭代後vocabSet返回值類似於:['my','dog','flea',.....]
    print('詞彙表為:\n',vocabSet)                           #"" | "是並集符號,表示將每次迭代的不重複資料集都和之前的vocabSet進行合併                                                       
    return list(vocabSet)                     #將所有行的不重複資料都進行合併之後,最後得到整個dataSet的不重複的資料集,也就是詞彙表

    #   該方法最後返回的list(vocabSet)詞彙表l類似如下結構:
    #   ['dog', 'help', 'ate', 'is', 'buying', 'him', 'mr', 
    #   'maybe', 'park', 'quit', 'so', 'stupid', 'stop', 'steak',
    #   'licks', 'food', 'please', 'problems', 'cute', 'I', 'flea',
    #   'posting', 'love', 'how', 'dalmation', 'take', 'garbage', 'not',
    #   'my', 'to', 'has', 'worthless']
    
    
#分類器訓練函式
# 其中trainMatrix是setOfWords2Vec方法返回的returnVec所組成的矩陣,如下:
# [[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,0,1,0],
#  [1,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0],
#  [0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0],
#  [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
#  [0,0,1,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0],
#  [1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]]
# trainCategory表示類別標籤向量,即loadDataSet方法裡的classVec,classVec=[0,1,0,1,0,1]
#該方法最終將出現錯誤
def trainNB0(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)           #返回文件數目,numTrainDocs = 6
    numWords = len(trainMatrix[0])            #計算詞彙表裡的詞條數,numWords = 32
    pAbusive = sum(trainCategory)/float(numTrainDocs)     #計算侮辱類文件佔總文件的概率(比例)pAbusive = 3/6 = 0.5
    p0Num = np.zeros(numWords);p1Num = np.zeros(numWords) #建立兩個numpy陣列,每個元素都是0,長度為32
    p0Denom = 0.0; p1Denom = 0.0              #初始化兩個數,這兩個數是之後算條件概率的分母,p0Denom是非侮辱性相對應的條件概率的分母,p1Denom則是侮辱性的分母
    for i in range(numTrainDocs):             #對類別標籤進行遍歷,也就是對[0,1,0,1,0,1]的每個元素進行遍歷,0代表非侮辱,1代表侮辱
        if trainCategory[i]==1:               #如果是侮辱的
            p1Num += trainMatrix[i]           #那就把trainMatrix矩陣中對應的那一行的標籤累加到p1Num上,
            p1Denom += sum(trainMatrix[i])    #然後,將所有詞出現的次數累加到p1Denom上
                                              #最後p1Num得到的結果是:
                                              # p1Num = [2. 0. 0. 0. 1. 1. 0. 1. 1. 1. 0. 3. 1. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 1. 0. 2.]
                                              # 該陣列的意思:在整個詞條中,第一個詞出現的頻次為2,第二個詞頻次為0,以此類推
                                              #p1Denom=19,意思是侮辱性文件中所有詞彙出現的總次數,也就是p1Num中所有元素相加,p1Denom = 19  
        else:                                 #如果是非侮辱的,同上所述,將得到非侮辱性文件的結果,
            p0Num += trainMatrix[i]           #p0Num = [1. 1. 1. 1. 0. 2. 1. 0. 0. 0. 1. 0. 1. 1. 1. 0. 1. 1. 1. 1. 1. 0. 1. 1. 1. 0. 0. 0. 3. 1. 1. 0.]
            p0Denom += sum(trainMatrix[i])    #p0Denom = 24
    p1Vect = p1Num/p1Denom                    #最後需要分別計算在侮辱類和非侮辱類中,每個詞出現的概率,
    p0Vect = p0Num/p0Denom                    #比如對於侮辱類,第一個詞出現兩次,除以所有侮辱類的次數19.得到2/19=0.10526316
    return p0Vect,p1Vect,pAbusive             #p0Vect是非侮辱類每個詞出現的概率,p1Vect是侮辱類每個詞出現的概率,pAbusive是侮辱類文件佔總文件的比例
#該方法最後返回的結果將是:
#     p1Vect = [0.10526316 0.         0.         0.         0.05263158 0.05263158
#               0.         0.05263158 0.05263158 0.05263158 0.         0.15789474
#               0.05263158 0.         0.         0.05263158 0.         0.
#               0.         0.         0.         0.05263158 0.         0.
#               0.         0.05263158 0.05263158 0.05263158 0.         0.05263158
#               0.         0.10526316]
    
#     p0Vect = [0.04166667 0.04166667 0.04166667 0.04166667 0.         0.08333333
#               0.04166667 0.         0.         0.         0.04166667 0.
#               0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
#               0.04166667 0.04166667 0.04166667 0.         0.04166667 0.04166667
#               0.04166667 0.         0.         0.         0.125      0.04166667
#               0.04166667 0.        ]
    
#     pAbusive = 3/6 = 0.5



#修正後的貝葉斯分類器訓練函式
def trainNB1(trainMatrix,trainCategory):
    numTrainDocs = len(trainMatrix)                            #計算訓練的文件數目
    numWords = len(trainMatrix[0])                             #計算每篇文件的詞條數
    pAbusive = sum(trainCategory)/float(numTrainDocs)          #文件屬於侮辱類的概率
    p0Num = np.ones(numWords); p1Num = np.ones(numWords)       #建立numpy.ones陣列,詞條出現數初始化為1,拉普拉斯平滑
    p0Denom = 2.0; p1Denom = 2.0                               #分母初始化為2,拉普拉斯平滑
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:                              
            p1Num += trainMatrix[i]                            #p1Num=[1. 1. 2. 2. 1. 2. 2. 1. 1. 3. 1. 2. 1. 1. 1. 1. 3. 2. 2. 1. 1. 1. 2. 2. 1. 2. 2. 1. 1. 4. 1. 2.]
            p1Denom += sum(trainMatrix[i])                     #p1Denom = 21
        else:                                                
            p0Num += trainMatrix[i]                            #p0Num=[2. 2. 1. 1. 2. 1. 2. 2. 2. 1. 2. 1. 2. 2. 2. 2. 2. 1. 1. 2. 2. 2. 1. 2. 2. 1. 3. 4. 2. 1. 2. 1.]
            p0Denom += sum(trainMatrix[i])                     #p0Denom = 26
    p1Vect = np.log(p1Num/p1Denom)                             #取對數,防止下溢位        
    print('p1Vect:\n',p1Vect)
    p0Vect = np.log(p0Num/p0Denom) 
    print('p0Vect:\n',p0Vect)
    return p0Vect,p1Vect,pAbusive   

#該函式最後返回的p0Vect,p1Vect結果是:(相比之前的訓練器,將初始陣列的元素0改成1,分母初始值由0改為2,然後概率取對數,就得到以下結果
#                                       元素0改為1,屬於拉普拉斯平滑,概率取對數是防止下溢位)
#     p0Vect = [-2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936 -3.25809654
#               -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654
#               -2.56494936 -2.56494936 -2.56494936 -2.56494936 -2.56494936 -3.25809654
#               -3.25809654 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936
#               -2.56494936 -3.25809654 -2.15948425 -1.87180218 -2.56494936 -3.25809654
#               -2.56494936 -3.25809654]


#     p0Vect = [-3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526
#               -2.35137526 -3.04452244 -3.04452244 -1.94591015 -3.04452244 -2.35137526
#               -3.04452244 -3.04452244 -3.04452244 -3.04452244 -1.94591015 -2.35137526
#               -2.35137526 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526
#               -3.04452244 -2.35137526 -2.35137526 -3.04452244 -3.04452244 -1.65822808
#               -3.04452244 -2.35137526]


#樸素貝葉斯分類器分類函式
#vec2Classify是待測試的詞條
#p0Vec是非侮辱類的條件概率陣列
#p1Vec是侮辱類的條件概率陣列
#pClass1是文件屬於侮辱類的概率
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)           #計算該文件屬於侮辱類的概率
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)     #計算該文件屬於非侮辱類的概率
    print('屬於p0的概率取對數:',p0)                            #注意上面兩行的計算公式省略了貝葉斯公式裡的分母
    print('屬於p1的概率取對數:',p1)                            #因為不管是計算p0還是p1,分母都是一樣的,而我們要比較概率的大小,只需要比較分子即可
    if p1 > p0:
        return 1                                               #如果p1大於p0,說明屬於侮辱類的可能性更大,返回1,(1代表侮辱性)
    else:
        return 0                                               #反之,則返回0(代表非侮辱性)

    
    
def testingNB():
    listOPosts,listClasses = loadDataSet()                                  #建立實驗樣本
    myVocabList = createVocabList(listOPosts)                               #建立詞彙表
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))             #將實驗樣本向量化
    p0V,p1V,pAb = trainNB1(np.array(trainMat),np.array(listClasses))        #訓練樸素貝葉斯分類器
    testEntry = ['love', 'my', 'dalmation']                                 #測試樣本1
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))              #測試樣本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於侮辱類')                                        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非侮辱類')                                       #執行分類並列印分類結果
    testEntry = ['stupid', 'garbage']                                       #測試樣本2

    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))              #測試樣本向量化
    if classifyNB(thisDoc,p0V,p1V,pAb):
        print(testEntry,'屬於侮辱類')                                        #執行分類並列印分類結果
    else:
        print(testEntry,'屬於非侮辱類')


if __name__ == '__main__':
    testingNB()

最後列印資訊,並分類得出的結果是:

詞彙表為:
 {'to', 'problems', 'help', 'buying', 'park', 'dalmation', 'how', 'I', 'is', 'posting', 'stupid', 'ate', 'mr', 'has', 'stop', 'take', 'quit', 'him', 'garbage', 'my', 'steak', 'not', 'cute', 'maybe', 'flea', 'love', 'dog', 'worthless', 'food', 'so', 'licks', 'please'}
p1Vect:
 [-2.35137526 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -1.65822808 -3.04452244
 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -2.35137526 -2.35137526
 -2.35137526 -3.04452244 -3.04452244 -2.35137526 -3.04452244 -2.35137526
 -3.04452244 -3.04452244 -1.94591015 -1.94591015 -2.35137526 -3.04452244
 -3.04452244 -3.04452244]
p0Vect:
 [-2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.15948425
 -3.25809654 -1.87180218 -2.56494936 -3.25809654 -2.56494936 -3.25809654
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -2.56494936 -2.56494936]
屬於p0的概率取對數: -7.694848072384611
屬於p1的概率取對數: -9.826714493730215
['love', 'my', 'dalmation'] 屬於非侮辱類
屬於p0的概率取對數: -7.20934025660291
屬於p1的概率取對數: -4.702750514326955
['stupid', 'garbage'] 屬於侮辱類