一、概述
1.1 简介
朴素贝叶斯(Naive Bayesian)是基于贝叶斯定理和特征条件独立假设的分类方法,它通过特征计算分类的概率,选取概率大的情况进行分类,因此它是基于概率论的一种机器学习分类方法。因为分类的目标是确定的,所以也是属于监督学习。
Q1:什么是基于概率论的方法?
通过概率来衡量事件发生的可能性。概率论和统计学恰好是两个相反的概念,统计学是抽取部分样本进行统计来估算总体的情况,而概率论是通过总体情况来估计单个事件或者部分事情的发生情况。因此,概率论需要已知的数据去预测未知的事件。
例如,我们看到天气乌云密布,电闪雷鸣并阵阵狂风,在这样的天气特征(F)下,我们推断下雨的概率比不下雨的概率大,也就是p(下雨)>p(不下雨)p(下雨)>p(不下雨),所以认为待会儿会下雨。这个从经验上看对概率进行判断。
而气象局通过多年长期积累的数据,经过计算,今天下雨的概率p(下雨)=85%,p(不下雨)=15%p(下雨)=85%,p(不下雨)=15%,同样的,p(下雨)>p(不下雨)p(下雨)>p(不下雨),因此今天的天气预报肯定预报下雨。这是通过一定的方法计算概率从而对下雨事件进行判断。
Q2:朴素贝叶斯,朴素在什么地方?
之所以叫朴素贝叶斯,因为它简单、易于操作,基于特征独立性假设,假设各个特征不会相互影响,这样就大大减小了计算概率的难度。
1.2 条件概率与贝叶斯定理
(1)概率论中几个基本概念
事件交和并:
A和B两个事件的交,指的是事件A和B同时出现,记为A∩B;
A和B两个事件的并,指的是事件A和事件B至少出现一次的情况,记为A∪B。
互补事件:
事件A的补集,也就是事件A不发生的时候的事件,记为 A c A^c Ac。这个时候,要么A发生,要么 A c A^c Ac发生, P ( A ) + P ( A c ) = 1 P(A)+P(A^c)=1 P(A)+P(Ac)=1。
条件概率(conditional probability):
某个事件发生时另外一个事件发生的概率,如事件B发生条件下事件A发生的概率:
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B)=\frac{P(A\cap B)}{P(B)} P(A∣B)=P(B)P(A∩B)
概率的乘法法则(multiplication rule of probability):
P ( A ∩ B ) = P ( A ) P ( A ∣ B ) o r P ( A ∩ B ) = P ( B ) P ( A ∣ B ) P(A\cap B)=P(A)P(A|B)orP(A\cap B)=P(B)P(A|B) P(A∩B)=P(A)P(A∣B)orP(A∩B)=P(B)P(A∣B)
独立事件交的概率:
两个相互独立的事件,其交的概率为:
P ( A ∩ B ) = P ( A ) P ( B ) P(A\cap B)=P(A)P(B) P(A∩B)=P(A)P(B)
(2)贝叶斯定理(Bayes’s Rule):
如果有k个互斥且有穷个事件
B 1 , B 2 , … , B k B_1,B_2,\dots,B_k B1,B2,…,Bk,并且, P ( B 1 ) + P ( B 2 ) + ⋯ + P ( B k ) = 1 P(B_1)+P(B_2)+\dots+P(B_k)=1 P(B1)+P(B2)+⋯+P(Bk)=1和一个可以观测到的事件A,那么有:
P ( B i ∣ A ) = P ( B i ∩ A ) P ( A ) = P ( B i ) P ( A ∣ B i ) P ( B i ) P ( A ∣ B i ) + P ( B 2 ) P ( A ∣ B 2 ) + ⋯ + P ( B k ) P ( A ∣ B k ) P(B_i|A)=\frac{P(B_i\cap A)}{P(A)}=\frac{P(B_i)P(A|B_i)}{P(B_i)P(A|B_i)+P(B_2)P(A|B_2)+\dots+P(B_k)P(A|B_k)} P(Bi∣A)=P(A)P(Bi∩A)=P(Bi)P(A∣Bi)+P(B2)P(A∣B2)+⋯+P(Bk)P(A∣Bk)P(Bi)P(A∣Bi)
p
(
A
)
:
事
件
A
发
生
的
概
率
p(A):事件A发生的概率
p(A):事件A发生的概率
p
(
A
∩
B
)
:
事
件
A
和
事
件
B
同
时
发
生
的
概
率
p(A\cap B):事件A和事件B同时发生的概率
p(A∩B):事件A和事件B同时发生的概率
p
(
A
∣
B
)
:
表
示
事
件
A
在
事
件
B
发
生
的
条
件
下
发
生
的
概
率
p(A|B):表示事件A在事件B发生的条件下发生的概率
p(A∣B):表示事件A在事件B发生的条件下发生的概率
1.3 朴素贝叶斯分类的原理
朴素贝叶斯基于条件概率、贝叶斯定理和独立性假设原则
(1)首先,我们来看条件概率原理:
基于概率论的方法告诉我们,当只有两种分类时:
如果
p
1
(
x
,
y
)
>
p
2
(
x
,
y
)
p1(x,y)>p2(x,y)
p1(x,y)>p2(x,y),那么分入类别1
如果
p
1
(
x
,
y
)
<
p
2
(
x
,
y
)
p1(x,y)<p2(x,y)
p1(x,y)<p2(x,y),那么分入类别2
(2)其次,贝叶斯定理
同样的道理,引入贝叶斯定理,有:
p ( c i ∣ x , y ) = p ( x , y ∣ c i ) p ( c i ) p ( x , y ) p(c_i|x,y)=\frac{p(x,y|c_i)p(c_i)}{p(x,y)} p(ci∣x,y)=p(x,y)p(x,y∣ci)p(ci)
其中, x , y x,y x,y表示特征变量, c i c_i ci表示分类, p ( c i ∣ x , y ) p(c_i|x,y) p(ci∣x,y)即表示在特征为 x , y x,y x,y的情况下分入类别 c i c_i ci的概率,因此,结合条件概率和贝叶斯定理,有:
-
如果 p ( c 1 ∣ x , y ) > p ( c 2 ∣ x , y ) p(c_1|x,y)>p(c_2|x,y) p(c1∣x,y)>p(c2∣x,y),那么分类应当属于 c 1 c_1 c1;
-
如果 p ( c 1 ∣ x , y ) < p ( c 2 ∣ x , y ) p(c_1|x,y)<p(c_2|x,y) p(c1∣x,y)<p(c2∣x,y),那么分类应当属于 c 2 c_2 c2;
-
贝叶斯定理最大的好处是可以用已知的三个概率去计算未知的概率,而如果仅仅是为了比较 p ( c i ∣ x , y ) 和 p ( c j ∣ x , y ) p(c_i|x,y)和p(c_j|x,y) p(ci∣x,y)和p(cj∣x,y)的大小,只需要已知两个概率即可,分母相同,比较 p ( x , y ∣ c i ) p ( c i ) p(x,y|c_i)p(c_i) p(x,y∣ci)p(ci)和 p ( x , y ∣ c j ) p ( c j ) p(x,y|c_j)p(c_j) p(x,y∣cj)p(cj)即可。
(3)特征条件独立假设原则
朴素贝叶斯最常见的分类应用是对文档进行分类,因此,最常见的特征条件是文档中,出现词汇的情况,通常将词汇出现的特征条件用词向量
ω
ω
ω表示,由多个数值组成,数值的个数和训练样本集中的词汇表个数相同。
因此,上述的贝叶斯条件概率公式可表示为:
p ( c i ∣ w ) = p ( ω ∣ c i ) p ( c i ) p ( ω ) p(c_i|w)=\frac{p(\omega|c_i)p(c_i)}{p(\omega)} p(ci∣w)=p(ω)p(ω∣ci)p(ci)
前面提到朴素贝叶斯还有一个假设,就是基于特征条件独立的假设,也就是我们姑且认为词汇表中各个单词独立出现,不会相互影响,因此, p ( ω ∣ c i ) p(\omega|c_i) p(ω∣ci)可以将 ω \omega ω展开成独立事件概率相乘的形式,因此:
p ( ω ∣ c i ) = p ( ω 0 ∣ c i ) p ( ω 1 ∣ c i ) p ( ω 2 ∣ c i ) … p ( ω N ∣ c i ) p(\omega|c_i)=p(\omega_0|c_i)p(\omega_1|c_i)p(\omega_2|c_i)\dots p(\omega_N|c_i) p(ω∣ci)=p(ω0∣ci)p(ω1∣ci)p(ω2∣ci)…p(ωN∣ci)
1.4 朴素贝叶斯分类的流程和优缺点
(1)分类流程
- 数据准备:收集数据,并将数据预处理为数值型或者布尔型,如对文本分类,需要将文本解析为词向量
- 训练数据:根据训练样本集计算词项出现的概率,训练数据后得到各类下词汇出现概率的向量
- 测试数据:用测试样本集去测试分类的准确性
(2) 优缺点
- 监督学习,需要确定分类的目标
- 对缺失数据不敏感,在数据较少的情况下依然可以使用该方法
- 可以处理多个类别 的分类问题
- 适用于标称型数据
- 对输入数据的形势比较敏感
- 由于用先验数据去预测分类,因此存在误差
二、Python算法实现
以在线社区的留言板评论为例,运用朴素贝叶斯分类方法,对文本进行自动分类。
构造一些实验样本,包括已经切分词条的文档集合,并且已经分类(带有侮辱性言论,和正常言论)。为了获取方便,先构造一个loadDataSet函数来生成实验样本。
def loadDataSet():
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec=[0,1,0,1,0,1] #1表示侮辱性言论,0表示正常言论
return postingList,classVec
2.1 根据文档词汇表构建词向量
(1)构建词汇表生成函数creatVocabList:
def createVocabList(dataSet):
vocabSet=set([])
for document in dataSet:
vocabSet=vocabSet|set(document) #取两个集合的并集
return list(vocabSet)
(2)对输入的词汇表构建词向量:
#词集模型
def setOfWords2Vec(vocabList,inputSet):
returnVec=np.zeros(len(vocabList)) #生成零向量的array
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]=1 #有单词,则该位置填充0
else: print('the word:%s is not in my Vocabulary!'% word)
return returnVec #返回全为0和1的向量
这种构建词向量的方法,只记录了每个词是否出现,而没有记录词出现的次数,这样的模型叫做词集模型,如果在词向量中记录词出现的次数,没出现一次,则多记录一次,这样的词向量构建方法,被称为词袋模型,下面构建以一个词袋模型的词向量生成函数bagOfWord2VecMN:
#词袋模型
def bagOfWords2VecMN(vocabList,inputSet):
returnVec=[0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]+=1
return returnVec #返回非负整数的词向量
import numpy as np
listPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listPosts)
myVocabList
[‘garbage’,
‘love’,
‘posting’,
‘steak’,
‘stupid’,
‘please’,
‘not’,
‘my’,
‘dalmation’,
‘I’,
‘worthless’,
‘help’,
‘ate’,
‘licks’,
‘how’,
‘buying’,
‘food’,
‘to’,
‘has’,
‘problems’,
‘dog’,
‘mr’,
‘flea’,
‘take’,
‘stop’,
‘quit’,
‘him’,
‘is’,
‘so’,
‘maybe’,
‘cute’,
‘park’]
setOfWords2Vec(myVocabList,listPosts[0])
array([0., 0., 0., 0., 0., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 0., 0.,
0., 1., 1., 1., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
setOfWords2Vec(myVocabList,listPosts[1])
array([0., 0., 0., 0., 1., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
1., 0., 0., 1., 0., 0., 1., 0., 0., 1., 0., 0., 1., 0., 1.])
2.2 运用词向量计算概率
再看前文提到的朴素贝叶斯的原理,要计算词向量 ω = ( ω 0 , ω 1 , ω 2 , . . . ω N , ) ω=(ω_0,ω_1,ω_2,...ω_N,) ω=(ω0,ω1,ω2,...ωN,),落入 c i c_i ci类别下的概率:
p ( c i ∣ ω ) = p ( ω ∣ c i ) p ( c i ) p ( ω ) p(c_i|\omega)=\frac{p(\omega|c_i)p(c_i)}{p(\omega)} p(ci∣ω)=p(ω)p(ω∣ci)p(ci)
p ( c i ) p(c_i) p(ci)好求,用样本集中, c i c_i ci的数量/样本总数即可
p ( ω ∣ c i ) p(\omega|c_i) p(ω∣ci)由各个条件特征互相独立且地位相同, p ( ω ∣ c i ) = p ( ω 0 ∣ c i ) p ( ω 1 ∣ c i ) … p ( ω N ∣ c i ) p(\omega|c_i)=p(\omega_0|c_i)p(\omega_1|c_i)\dots p(\omega_N|c_i) p(ω∣ci)=p(ω0∣ci)p(ω1∣ci)…p(ωN∣ci),可以分别求 p ( ω o ∣ c i ) , p ( ω 1 ∣ c i ) , p ( ω 2 ∣ c i ) , … , p ( ω N ∣ c i ) p(\omega_o|c_i),p(\omega_1|c_i),p(\omega_2|c_i),\dots,p(\omega_N|c_i) p(ωo∣ci),p(ω1∣ci),p(ω2∣ci),…,p(ωN∣ci),从而得到 p ( ω ∣ c i ) p(\omega|c_i) p(ω∣ci)
而求
p
(
ω
k
∣
c
i
)
p(\omega_k|c_i)
p(ωk∣ci)也就编程了求在分类类别为
c
i
c_i
ci的文档词汇表集合中,单个词
w
k
w_k
wk出现的概率,也就是
p
(
w
k
∣
c
i
)
=
w
k
在
c
i
中
出
现
的
次
数
c
i
中
词
总
数
p(w_k|c_i)=\frac{w_k在c_i中出现的次数}{c_i中词总数}
p(wk∣ci)=ci中词总数wk在ci中出现的次数
因此计算出现概率大致有这么一些流程:
用Python代码实现,创建函数TrainNB:
def trainNB0(trainMatrix,trainCategory):
numTrainDocs=len(trainMatrix) #文档数目
numWord=len(trainMatrix[0]) #词汇表词数目
pAbusive=sum(trainCategory)/len(trainCategory) #p1,出现侮辱性评论的概率
p0Num=np.zeros(numWord);p1Num=np.zeros(numWord)
p0Demon=0;p1Demon=0
for i in range(numTrainDocs):
if trainCategory[i]==0:
p0Num+=trainMatrix[i] #向量相加
p0Demon+=sum(trainMatrix[i]) #向量中1累加求和
else:
p1Num+=trainMatrix[i]
p1Demon+=sum(trainMatrix[i])
p0Vec=p0Num/p0Demon
p1Vec=p1Num/p1Demon
return p0Vec,p1Vec,pAbusive
解释:
- pAbusive=sum(trainCategory)/len(trainCategory),表示文档集中分类为1的文档数目,累加求和将词向量中所有1相加,len求长度函数则对所有0和1进行计数,最后得到分类为1的概率
- p0Num+=trainMatrix[i];p0Demon+=sum(trainMatrix[i]),前者是向量相加,其结果还是向量,trainMatrix[i]中是1的位置全部加到p0Num中,后者是先求和(该词向量中词项的数目),其结果是数值,表示词项总数。
- p0Vec=p0Num/p0Demon,向量除以数值,结果是向量,向量中每个元素都除以该数值。
测试:对构建的朴素贝叶斯分类器训练函数进行测试:
trainMat=[]
for postinDoc in listPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0v,p1v,pAb=trainNB0(trainMat,listClasses)
#myVocabList中第2个词汇是"Love",即myVocabList[1]='Love'
p0v[1]
0.041666666666666664
p1v[1]
0.0
#myVocabList中第5个词汇是"stupid",即myVocabList[4]='stupid'
p0v[4]
0.0
p1v[4]
0.15789473684210525
从结果我们看到,侮辱性文档出现的概率是0.5,词项’love’在侮辱性文档中出现的概率是0,在正常言论中出现的概率是0.042;词项‘stupid’在正常言论中出现的概率是0,在侮辱性言论中出现的规律是0.158.
-
算法漏洞:
- 乘积为0
我们看到,当某分类下某词项出现频次为0时,其概率也是0,因此在计算 p ( w 0 ∣ c i ) p ( w 1 ∣ c i ) p ( w 2 ∣ c i ) . . . . . . p ( w N ∣ c i ) p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)......p(w_N|c_i) p(w0∣ci)p(w1∣ci)p(w2∣ci)......p(wN∣ci)会因为其中某个的概率为0而全部是0。
为了避免这样的情况发生,我们将所有词项出现的频次都初始化为1,某类所有词项数量初始化为2。
- 因子太小导致结果溢出问题
由于 p ( w 0 ∣ c i ) p ( w 1 ∣ c i ) p ( w 2 ∣ c i ) . . . . . . p ( w N ∣ c i ) p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)......p(w_N|c_i) p(w0∣ci)p(w1∣ci)p(w2∣ci)......p(wN∣ci)中每个因子都很小,所有因子相乘,特别是因子数量多的时候,会导致结果溢出,从而得到错误的数据
避免溢出问题的发生,可以使用求自然对数的方法,自然对数和原本的数值同增同减,不会有任何损失,因此不会影响求得的概率结果。
因此,将朴素贝叶斯分类器函数修改为:
def trainNB1(trainMatrix,trainCategory):
numTrainDocs=len(trainMatrix)
numWord=len(trainMatrix[0])
pAbusive=sum(trainCategory)/len(trainCategory)
p0Num=np.ones(numWord);p1Num=np.ones(numWord)# 初始化为1
p0Demon=2;p1Demon=2 #初始化为2
for i in range(numTrainDocs):
if trainCategory[i]==0:
p0Num+=trainMatrix[i]
p0Demon+=sum(trainMatrix[i])
else:
p1Num+=trainMatrix[i]
p1Demon+=sum(trainMatrix[i])
p0Vec=np.log(p0Num/p0Demon) #对结果求对数
p1Vec=np.log(p1Num/p1Demon) #对结果求自然对数
return p0Vec,p1Vec,pAbusive
2.3 运用分类器函数对文档进行分类
前文概率论讲到,计算文档在各类中的概率,取较大者作为该文档的分类,所以构建分类函数classifyNB:
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1-pClass1)
if p1>p0:
return 1
else:
return 0
说明:
p1=sum(vec2Classifyp1Vec)+log(pClass1) 的数学原理是ln(ab)=ln(a) +ln(b)
接下来构造几个样本,来测试分类函数:
def testingNB():
listPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listPosts)
trainMat=[]
for postinDoc in listPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB1(trainMat,listClasses)
testEntry=['love','my','dalmation']
thisDoc=setOfWords2Vec(myVocabList,testEntry)
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry=['stupid','garbage']
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
testingNB()
[‘love’, ‘my’, ‘dalmation’] classified as: 0
[‘stupid’, ‘garbage’] classified as: 1