機器學習——樸素貝葉斯演算法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