機器學習算法系列(五)
本篇主要講解了貝葉斯分類器的原理、事例,及樸素貝葉斯分類器、EM演算法(座標上升法)及基於python的樸素貝葉斯實現。
**
4、貝葉斯分類器:
**
貝葉斯推斷,其實就是求條件概率
它建立在主觀判斷的基礎上,也就是說,你可以不需要客觀證據,先估計一個值,然後根據實際結果不斷修正。
條件概率公式:
事例簡介:兩個盤A1/A2分別裝有10個紅球R、20個白球W及15個紅球R、15個白球W;
問當在2盤隨機取出一個紅球時來自盤1的概率:即P(A1 | R);(這一個值我們是可以通過全概率公式計算出來的,具體細節不在推導)
對於P(A1)先驗概率肯定是0.5(因為只有2個盤各佔1/2),而如何求出真實的後驗概率P(A1 | R)
優化
對於貝葉斯分類器如下,求樣本x的類別c需要先求出P(x | c)樣本x相對於類標記c的類條件概率,這表示的求的是樣本x所有屬性上的聯合概率,而對於此所要求的值難以從有限的訓練樣本直接估計而得,為避開這個障礙引入了樸素貝葉斯分類器;
樸素貝葉斯分類器:對於貝葉斯分類器的難點—於有限的樣本上求得所有屬性的聯合概率,樸素貝葉斯對於樣本x進行屬性上的分解(其假設樣本集的所有屬性之間相互獨立),於是P(x | c)可以寫為:
這樣,為每個樣本估計類條件概率分解為每個樣本的每個屬性
在這裡需要注意一個問題,因為根據其上概率計算式可以發現其最後樣本概率為所有屬性概率值的乘積,即若有一個屬性的概率值為0則此樣本概率值直接為0,而當某個屬性在某一個類別c中沒有出現時那麼其屬性概率值一定為0,而這直接導致樣本值在此類別c上概率值直接為0顯然這是不合理的;
因此在估計概率值時,常常用進行平滑(smoothing)處理如拉普拉斯修正(Laplacian correction):
如上處理之後,不論分子上的屬性值是否出現,其加上1之後都不會成為0所以對於最終樣本的概率值影響將會減少;
當訓練集越來越大時即如上式中的分母D就越大,則N的影響相對減小即拉普拉斯修正的引入影響越來越小;
在前面的討論當中,我們一直假設訓練樣本所有屬性變數的值都已被觀測到,即訓練樣本是完整的;然而實際情況卻非都如此,當訓練樣本的某些屬性未知時,即存在未觀測變數的情形下,如何對模型的引數進行估計呢?下面引入EM演算法。
EM(Expectation-Maximization)演算法是一種常用的估計引數或隱變數的利器,也稱為“期望最大演算法”,EM演算法是一種迭代型的演算法,在每一次的迭代過程中,主要分為兩步:即求期望(Expectation)步驟和最大化(Maximization)步驟。
可以理解為座標上升法:
圖中的直線式迭代優化的路徑,可以看到每一步都會向最優值前進一步,而且前進路線是平行於座標軸的,因為每一步只優化一個變數。
這猶如在x-y座標系中找一個曲線的極值,然而曲線函式不能直接求導,因此什麼梯度下降方法就不適用了。但固定一個變數後,另外一個可以通過求導得到,因此可以使用座標上升法,一次固定一個變數,對另外的求極值,最後逐步逼近極值。對應到EM上,E步:固定θ,優化Q;M步:固定Q,優化θ;交替將極值推向最大。
下面是python實現的樸素貝葉斯分類器,且採用了拉普拉斯平滑處理:
class NBClassify(object):
def __init__(self, fillNa = 1):
self.fillNa = 1
pass
def train(self, trainSet):
# 計算每種類別的概率
# 儲存所有tag的所有種類,及它們出現的頻次
dictTag = {}
for subTuple in trainSet:
dictTag[str(subTuple[1])] = 1 if str(subTuple[1]) not in dictTag.keys() else dictTag[str(subTuple[1])] + 1
# 儲存每個tag本身的概率
tagProbablity = {}
totalFreq = sum([value for value in dictTag.values()])
for key, value in dictTag.items():
tagProbablity[key] = value / totalFreq
# print(tagProbablity)
self.tagProbablity = tagProbablity
##############################################################################
# 計算特徵的條件概率
# 儲存特徵屬性基本資訊{特徵1:{值1:出現5次, 值2:出現1次}, 特徵2:{值1:出現1次, 值2:出現5次}}
dictFeaturesBase = {}
for subTuple in trainSet:
for key, value in subTuple[0].items():
if key not in dictFeaturesBase.keys():
dictFeaturesBase[key] = {value:1}
else:
if value not in dictFeaturesBase[key].keys():
dictFeaturesBase[key][value] = 1
else:
dictFeaturesBase[key][value] += 1
dictFeatures = {}.fromkeys([key for key in dictTag])
for key in dictFeatures.keys():
dictFeatures[key] = {}.fromkeys([key for key in dictFeaturesBase])
for key, value in dictFeatures.items():
for subkey in value.keys():
value[subkey] = {}.fromkeys([x for x in dictFeaturesBase[subkey].keys()])
# initialise dictFeatures
for subTuple in trainSet:
for key, value in subTuple[0].items():
dictFeatures[subTuple[1]][key][value] = 1 if dictFeatures[subTuple[1]][key][value] == None else dictFeatures[subTuple[1]][key][value] + 1
# print(dictFeatures)
# 將訓練樣本中沒有的專案,由None改為一個非常小的數值,表示其概率極小而並非是零
for tag, featuresDict in dictFeatures.items():
for featureName, fetureValueDict in featuresDict.items():
for featureKey, featureValues in fetureValueDict.items():
if featureValues == None:
fetureValueDict[featureKey] =0.01
# 由特徵頻率計算特徵的條件概率P(feature|tag)
for tag, featuresDict in dictFeatures.items():
for featureName, fetureValueDict in featuresDict.items():
totalCount = sum([x for x in fetureValueDict.values() if x != None])
for featureKey, featureValues in fetureValueDict.items():
fetureValueDict[featureKey] = featureValues/totalCount if featureValues != None else None
self.featuresProbablity = dictFeatures
##############################################################################
def classify(self, featureDict):
resultDict = {}
# 計算每個tag的條件概率
for key, value in self.tagProbablity.items():
iNumList = []
for f, v in featureDict.items():
if self.featuresProbablity[key][f][v]:
iNumList.append(self.featuresProbablity[key][f][v])
conditionPr = 1
for iNum in iNumList:
conditionPr *= iNum
resultDict[key] = value * conditionPr
# 對比每個tag的條件概率的大小
resultList = sorted(resultDict.items(), key=lambda x:x[1], reverse=True)
return resultList
if __name__ == '__main__':
trainSet = [
({"症狀":"打噴嚏", "職業":"護士"}, "感冒 "),
({"症狀":"打噴嚏", "職業":"農夫"}, "過敏 "),
({"症狀":"頭痛", "職業":"建築工人"}, "腦震盪"),
({"症狀":"頭痛", "職業":"建築工人"}, "感冒 "),
({"症狀":"打噴嚏", "職業":"教師"}, "感冒 "),
({"症狀":"頭痛", "職業":"教師"}, "腦震盪"),
]
monitor = NBClassify()
# trainSet is something like that [(featureDict, tag), ]
monitor.train(trainSet)
# 打噴嚏的建築工人
# 請問他患上感冒的概率有多大?
result = monitor.classify({"症狀":"打噴嚏", "職業":"建築工人"})
print(result)
上面這個程式例子還是非常不錯的,其核心分為兩步,一是由特徵頻率計算特徵的條件概率P(feature|tag),即如上的self.featuresProbablity = dictFeatures;另一個就是分類了,classify(self, featureDict)包括各屬性概率求積以及最後再乘以類別概率;這兩步也就是貝葉斯分類器的核心思想了,計算理解起來其實是非常簡單的;然後就是加上平滑處理,對於沒有值的屬性類別上面賦值了0.01;下面是這兩步的結果截圖更有利於核心思想的理解:
及最終相關案例的執行結果:
參考了許多講解及案例,有些理解的並不是十分明朗,還需後續溫故知新。