一個Python例子帶你理解樸素貝葉斯算法原理

一個Python例子帶你理解樸素貝葉斯算法原理

前言

前面我們在我們學習過KNN和決策樹後,我們提出了一個問題,就是我們希望算法能夠給我們一個分類概率,並不希望直接告訴我們分類情況。樸素貝葉斯將爲我們解決這個問題。

源代碼已上傳GitHub,有需要的夥伴們自行去取,地址:樸素貝葉斯源代碼

樸素貝葉斯原理解析

一、條件概率

概率我們都瞭解過吧,就是在某一件事情發生的可能性。

那條件概率呢,就是在概率的基礎上我給它一個前置條件,舉個例子:

現在有二個黑箱子,裏面有一些形狀大小相同的小球,分佈情況如下圖所示:

C:\Users\86151\AppData\Roaming\Typora\typora-user-images

那麼,我此時問,從箱子A中隨機選一個小球,是紅顏色的概率是多少?

很簡單,答案是2/3.

也就是P(紅|箱子A)=2/3 這裏的箱子A 就是前置條件。

同理可得,P(紅|箱子B)=1/4

如果我們將兩個箱子的球合併在一起,此時不存在什麼條件了,那麼P(紅) 就是一個普通的概率。

二、貝葉斯公式

貝葉斯公式是概率論中一個非常重要而且有用的公式,先看一下公式吧:

​ *P(A|B)=P(B|A)P(A)/P(B) (1)

如何去理解呢?咱們先看下面這個:

​ *P(A∩B) = P(A)P(B|A) (2)

P(A∩B) 表示事件A和事件B同時發生的概率,就等於事件A發生的概率乘以在事件A已發生的條件下,事件B發生的概率。

還是拿盒子小球作爲例子,假設事件A爲從箱子A中取球,事件B爲取到紅顏色的球。

那麼,P(A∩B) 表示爲從箱子A中取到紅顏色球的概率

再看等號右邊,P(A) 表示從箱子A中取球的概率,這裏因爲只有兩個箱子,且形狀大小無異,所以概率值爲1/2;P(B|A) 表示從箱子A中取到紅色球的概率,值爲2/3.

這下大家能夠明白上面式子(2)爲什麼相等了吧。

如果此時我們將A和B變量互換,此時有:

​ *P(B∩A) = P(B)P(A|B)

顯然,P(B∩A)=P(A∩B) 均表示事件A和事件B同時發生的概率

所以有:

​ **P(B)P(A|B) = P(A)P(B|A) (3)

公式(3)就被稱爲貝葉斯公式,它揭示了條件概率中條件與結果的關係,簡單的來說,這個公式讓我們得以去根據條件去判斷最終結果,也就是我們說的分類問題。

三、如何去應用貝葉斯公式?

到第二部分的時候,我會給大家使用源代碼進行分析,這裏主要是通過文字講述一下如何應用。

我們要解決的問題是垃圾郵箱的分類問題,通過郵件內容,我們去判斷這個郵箱是否是垃圾郵箱。

下圖爲正常郵箱內容:

在這裏插入圖片描述
這個爲垃圾郵箱內容:

在這裏插入圖片描述

那麼我們如何去分辨該郵件是否是垃圾郵件呢,聽聽這個思路

垃圾郵件中總是有一些詞彙出現的頻率比較高,如果我們針對郵件中的單詞建立一個詞向量(顯示郵件中出現單詞的數量),根據訓練數據計算不同類型郵箱單詞出現的概率,就能夠利用這些數據,使用貝葉斯公式去預測郵件的歸屬問題。

上面不明白沒關係,咱們將貝葉斯公式稍微改裝一下:

img

圖中Ci 表示郵件分類 i=0時,表示郵件是非垃圾郵件 i=1 表示郵件是垃圾郵件

d表示數據,也就是我們將要構建的每個郵件的詞向量

詞向量,就是用來記錄單詞出現次數的一個列表

在這裏插入圖片描述
是一個條件概率,表示在郵件類型爲i的條件下,數據d的概率(這裏理解爲各個單詞出現的概率)
在這裏插入圖片描述

同樣也是一個條件概率,表示在數據d的情況下,郵件的屬於i類的概率

在這裏插入圖片描述
表示郵件屬於i的概率,就是一個普通的概率問題

好了,基本上我們就是通過這樣一個公式來寫我們代碼,不過,寫代碼前,我們先說一下流程:

  1. 處理郵件數據,去除掉標點符號,將每個郵箱的單詞放入列表中
  2. 統計郵件中所出現過的單詞,存儲到一個列表中,長度L就是我們所要創建詞向量的大小
  3. 對每個郵件創建一個長度爲L的列表(稱之爲詞向量),初始值爲1,遍歷每個郵件的單詞列表,統計單詞出現次數.
  4. 分別對不同類型的郵件的詞向量進行相加,再處理他們各自的單詞總數,此時結果爲
    在這裏插入圖片描述
  5. 再將詞向量的各個數相乘,最後再乘以數據集中郵件各個類別的概率
    在這裏插入圖片描述

貝葉斯算法源代碼解析

一、 加載處理數據

def textParse(text):
    text1 = re.split(r'\W+',text)
    text2 = [textfilter.lower() for textfilter in text1 if len(textfilter)>2]#返回字符長度大於2的字符
    # to do -這裏可以添加自定義過濾字符串的規則
    return text2

def loadDataSet():
    fileurl1 = 'datasets/email/ham'
    fileurl2 = 'datasets/email/spam'#垃圾郵件
    postingList=[]
    classVec = []
    fulltext=[]
    for i in range(1,26):
        f1 = open(fileurl1+'/{}.txt'.format(i),errors='ignore')
        text1 = textParse(f1.read())
        postingList.append(text1)
        fulltext.extend(text1)
        classVec.append(1)
        f2 = open(fileurl2+'/{}.txt'.format(i),'r')
        text2 = textParse(f2.read())
        postingList.append(text2)
        fulltext.extend(text2)
        classVec.append(0)
    return postingList,classVec,fulltext

這裏一共有兩個函數,textParse()和loadDataSet()

textParse()方法用來去篩選掉郵件中的標點符號和長度小於2的單詞

loadDataSet() 加載不同文件夾下的txt文件,裏面存放着郵件的內容。文件ham表示文件下面的郵件爲非垃圾郵件;文件spam表示文件下面的郵件爲垃圾郵件。

垃圾郵件和非垃圾郵件各有25個,一共有50個郵件

二、創建詞向量

def setOfWords2Vec(vocabList,inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)]+=1
        else:
            print("the word is not in my vocabulary:",word)
    return returnVec

vocabList 就是步驟中咱們說到的所有郵件出現的單詞列表

inputSet 輸入數據,也就是經過咱們處理過後的郵件單詞列表

這裏主要就一個循環判斷語句,用來統計inputSet 輸入的數據中,單詞出現的次數,以此來構建詞向量

三、訓練函數

def trainNB0(trainMatrix,trainCategory):
    #print("trainMatrix",trainMatrix)
    numTrainDocs = len(trainMatrix)#共有幾個文本
    numWords = len(trainMatrix[0])#共有多少單詞
    pAbusive = sum(trainCategory)/float(numTrainDocs)#文本中有多少是侮辱性文檔
    p0Num = np.ones(numWords)#創建全爲1的數組
    p1Num = np.ones(numWords)#
    p0Denom = 1.0*numWords#
    p1Denom = 1.0*numWords
    for i in range(numTrainDocs):
        print("trainCategory[i]",trainCategory[i])
        if trainCategory[i]==1:#文檔的分類
            p1Num+=trainMatrix[i]#計算每個單詞出現的次數
            p1Denom+=sum(trainMatrix[i])#統計文檔類型屬於1的所有文檔單詞個數
        else:
            p0Num+=trainMatrix[i]
            p0Denom+=sum(trainMatrix[i])
    p1Vect = np.log(p1Num/p1Denom)
    p0Vect = np.log(p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive

輸入數據

trainMatrix :測試數據集的詞向量列表

trainCategory:測試數據集的郵件分類情況,垃圾郵件爲1 非垃圾郵件爲0

處理過程

這裏通過一個for循環,根據測試數據的郵件分類情況,對各個類別的詞向量進行相加,並統計所有的單詞數量。

然後將相加後的向量除以單詞總數,計算出單詞出現的頻率

最後使用log函數進行轉化

返回數據:

p0Vect:類型爲0的經log函數轉化後的詞向量頻率

p1Vect:類型爲1的經log函數轉化後的詞向量頻率

pAbusive:數據集中垃圾郵件的概率

四、分類函數

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p0 = np.sum(vec2Classify*p0Vec)+np.log(pClass1)
    p1 = np.sum(vec2Classify*p1Vec)+np.log(1.0-pClass1)
    if p1>p0:
        return 1
    else:
        return 0

輸入數據

vec2Classify 輸入數據的詞向量

p0Vec、p1Vec、pClass1 均爲trainNB0()函數的返回值

處理過程

先拓展一個知識點:

在這裏插入圖片描述
這個是咱們前面提到的一個條件概率,d是詞向量。

我們直到,在代碼中,詞向量是使用列表來表示,但是這裏要得出一個數,怎麼計算呢

概率論中有一個這樣的公式:

在這裏插入圖片描述

當然前提假設,詞向量中的各個值之間是相互獨立的。

如果這裏不明白的,可以去看一下概率論基礎補一下

由於前面咱們使用了log函數,所以有

log(axbxcxd) = log(a)+log(b)+log©+log(d)

所以在代碼中使用求和進行計算

最後對p0和p1進行概率比較,值較大的就是輸入數據的所屬類

五、測試函數

def testingNB():
    listOPosts,listClasses,fulltext = loadDataSet()
    # myVocabList = createVocabList(listOPosts)
    myVocabList = list(set(fulltext))
    #print("單詞列表:",myVocabList)
    #隨機取10個郵件作爲測試使用
    #print("listOPosts.length:",len(listOPosts))
    trainsetnum = list(range(50))
    testset = []
    for i in range(10):
        index = int(random.uniform(0,len(trainsetnum)))
        testset.append(trainsetnum[index])
        del(trainsetnum[index])
    print("testset:",testset)
    #重新組裝訓練數據和測試數據
    traindata = []
    trainclass=[]
    for j in trainsetnum:
        traindata.append(setOfWords2Vec(myVocabList,listOPosts[j]))#這裏直接轉化爲詞向量
        trainclass.append(listClasses[j])
    testdata=[]
    testclass=[]
    for k in testset:
        testdata.append(setOfWords2Vec(myVocabList,listOPosts[k]))
        testclass.append(listClasses[k])
    p0V,p1V,pSpam = trainNB0(traindata,trainclass)
    errorcount=0
    for i in range(len(testdata)):
        wordVector = testdata[i]
        print("輸出最終分類結果:",classifyNB(wordVector,p0V,p1V,pSpam))
        print("輸出原本結果-:",testclass[i])
        if testclass[i]!=classifyNB(wordVector,p0V,p1V,pSpam):
            errorcount+=1
    print(" the error rate is:{}".format(errorcount))

這個函數只是進行測試模型的精準度,我測試了大概10次,一共出現錯誤沒超過5個,也就是相當於正確率在95%以上,還是挺準的。

這裏將訓練數據爲40個郵件,測試數據爲10個郵件。

總結

這裏咱們僅僅是貝葉斯公式的一個小小應用,我們實驗的前提是這些單詞之間沒有什麼聯繫,都是獨立的個體。但是在實際中,有些單詞總是會出現在某些單詞後面或者前面,比如 am 總是出現在I後面,這些信息咱們沒有充分利用到。

代碼中,我們使用對數函數來解決下溢出問題(很多非常小的數相乘會失準),使用了將所有詞出現的次數初始化爲1,解決了一個概率爲0總體乘積爲0的情況。

總的來說,樸素貝葉斯公式還是一個比較優良的分類器,後面等我們學到更多分類算法後,再於其進行對比。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章