朴素贝叶斯模型是机器学习中常常提到的概念。可是相信不少朋友都是知其然而不知其因此然。本文将尽可能使用易懂的方式介绍朴素贝叶斯模型原理,而且经过具体应用场景和源码来帮助你们深刻理解这个概念。html
已知m个样本 (x1,y1), ...... (xm,ym),x是特征变量,y是对应的类别。要求得一个模型函数或者映射规则h,对于新的样本 xt,可以尽可能准确的预测出 yt = h(xt)。java
咱们也能够从几率的角度来考虑一下上述问题。假设y有m个类别,即 y1,......yn ∈ {C1,......Cm},对于样本 xt,若是能计算出每一个类别的条件几率 P(C1|xt),......P(Cm|xt),那么能够认为几率最大的那个类别就是 xt 所属的类别。算法
h叫作分类器。分类算法的任务就是构造分类器h。机器学习
朴素贝叶斯(Naive Bayes)算法理论基础是基于贝叶斯定理和条件独立性假设的一种分类方法。朴素的意思是假设各个特征之间相互条件独立的。函数
贝叶斯分类器的基本方法:在统计资料的基础上,依据找到的一些特征属性,来计算各个类别的几率,找到几率最大的类,从而实现分类。即贝叶斯分类器经过预测一个对象属于某个类别的几率,再预测其类别。学习
找到一个已知分类的待分类项集合,这个集合叫作训练样本集。google
找出最大几率的那个类。spa
前文呼延灼的方法是:
求解问题(A): 呼延灼想知道本身是不是公明哥哥的心腹,用A来表明"你是大哥的心腹"。
已知结果(B): 大哥对你下拜。记做事件B。
推理结果 P(A|B): 想经过大哥对你下拜这个事件,来判断大哥视你为心腹的几率。.net
因而有:code
P(A|B) = P(B|A)P(A)/P(B) P(A|B) 也就是在B事件"大哥下拜"发生以后,对A事件"大哥视你为心腹"几率的从新评估。
其实上述公式也隐含着:经过贝叶斯公式可以把人分红两类:大哥的心腹 / 普通下属。
因此贝叶斯公式能够用类别的思路从新解读。
咱们把 B 理解成“具备某特征”,把A理解成“类别标签”。在最简单的二分类问题(是与否断定)下,咱们将A 理解成“属于某类”的标签。
P(类别|特征)=P(特征|类别)P(类别)/P(特征)
以前只假设A只有B一个条件, 但在实际应用中,不多有一件事只受一个特征影响的状况,每每影响一件事的因素有多个。假设,影响 B 的因素有 n 个,分别是 b1,b2,…,bn。
则 P(A|B) 能够写为:
P(A|b1,b2,...,bn) = P(A) P(b1,b2,...,bn|A) / P(b1,b2,...,bn)
由于假设从 b1 到 bn 这些特征之间,在几率分布上是条件独立的,也就是说每一个特征 bi与其余特征都不相关。因此能够作以下转换
P(b1,b2,...,bn|A) = P(b1|A)P(b2|A)...P(bn|A)
这个转换其实就是 独立变量的联合分布 = 各变量先验分布的乘积。只不过这里是条件几率,可是由于变换先后都有一样的条件 A,从样本空间 A 的角度看,其实就是联合分布转换成先验分布的乘积。
因此贝叶斯定理能够作以下推导
P(A|b1,b2,...,bn) = P(A) [P(b1|A)P(b2|A)...P(bn|A)] / P(b1,b2,...,bn)
话说在前文[白话解析] 深刻浅出贝叶斯定理中,呼延灼经过贝叶斯定理,推出了本身不是公明哥哥心腹的结论。虽然有些气闷,可是也好奇于贝叶斯定理的威力,因而他就决定用朴素贝叶斯模型对马军头领和步军头领进行分类。
目前有一个极简版朴素贝叶斯分类模型,能区分出两个类(A1, A2),用来分类的特征也有两个(B1, B2)。
因此公式为:
P(A|B1,B2) = P(A) [P(B1|A)P(B2|A)] / P(B1,B2)
这个就是分类器:
P(A|B1,B2) = P(A) [P(B1|A)P(B2|A)] / P(B1,B2) = P(A) [P(B1|A)P(B2|A)] / [P(B1) P(B2)] b1,b2表示特征变量,Ai表示分类,p(Ai|b1,b2)表示在特征为b1,b2的状况下分入类别Ai的几率
再重温下朴素贝叶斯分类器,经过预测一个对象属于某个类别的几率,再预测其类别。
找到一个已知分类的待分类项集合,这个集合叫作训练样本集。
找出最大几率的那个类。
样本是10位马军头领, 10位步兵头领,如今设定以下:
已知有两个分类: A1=马军头领 A2=步军头领 两个用来分类的特征: F1=纹身 F2=闹事 特征能够以下取值: f11 = 有纹身 f12 = 无纹身 f21 = 爱闹事 f22 = 不爱闹事
有了分类器模型和预制条件,下面就看如何推导出分类器模型参数了。
如下是根据已知数据统计得来。就是由实际数值训练出来的 分类器参数。
假定 马军头领中,2位有纹身,1位爱闹事,步兵头领中,7位有纹身,6位爱闹事。因此获得统计数据以下: P(有纹身) = P(f11) = (7+2)/20 = 9/20 = 0.45 P(无纹身) = P(f12) = 11/20 = 0.55 P(爱闹事) = P(f21) = 7/20 = 0.35 P(不爱闹事) = P(f22) = 13/20 = 0.65 P(F1=f11|A=A1) = P(有纹身|马军头领) = 2/20 = 0.1 P(F1=f12|A=A1) = P(无纹身|马军头领) = 8/20 = 0.4 P(F1=f11|A=A2) = P(有纹身|步兵头领) = 7/20 = 0.35 P(F1=f12|A=A2) = P(无纹身|步兵头领) = 3/20 = 0.15 P(F2=f21|A=A1) = P(爱闹事|马军头领) = 1/20 = 0.05 P(F2=f22|A=A1) = P(不爱闹事|马军头领) = 9/20 = 0.45 P(F2=f21|A=A2) = P(爱闹事|步兵头领) = 6/20 = 0.3 P(F2=f22|A=A2) = P(不爱闹事|步兵头领) = 4/20 = 0.2
这样就训练(统计)出来了一个分类器模型的参数。
能够结合以前的分类器
P(A|F1,F2) = P(A) [P(F1|A)P(F2|A)] / P(F1,F2) = P(A) [P(F1|A)P(F2|A)] / [P(F1) P(F2)]
来对 "待分类数据" 作处理了。
若是有某位头领 x:不纹身,不闹事。进行针对两个分类(马军头领,步兵头领)进行两次运算,得出两个数值。
(不纹身,不闹事)是马军头领的可能性
P(马军头领|不纹身,不闹事) = P(马军头领) [P(无纹身|马军头领) P(不闹事|马军头领) ] / [P(无纹身)P(不闹事)] P(A=A1|x) = p(A=A1) P(F1=f12|A=A1)p(F2=f22|A=A1) / [P(f12)P(f22)] = 0.5 * 0.4 * 0.45 / [0.55 * 0.65] = 0.18 / [0.55 * 0.65] = 0.25
(不纹身,不闹事)是步兵头领的可能性
P(步兵头领|不纹身,不闹事) = P(步兵头领) [P(无纹身|步兵头领) P(不闹事|步兵头领) ] / [P(无纹身)P(不闹事)] P(A=A2|x) = p(A=A2) P(F1=f12|A=A2)p(F2=f22|A=A2) / [P(f12)P(f22)] = 0.5 * 0.15 * 0.2 / [0.55 * 0.65] = 0.03 / [0.55 * 0.65] = 0.04
因此x是马军的可能性更大。
贝叶斯定理最大的好处是能够用已知的频率去计算未知的几率,咱们 简单地将频率当成了几率。
咱们能够经过snowNLP的源码来对朴素贝叶斯模型再进一步理解。
在bayes对象中,有两个属性d和total,d是一个数据字典,total存储全部分类的总词数,通过train方法训练数据集后,d中存储的是每一个分类标签的数据key为分类标签,value是一个AddOneProb对象。
这里的代码就是简单地将频率当成了几率。训练就是统计各个分类标签(key)所对应的个数。
#训练数据集 def train(self, data): #遍历数据集,data 中既包含正样本,也包含负样本 for d in data: # data中是list # d[0]:分词的结果,list # d[1]:标签-->分类类别,正/负样本的标记 c = d[1] #判断数据字典中是否有当前的标签 if c not in self.d: #若是没有该标签,加入标签,值是一个AddOneProb对象 self.d[c] = AddOneProb() # 类的初始化 #d[0]是评论的分词list,遍历分词list for word in d[0]: #调用AddOneProb中的add方法,添加单词 self.d[c].add(word, 1) #计算总词数,是正类和负类之和 self.total = sum(map(lambda x: self.d[x].getsum(), self.d.keys())) # # 取得全部的d中的sum之和 class AddOneProb(BaseProb): def __init__(self): self.d = {} self.total = 0.0 self.none = 1 #添加单词 def add(self, key, value): #更新该类别下的单词总数 self.total += value #若是单词未出现过,需新建key if not self.exists(key): #将单词加入对应标签的数据字典中,value设为1 self.d[key] = 1 #更新总词数 self.total += 1 #若是单词出现过,对该单词的value值加1 self.d[key] += value
具体分类则是计算各个分类标签的几率
#贝叶斯分类 def classify(self, x): tmp = {} #遍历每一个分类标签 for k in self.d: # 正类和负类 #获取每一个分类标签下的总词数和全部标签总词数,求对数差至关于log(某标签下的总词数/全部标签总词数) tmp[k] = log(self.d[k].getsum()) - log(self.total) # 正类/负类的和的log函数-全部之和的log函数 for word in x: #获取每一个单词出现的频率,log[(某标签下的总词数/全部标签总词数)*单词出现频率] tmp[k] += log(self.d[k].freq(word)) #计算几率 ret, prob = 0, 0 for k in self.d: now = 0 try: for otherk in self.d: now += exp(tmp[otherk]-tmp[k]) now = 1/now except OverflowError: now = 0 if now > prob: ret, prob = k, now return (ret, prob)
对于有两个类别c1,c1的分类问题来讲,其特征为w1,⋯,wn,特征之间是相互独立的,属于类别c1的贝叶斯模型的基本过程为:
P(c1∣w1,⋯,wn)=P(w1,⋯,wn∣c1)⋅P(c1) / P(w1,⋯,wn) 若是作句子分类,能够认为是出现了w1, w2, ..., wn这些词以后,该句子被概括到c1类的几率。
其中:
P(w1,⋯,wn)=P(w1,⋯,wn∣c1)⋅P(c1) + P(w1,⋯,wn∣c2)⋅P(c2)
预测的过程使用到了上述的公式,即:
\[ P(c1∣w1,⋯,wn)=\frac{P(w1,⋯,wn∣c1)⋅P(c1)}{P(w1,⋯,wn∣c1)⋅P(c1)+P(w1,⋯,wn∣c2)⋅P(c2)} \]
对上述的公式简化:
\[ P(c1∣w1,⋯,wn)=\frac{P(w1,⋯,wn∣c1)⋅P(c1)}{P(w1,⋯,wn∣c1)⋅P(c1)+P(w1,⋯,wn∣c2)⋅P(c2)} \]
\[ =\frac{1}{1+\frac{P(w1,⋯,wn∣c2)⋅P(c2)}{P(w1,⋯,wn∣c1)⋅P(c1)}} \]
\[ =\frac{1}{1+exp[log(\frac{P(w1,⋯,wn∣c2)⋅P(c2)}{P(w1,⋯,wn∣c1)⋅P(c1)})]} \]
\[ =\frac{1}{1+exp[log(P(w1,⋯,wn∣c2)⋅P(c2))−log(P(w1,⋯,wn∣c1)⋅P(c1))]} \]
其中,分母中的1能够改写为:
\[ 1=exp[log(P(w1,⋯,wn∣c1)⋅P(c1))−log(P(w1,⋯,wn∣c1)⋅P(c1))] \]
根据上面的公式,针对c1, c2,咱们须要
\[ P(w1,⋯,wn∣c1)⋅P(c1) \]
\[ P(c1) \]
结合代码
p(Ck) = k这类词出现的几率 = self.d[k].getsum() / self.total p(w1|Ck) = w1这个词在Ck类出现的几率 = self.d[k].freq(word) k = 1,2
\[ log(P(w1,⋯,wn∣c1)⋅P(c1)) \]
这个公式就是
\[ log(P(w1|c1)...p(wn∣c1)⋅P(c1)) \]
这个公式的结果就是:
\[ log(sum_{p(w1|C1)...p(wn|C1)}) + log(P(c1)) \]
最后展开:
\[ log(sum_{p(w1|C1)...p(wn|C1)}) + log(self.d[1].getsum()) - log(self.total)) \]
这个就是下面的 tmp[k]。其中,第一个for循环中的tmp[k]对应了公式中的log(P(ck)),第二个for循环中的tmp[k]对应了公式中的log(P(w1,⋯,wn∣ck)⋅P(ck))。两个for循环的结果就是最终的tmp[k]。
def classify(self, x): tmp = {} for k in self.d: # 正类和负类 tmp[k] = log(self.d[k].getsum()) - log(self.total) # 正类/负类的和的log函数-全部之和的log函数 for word in x: tmp[k] += log(self.d[k].freq(word)) # 词频,不存在就为0 ret, prob = 0, 0 for k in self.d: now = 0 try: for otherk in self.d: now += exp(tmp[otherk]-tmp[k]) # for循环中有一个结果是0, exp(0)就是1.就是上面分母中的1 now = 1/now except OverflowError: now = 0 if now > prob: ret, prob = k, now return (ret, prob)