1. 程式人生 > >統計學習方法樸素貝葉斯法(附簡單模型程式碼)

統計學習方法樸素貝葉斯法(附簡單模型程式碼)

樸素貝葉斯(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,可見程式結果是正確的。

以上內容均出自李航老師的《統計學習方法》。