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

機器學習——樸素貝葉斯演算法Python實現

簡介

這裡參考《統計學習方法》李航編進行學習總結。詳細演算法介紹參見書籍,這裡只說明關鍵內容。
這裡寫圖片描述
這裡寫圖片描述

條件獨立下:p{X=x|Y=y}=p{X1=x1|Y=y} * p{X2=x2|Y=y} *...* p{Xn=xn|Y=y}

這裡寫圖片描述

(4.4)等價於p{Y=ck|X=x}= p{X=x|Y=ck}*p{Y=ck} / p{X=x}
所以對不同的Y=ck,分母都是一樣的,最後(4.7)比較選出最大概率時可以忽略分母,僅比較分子。
而分子為p{Y=ck} * p{X1=x1|Y=ck}* p{X2=x2|Y=ck}*…*p{Xn=xn|Y=ck}

所以(4.7)的公式即為
max(p{Y=ck}* p{X1=x1|Y=ck} * p{X2=x2|Y=ck}*…*p{Xn=xn|Y=ck}) (k=1,2,…) 並返回對應的ck。

因此模型只需要訓練出(生成)所有的 p{Y=ck} 和 p{Xi=xij|Y=ck}就可以利用(4.7)進行分類了.
(注:這裡的xij表示第i個特徵的第j個取值。)

案例:

下面通過一個例項來實現這個演算法。
專案資料下載及說明,如下連結:
http://archive.ics.uci.edu/ml/datasets/Car+Evaluation
請自行下載資料,以及瞭解資料的相關內容。

資料樣例:

Class Values:
unacc, acc, good, vgood

Attributes:
buying: vhigh, high, med, low.
maint: vhigh, high, med, low.
doors: 2, 3, 4, 5more.
persons: 2, 4, more.
lug_boot: small, med, big.
safety: low, med, high.

樣本:

vhigh,vhigh,2,2,small,low,unacc
vhigh,vhigh,2,2,small,med,unacc
vhigh,vhigh,2,2,small,high,unacc
vhigh,vhigh,2,2,med,low,unacc
vhigh,vhigh,2,2,med,med,unacc
vhigh,vhigh,2,2,med,high,unacc
,,,

先讀取資料:

import numpy as np

#從文件中讀取資料,每條資料轉成列表的形式
def readData(path):
    dataList = []
    with
open(path,'r') as f: dataSet = f.readlines() for d in dataSet: d = d[:-1] d = d.split(',') print(d) dataList.append(d) return dataList

然後利用這些資料生成所有的 p{Y=ck} 和 p{Xi=xij|Y=ck}。

#為方便程式碼處理,先做屬性對映
#分類值對映
Cls = {'unacc':0, 'acc':1, 'good':2, 'vgood':3}   
#特徵值對映,共6個特徵值,每個特徵表示為X[i],X[i][xiv]表示特徵Xi的取值。
X = [{'vhigh':0, 'high':1, 'med':2, 'low':3},
         {'vhigh':0, 'high':1, 'med':2, 'low':3},
         {'2':0, '3':1, '4':2, '5more':3},
         {'2':0, '4':1, 'more':2},
         {'small':0, 'med':1, 'big':2},
         {'low':0, 'med':1, 'high':2}]

#訓練模型,生成概率矩陣即生成p{Y=yi}和 p{Xi=xij|Y=yi}
def NBtrain(labelData):
    datanum = len(labelData)

    Arr = np.zeros((4,6,4))   #Arr[y][xi][xiv]表示在分類y的條件下,特徵Xi取值為xiv的數量。
    for d in labelData:
        y = Cls[d[-1]]     #取分類的對映值
        for i in range(len(d)-1):
            v = X[i][d[i]]    #取每個特徵的對映值
            Arr[y][i][v] +=1  #計數

    probXCY = np.zeros((4,6,4))  #probXCY[y][xi][xiv]表示在分類y的條件下,特徵Xi取值為xiv的概率即p{Xi=xiv|Y=y}
    numY = []         #分類為yi的數量
    probY =[]         #分類為yi的概率

    for y in Cls.values():
        numY.append(np.sum(Arr[y][0]))
        probY.append( numY[y]/datanum ) 
        print(y, numY[y], probY[y])
        for xi in range(len(X)):
            s = len(X[xi])    #特徵Xi的值的個數。
            for xiv in X[xi].values():
                probXCY[y][xi][xiv] = Arr[y][xi][xiv]/numY[y] 
            print('\n')

    # print(Arr)
    # print(probXCY)
    return probXCY,probY

訓練模型生成後就需要完成對新資料的分類任務,其實就是實現(4.7)的計算就可以了。實現程式碼如下:

def NBclassify(probXCY,probY,predData):
    unknowData = predData
    datanum = len(unknowData)
    YofX = []    #記錄資料的分類
    diffNum = 0  #記錄分類結果與實際不同的數量
    for d in unknowData:
        probyCx = []    #記錄p{Y=yi|X[...]=x[...]}
        for y in Cls.values():
            p = 10**5   #概率偏移,防止計算得到的數值過小
            for xi in range(len(X)):
                xiv = X[xi][d[xi]]    #取對映值
                p *= probXCY[y][xi][xiv]
            p *= probY[y]    #p =p{y} * p{X1=x1|Y=y} * p{X2=x2|Y=y} *...* p{Xn=xn|Y=y} 
            probyCx.append(p)

        YofX.append(probyCx.index(max(probyCx)))  #等同於max( p{Y=yi|X=x} )即取概率最大的那個分類yi為該資料的分類

        print(d)
        print("分類為:",YofX[-1])
        if(YofX[-1] != Cls[d[-1]]):
            diffNum +=1
            #print(probyCx)
            print("真實分類為:",Cls[d[-1]])
        else:
            print("分類正確")


    print("錯誤數:",diffNum,"\t資料量:",datanum)
    print("錯誤率:",diffNum/datanum)

    return YofX

至此就完成了案例的樸素貝葉斯分類的實現,你可以將資料分成兩部分,一部分用於訓練,一部分用於測試。也可以直接將整個資料作為訓練同時用於測試。

上述演算法存在些小問題,會出現概率值為0 的情況
這裡寫圖片描述
這裡寫圖片描述
為了避免概率值為零的情況採用拉普拉斯平滑做調整。訓練函式對應於這兩個公式的兩條語句做點修改。如下:

probY.append( (numY[y]+1)/(datanum+len(Cls)) )  #做拉普拉斯平滑

s = len(X[xi])    #特徵Xi的值的個數。            
probXCY[y][xi][xiv] = (Arr[y][xi][xiv]+1)/(numY[y]+s)   #做拉普拉斯平滑避免概率值為0的情況

下面是完整程式碼:

import numpy as np
from enum import Enum

#從文件中讀取資料,每條資料轉成列表的形式
def readData(path):
    dataList = []
    with open(path,'r') as f:
        dataSet = f.readlines()

    for d in dataSet:
        d = d[:-1]
        d = d.split(',')
        print(d)
        dataList.append(d)

    return dataList

# http://archive.ics.uci.edu/ml/datasets/Car+Evaluation
# Class Values:
# unacc, acc, good, vgood
#
# Attributes:
# buying: vhigh, high, med, low.
# maint: vhigh, high, med, low.
# doors: 2, 3, 4, 5more.
# persons: 2, 4, more.
# lug_boot: small, med, big.
# safety: low, med, high.

#對映屬性值,方便程式碼處理
Cls = {'unacc':0, 'acc':1, 'good':2, 'vgood':3}   #分類值對映
#特徵值對映,共6個特徵值,每個特徵表示為X[i],X[i][xiv]表示特徵Xi的取值。
X = [{'vhigh':0, 'high':1, 'med':2, 'low':3},
         {'vhigh':0, 'high':1, 'med':2, 'low':3},
         {'2':0, '3':1, '4':2, '5more':3},
         {'2':0, '4':1, 'more':2},
         {'small':0, 'med':1, 'big':2},
         {'low':0, 'med':1, 'high':2}]

#訓練模型,生成概率矩陣即生成p{Y=yi}和 p{Xi=xiv|Y=yi}
def NBtrain(labelData):
    datanum = len(labelData)

    Arr = np.zeros((4,6,4))   #Arr[y][xi][xiv]表示在分類y的條件下,特徵Xi取值為xiv的數量。
    for d in labelData:
        y = Cls[d[-1]]     #取分類的對映值
        for i in range(len(d)-1):
            v = X[i][d[i]]    #取每個特徵的對映值
            Arr[y][i][v] +=1  #計數

    probXCY = np.zeros((4,6,4))  #probXCY[y][xi][xiv]表示在分類y的條件下,特徵Xi取值為xiv的概率即p{Xi=xiv|Y=y}
    numY = []         #分類為yi的數量
    probY =[]         #分類為yi的概率

    for y in Cls.values():
        numY.append(np.sum(Arr[y][0]))
        probY.append( (numY[y]+1)/(datanum+len(Cls)) )  #做拉普拉斯平滑
        print(y, numY[y], probY[y])
        for xi in range(len(X)):
            s = len(X[xi])    #特徵Xi的值的個數。
            for xiv in X[xi].values():
                probXCY[y][xi][xiv] = (Arr[y][xi][xiv]+1)/(numY[y]+s)   #做拉普拉斯平滑避免概率值為0的情況

[3]/numY[0])

    # print(Arr)
    # print(probXCY)
    return probXCY,probY


def NBclassify(probXCY,probY,predData):
    unknowData = predData
    datanum = len(unknowData)
    YofX = []    #記錄資料的分類
    diffNum = 0  #記錄分類結果與實際不同的數量
    for d in unknowData:
        probyCx = []    #記錄p{Y=yi|X[...]=x[...]}
        for y in Cls.values():
            p = 10**5   #概率偏移,防止計算得到的數值過小
            for xi in range(len(X)):
                xiv = X[xi][d[xi]]    #取對映值
                p *= probXCY[y][xi][xiv]
            p *= probY[y]    # p{X1=x1|Y=y} * p{X2=x2|Y=y} *...* p{Xn=xn|Y=y} * p{y}
            probyCx.append(p)

        YofX.append(probyCx.index(max(probyCx)))  #max( p{Y=yi|X[...]=x[...]} )即取概率最大的那個分類yi為該資料的分類

        #分類記錄
        print(d)
        print("分類為:",YofX[-1])
        if(YofX[-1] != Cls[d[-1]]):
            diffNum +=1
            #print(probyCx)
            print("真實分類為:",Cls[d[-1]])
        else:
            print("分類正確")


    print("錯誤數:",diffNum,"\t資料量:",datanum)
    print("錯誤率:",diffNum/datanum)

    return YofX

#測試:
dS = readData('car.data.txt')
probXCY,probY = NBtrain(dS)
NBclassify(probXCY,probY,dS)

執行輸出結果為:

...
...
...
['low', 'low', '5more', 'more', 'big', 'med', 'good']
分類為: 2
分類正確
['low', 'low', '5more', 'more', 'big', 'high', 'vgood']
分類為: 3
分類正確
錯誤數: 223    資料量: 1728
錯誤率: 0.12905092592592593