統計學習方法樸素貝葉斯法(附簡單模型程式碼)
樸素貝葉斯(naïve Bayes) 法是基於貝葉斯定理與特徵條件獨立假設的分類方法。對於給定的訓練資料集, 首先基於特徵條件獨立假設學習輸入/輸出的聯合概率分佈; 然後基於此模型, 對給定的輸入x, 利用貝葉斯定理求出後驗概率最大的輸出y。 樸素貝葉斯法實現簡單, 學習與預測的效率都很高, 是一種常用的方法。
1. 樸素貝葉斯法的學習與分類
基本方法
訓練資料集:
由X和Y的聯合概率分佈P(X,Y)獨立同分布產生
樸素貝葉斯通過訓練資料集學習聯合概率分佈P(X,Y) ,
即先驗概率分佈:
及條件概率分佈:
注意: 條件概率為指數級別的引數:
條件獨立性假設:
“樸素” 貝葉斯名字由來, 犧牲分類準確性。
貝葉斯定理:
代入上式:這是樸素貝葉斯法分類的基本公式。 於是, 樸素貝葉斯分類器可表示為分母對所有ck都相同:
後驗概率最大化的含義
樸素貝葉斯法將例項分到後驗概率最大的類中, 等價於期望風險最小化,
假設選擇0-1損失函式: f(X)為決策函式
期望風險函式:
取條件期望:
只需對X=x逐個極小化, 得:
推匯出後驗概率最大化準則:
2. 樸素貝葉斯法的引數估計
應用極大似然估計法估計相應的概率,先驗概率P(Y=ck)的極大似然估計是:
設第j個特徵x(j)可能取值的集合為:
條件概率的極大似然估計:
學習與分類演算法Naïve Bayes Algorithm:
輸入:
訓練資料集:,其中
第i個樣本的第j個特徵,,是第j個特徵可能取的第l個值,j=1,2,…,n, l=1,2,…,Sj, yi∊{c1, c2,…,cK}。
輸出:
x的分類
步驟:
(1) 計算先驗概率及條件概率
(2) 對於給定的例項x=(x(1),x(2),…,x(n))T, 計算
(3) 確定例項x的類
3、簡單實現的程式碼
以下是天氣的打球的14條資訊:
有4個屬性會決定play,每個屬性又有若干個特徵,預測sunny,cool,high,TRUE是否會play.
結合上面的知識,本例子大概步驟如下:
(1)計算先驗概率(yes和no)
(2)計算每個屬性的特徵集合對應yes和no概率
(3)計算預測概率
下面是我寫的程式碼,沒有用第三方包寫的:
data_list = [
['sunny', 'hot', 'high', 'FALSE', 'no'],
['sunny', 'hot', 'high', 'TRUE', 'no'],
['overcast', 'hot', 'high', 'FALSE', 'yes'],
['rainy', 'mild', 'high', 'FALSE', 'yes'],
['rainy', 'cool', 'normal', 'FALSE', 'yes'],
['rainy', 'cool', 'normal', 'TRUE', 'no'],
['overcast', 'cool', 'normal', 'TRUE', 'yes'],
['sunny', 'mild', 'high', 'FALSE', 'no'],
['sunny', 'cool', 'normal', 'FALSE', 'yes'],
['rainy', 'mild', 'normal', 'FALSE', 'yes'],
['sunny', 'mild', 'normal', 'TRUE', 'yes'],
['overcast', 'mild', 'high', 'TRUE', 'yes'],
['overcast', 'hot', 'normal', 'FALSE', 'yes'],
['rainy', 'mild', 'high', 'TRUE', 'no']
]
# 計算出現次數
def get_count(indexs,attrs):
'''
indexs:待比較的索引列表
attrs:待比較的屬性列表
'''
count = 0
for i in data_list:
if len(indexs) == 1 and i[indexs[0]] == attrs[0]:
count += 1
else:
flag = True
for j in range(len(indexs)):
if i[indexs[j]] != attrs[j]:
flag = False
if flag:
count += 1
return count
# 計算先驗概率
yes_count = get_count([4],['yes'])
P_yes,P_no = yes_count/len(data_list),(len(data_list) - yes_count)/len(data_list)
print('先驗概率yes:%f,no:%f' %(P_yes,P_no))
# 計算每個屬性的特徵集合
attr_set_list = []
for i in range(4):
attr_set = []
for j in data_list:
attr_set.append(j[i])
attr_set_list.append(list(set(attr_set)))
print(attr_set_list)
# 計算每個屬性的特徵集合對應yes和no概率
predict_dict = {}
for i in range(len(attr_set_list)):
for j in attr_set_list[i]:
predict_dict[j] = [get_count([i,4],[j,'yes'])/yes_count,get_count([i,4],[j,'no'])/(len(data_list) - yes_count)]
print(predict_dict)
# 計算預測概率
def get_predict_p(predict_features):
p_yes,p_no = P_yes,P_no
for i in predict_features:
p_yes *= predict_dict[i][0]
p_no *= predict_dict[i][1]
return p_yes,p_no
predict_features = ['sunny','cool','high','TRUE']
print(get_predict_p(predict_features))
執行結果:
可以看到預測時yes的概率約為0.0053,no的概率約為0.0206。還原手算:
p_yes = 2/9×3/9×3/9×3/9×9/14=0.0053, p_no = 3/5×1/5×4/5×3/5×5/14=0.0206。驗證了上面的程式。
但是,問題並沒有解決,看上面的overcast,no的概率為0,即只要特徵有overcast就一定打球,這違背了樸素貝葉斯的基本假設:輸出依賴於所有的屬性。解決0概率的問題主要有平滑演算法,資料平滑的方法很多,最簡單的是拉普拉斯估計(Laplace estimator)--即在算每個feature的yes和no的概率時為每個特徵的計數都加1,每個特徵的計數加1時對應的yes和no也都加1,這是累加的,也就是說,每個屬性有多少個特徵,對應yes和no在計算時就加多少個。
其實要改動的程式碼不多,主要是在計算先驗概率和計算每個屬性的特徵集合對應yes和no概率時做處理,直接上程式碼:
data_list = [
['sunny', 'hot', 'high', 'FALSE', 'no'],
['sunny', 'hot', 'high', 'TRUE', 'no'],
['overcast', 'hot', 'high', 'FALSE', 'yes'],
['rainy', 'mild', 'high', 'FALSE', 'yes'],
['rainy', 'cool', 'normal', 'FALSE', 'yes'],
['rainy', 'cool', 'normal', 'TRUE', 'no'],
['overcast', 'cool', 'normal', 'TRUE', 'yes'],
['sunny', 'mild', 'high', 'FALSE', 'no'],
['sunny', 'cool', 'normal', 'FALSE', 'yes'],
['rainy', 'mild', 'normal', 'FALSE', 'yes'],
['sunny', 'mild', 'normal', 'TRUE', 'yes'],
['overcast', 'mild', 'high', 'TRUE', 'yes'],
['overcast', 'hot', 'normal', 'FALSE', 'yes'],
['rainy', 'mild', 'high', 'TRUE', 'no']
]
# 計算出現次數
def get_count(indexs,attrs):
'''
indexs:待比較的索引列表
attrs:待比較的屬性列表
'''
count = 0
for i in data_list:
if len(indexs) == 1 and i[indexs[0]] == attrs[0]:
count += 1
else:
flag = True
for j in range(len(indexs)):
if i[indexs[j]] != attrs[j]:
flag = False
if flag:
count += 1
return count
# 計算先驗概率
yes_count = get_count([4],['yes']) + 1
P_yes,P_no = yes_count/(len(data_list)+2),(len(data_list) + 2 - yes_count)/(len(data_list)+ 2)
print('先驗概率yes:%f,no:%f' %(P_yes,P_no))
# 計算每個屬性的特徵集合
attr_set_list = []
for i in range(4):
attr_set = []
for j in data_list:
attr_set.append(j[i])
attr_set_list.append(list(set(attr_set)))
print(attr_set_list)
# 計算每個屬性的特徵集合對應yes和no概率
predict_dict = {}
for i in range(len(attr_set_list)):
for j in attr_set_list[i]:
predict_dict[j] = [(get_count([i,4],[j,'yes'])+1)/(yes_count+len(attr_set_list[i]) - 1),(get_count([i,4],[j,'no'])+1)/(len(data_list) - yes_count + 1 +len(attr_set_list[i]))]
print(predict_dict)
# 計算預測概率
def get_predict_p(predict_features):
p_yes,p_no = P_yes,P_no
for i in predict_features:
p_yes *= predict_dict[i][0]
p_no *= predict_dict[i][1]
return p_yes,p_no
predict_features = ['sunny','cool','high','TRUE']
print(get_predict_p(predict_features))
為了方便,我在計算先驗概率直接手動給yes_count加了1,所以後面在計算每個屬性的特徵集合對應yes和no概率時要減去1。
結果:
此時就沒有了0概率,讓我們也手動驗證一下:
此處strong就是TRUE,可見程式結果是正確的。
以上內容均出自李航老師的《統計學習方法》。