本篇主要讲解了贝叶斯分类器的原理、事例,及朴素贝叶斯分类器、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),我们是可以通过全概率公式计算出来的,当我们计算出其真实值的时候与0.5比较大小,即可得到当取出的是红球时候来自盘1的概率是被增强了还是削弱了。
优化
对于贝叶斯分类器如下,求样本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;下面是这两步的结果截图更有利于核心思想的理解:
及最终相关案例的运行结果:
参考了许多讲解及案例,有些理解的并不是十分明朗,还需后续温故知新。