朴素贝叶斯(naive Bayes)算法是基于贝叶斯定理与特征条件独立假设的分类方法,对于给定的训练数据集,首先基于特征条件独立假设学习输入/输出的联合概率分布,然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y,朴素贝叶斯法实现简单,学习与预测的效率都很高,是一种常见的方法。
朴素贝叶斯(naive Bayes)算法是有监督的学习算法,解决的是分类问题,如客户是否流失、是否值得投资、信用等级评定等多分类问题。该算法的优点在于简单易懂、学习效率高、在某些领域的分类问题中能够与决策树、神经网络相媲美。但由于该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。
朴素贝叶斯是贝叶斯决策理论的一部分,所以首先了解一下贝叶斯理论。
假设现在我们有一个数据集,它由两类数据组成,数据分布如下图所示:
我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。
适用决策树不会非常成功,和简单的概率计算相比,KNN计算量太大,因此对于上述问题,最佳选择是概率比较方法。
已经了解了贝叶斯决策理论的核心思想,那么接下来,就是学习如何计算p1和p2概率。
在学习计算p1和p2概率之前,我们需要了解什么是条件概率(Condittional probability),就是指在事件B发生的情况下,事件A发生的概率,用 P(A|B) P ( A | B ) 来表示。
除了条件概率以外,在计算p1和p2的时候,还要用到全概率公式,因此,这里继续推导全概率公式。
P(B)=P(B⋂A)+P(B⋂A ′ ) P ( B ) = P ( B ⋂ A ) + P ( B ⋂ A ′ )其含义为:如果 A A 和 A ′ A ′ 构成一个样本空间的一个划分,那么事件 B B 的概率就等于 A A 和 A ′ A ′ 的概率分别乘以 B B 对这两个事件的条件概率之和。
于是条件概率就有了另一种写法:
P(A|B)=P(B|A)P(A)P(B|A)P(A)+P(B|A ′ )P(A ′ ) P ( A | B ) = P ( B | A ) P ( A ) P ( B | A ) P ( A ) + P ( B | A ′ ) P ( A ′ )
对条件概率进行变形,可以得到如下形式:
P(A|B)=P(A)P(B|A)P(B) P ( A | B ) = P ( A ) P ( B | A ) P ( B )
我们把P(A)称为”先验概率”(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为”后验概率”(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为”可能性函数”(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
所以,条件概率可以理解成下面的式子:
后验概率=先验概率∗调整因子 后 验 概 率 = 先 验 概 率 ∗ 调 整 因 子
这就是贝叶斯推断的含义:我们先预估一个”先验概率”,然后加入实验结果,看这个实验到底是增强还是削弱了”先验概率”,由此得到更接近事实的”后验概率”。
在这里,如果”可能性函数”P(B|A)/P(B)>1,意味着”先验概率”被增强,事件A的发生的可能性变大;如果”可能性函数”=1,意味着B事件无助于判断事件A的可能性;如果”可能性函数”<1,意味着”先验概率”被削弱,事件A的可能性变小。
“朴素”的解释:假设各个特征之间相互独立(在贝叶斯分类器上做了简化)
朴素贝叶斯的基础假设:
①每个特征相互独立;
②每个特征的权重(或重要性)都相等,即对结果的影响程度都相同。
朴素贝叶斯具体实现步骤:
举例说明:
某个医院早上来了六个门诊的病人,他们的情况如下表所示:
症状 | 职业 | 疾病 |
---|---|---|
打喷嚏 | 护士 | 感冒 |
打喷嚏 | 农夫 | 过敏 |
头痛 | 建筑工人 | 脑震荡 |
头痛 | 建筑工人 | 感冒 |
打喷嚏 | 教师 | 感冒 |
头痛 | 教师 | 脑震荡 |
现在又来了第七个病人,是一个打喷嚏的建筑工人。请问他患上感冒的概率有多大?
根据贝叶斯定理:
P(A|B)=P(B|A)P(A)P(B) P ( A | B ) = P ( B | A ) P ( A ) P ( B )
可得:
P(感冒|打喷嚏&建筑工人)=P(打喷嚏&建筑工人|感冒)P(感冒) / P(打喷嚏&建筑工人)
根据朴素贝叶斯条件独立假设可知,打喷嚏和建筑工人两个特征是独立的,所以:
P(感冒|打喷嚏&建筑工人)=P(打喷嚏|感冒)*P(建筑工人|感冒*P(感冒) / P(打喷嚏&建筑工人)
P(感冒|打喷嚏&建筑工人)=0.66*0.33*0.5/(0.5*0.33)=0.66
因此,这个打喷嚏的建筑工人,有66%的概率是得了感冒。同理,可以计算这个病人患上过敏或脑震荡的概率。比较这几个概率,就可以知道他最可能得什么病。
这就是贝叶斯分类器的基本方法:在统计资料的基础上,依据某些特征,计算各个类别的概率,从而实现分类。
同样,在编程的时候,如果不需要求出所属类别的具体概率,P(打喷嚏) = 0.5和P(建筑工人) = 0.33的概率是可以不用求的。
朴素贝叶斯是上节介绍的贝叶斯分类器的一个扩展,是用于文档分类的常用算法。
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。
我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。编写代码如下:
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
if __name__=='__main__':
postingList,classVec=loadDataSet()
for each in postingList:
print(each)
print(classVec)
结果:
创建一个词汇表,并将切好的词条转化为词条向量
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList,inputSet):
#创建一个其中所含元素都为0的向量
returnVec=[0]*len(vocabList)
#遍历每个词条
for word in inputSet:
#如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:print("the word: %s is not in my Vocabulary!" % word)
#返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
#创建一个空的不重复列表
vocabSet=set([])
for document in dataSet:
#取并集
vocabSet=vocabSet|set(document)
return list(vocabSet)
if __name__=='__main__':
postingList,classVec=loadDataSet()
print('postingList:\n',postingList)
myVocabList=createVocabList(postingList)
print('myVocabList:\n',myVocabList)
trainMat=[]
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
print('trainMat:\n',trainMat)
结果:
从运行结果可以看出,postingList是原始的词条列表,myVocabList是词汇表。myVocabList是所有单词出现的集合,没有重复的元素。词汇表是用来干什么的?没错,它是用来将词条向量化的,一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。trainMat是所有的词条向量组成的列表。它里面存放的是根据myVocabList向量化的词条向量。
我们已经得到了词条向量。接下来,我们就可以通过词条向量训练朴素贝叶斯分类器。
import numpy as np
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList,inputSet):
#创建一个其中所含元素都为0的向量
returnVec=[0]*len(vocabList)
#遍历每个词条
for word in inputSet:
#如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:print("the word: %s is not in my Vocabulary!" % word)
#返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
#创建一个空的不重复列表
vocabSet=set([])
for document in dataSet:
#取并集
vocabSet=vocabSet|set(document)
return list(vocabSet)
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix:训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory:训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect:侮辱类的条件概率数组 p1Vect:非侮辱类的条件概率数组 pAbusive:文档属于侮辱类的概率 Modify: 2018-03-14 """
def trainNB0(trainMtrix,trainCategory):
#计算训练的文档数目
numTrainDocs=len(trainMtrix)
#计算每篇文章的词条数
numWords=len(trainMtrix[0])
#文档属于侮辱类的概率
pAbusive=sum(trainCategory)/float(numTrainDocs)
#创建numpy.zeros数组
p0Num=np.zeros(numWords);p1Num=np.zeros(numWords)
#分母初始化为0.0
p0Denom=0.0;p1Denom=0.0
for i in range(numTrainDocs):
#统计属于侮辱类的条件概率
if trainCategory[i]==1:
p1Num+=trainMtrix[i]
p1Denom+=sum(trainMtrix[i])
#统计属于非侮辱类的条件概率
else:
p0Num+=trainMtrix[i]
p0Denom+=sum(trainMtrix[i])
#相除
p1Vect=p1Num/p1Denom
p0Vect=p0Num/p1Denom
#返回属于侮辱类的条件概率
return p0Vect,p1Vect,pAbusive
if __name__=='__main__':
postingList,classVec=loadDataSet()
myVocabList=createVocabList(postingList)
print('myVocabList:\n',myVocabList)
trainMat=[]
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(trainMat,classVec)
print('p0V:\n',p0V)
print('p1V:\n', p1V)
print('classVec:\n', classVec)
print('pAb:\n', pAb)
p0V存放的是每个单词属于类别0,也就是非侮辱类词汇的概率
p1V存放的就是各个单词属于侮辱类的条件概率。pAb就是先验概率。
已经训练好分类器,接着用分类器进行分类
import numpy as np
from functools import reduce
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList, inputSet):
# 创建一个其中所含元素都为0的向量
returnVec = [0] * len(vocabList)
# 遍历每个词条
for word in inputSet:
# 如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
# 返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
# 创建一个空的不重复列表
vocabSet = set([])
for document in dataSet:
# 取并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix:训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory:训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect:侮辱类的条件概率数组 p1Vect:非侮辱类的条件概率数组 pAbusive:文档属于侮辱类的概率 Modify: 2018-03-14 """
def trainNB0(trainMtrix, trainCategory):
# 计算训练的文档数目
numTrainDocs = len(trainMtrix)
# 计算每篇文章的词条数
numWords = len(trainMtrix[0])
# 文档属于侮辱类的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 创建numpy.zeros数组
p0Num = np.zeros(numWords);
p1Num = np.zeros(numWords)
# 分母初始化为0.0
p0Denom = 0.0;
p1Denom = 0.0
for i in range(numTrainDocs):
# 统计属于侮辱类的条件概率
if trainCategory[i] == 1:
p1Num += trainMtrix[i]
p1Denom += sum(trainMtrix[i])
# 统计属于非侮辱类的条件概率
else:
p0Num += trainMtrix[i]
p0Denom += sum(trainMtrix[i])
# 相除
p1Vect = p1Num / p1Denom
p0Vect = p0Num / p1Denom
# 返回属于侮辱类的条件概率
return p0Vect, p1Vect, pAbusive
""" 函数说明:朴素贝叶斯分类器分类函数 Parameters: vec2Classifyaaa:待分类的词条数组 p0Vec:侮辱类的条件概率数组 p1Vec:非侮辱类的条件概率数组 pClass1:文档属于侮辱类的概率 Returns: 0 :属于非侮辱类 1 :属于侮辱类 Modify: 2018-03-14 """
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
#对应元素相乘
p1=reduce(lambda x,y:x*y,vec2Classify*p1Vec)*pClass1
p0=reduce(lambda x,y:x*y,vec2Classify*p0Vec)*(1.0-pClass1)
print('p0:',p0)
print('p1:',p1)
if p1>p0:
return 1
else:
return 0
""" 函数说明:测试朴素贝叶斯分类器 Parameters: 无 Returns: 无 Modify: 2018-03-14 """
def testingNB():
#创建实验样本
listOPosts,listClasses=loadDataSet()
#创建词汇表
myVocabList=createVocabList((listOPosts))
trainMat=[]
for postinDoc in listOPosts:
#将实验样本向量化
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
#训练朴素贝叶斯分类器
p0V,p1V,pAb=trainNB0(np.array(trainMat),np.array(listClasses))
#测试样本1
testEntry=['love','my','dalmation']
#测试样本向量化
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
#执行分类并打印分类结果
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
# 执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类')
#测试样本2
testEntry=['stupid','garbage']
#测试样本向量化
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
# 执行分类并打印分类结果
if classifyNB(thisDoc, p0V, p1V, pAb):
print(testEntry, '属于侮辱类')
# 执行分类并打印分类结果
else:
print(testEntry, '属于非侮辱类')
if __name__ == '__main__':
testingNB()
我们发现,p0和p1的计算结果都是0,下面来探讨产生该结果的问题。
朴素贝叶斯推断的一些优点:
朴素贝叶斯推断的一些缺点:
1)零概率问题
造成原因:
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|A)p(w1|A)p(w2|A) p ( w 0 | A ) p ( w 1 | A ) p ( w 2 | A ) ,如果其中有一个为0,则最后的结果也为0。
解决方法:
为了降低这种影响,可以将所有词的出现次数初始化为1,并将分母初始化为2,这种做法称为“拉普拉斯平滑”,也称“加1平滑”,是比较常用的平滑方法,为了解决0概率问题。
2)下溢出
造成的原因:
是太多很小的数相乘,越乘越小,就造成了下溢出的问题。在相应小数位置进行四舍五入,计算结果可能就变成0了。
解决方法:
对乘积结果取自然对数,通过求对数可以避免下溢出或者浮点数舍入导致的错误,同时,采用自然对数进行处理不会有任何损失。
上图显示了 f(x) f ( x ) 和 lnf(x) l n f ( x ) 的曲线,可以看出上面两条曲线同增同减,且最大值处相同,取值虽然不同,但是不影响最终结果。
因此,可以对trainNB0
函数进行修改:
import numpy as np
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList,inputSet):
#创建一个其中所含元素都为0的向量
returnVec=[0]*len(vocabList)
#遍历每个词条
for word in inputSet:
#如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:print("the word: %s is not in my Vocabulary!" % word)
#返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
#创建一个空的不重复列表
vocabSet=set([])
for document in dataSet:
#取并集
vocabSet=vocabSet|set(document)
return list(vocabSet)
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix:训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory:训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect:侮辱类的条件概率数组 p1Vect:非侮辱类的条件概率数组 pAbusive:文档属于侮辱类的概率 Modify: 2018-03-14 """
def trainNB0(trainMtrix, trainCategory):
# 计算训练的文档数目
numTrainDocs = len(trainMtrix)
# 计算每篇文章的词条数
numWords = len(trainMtrix[0])
# 文档属于侮辱类的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 创建numpy.ones数组,词条初始化为1,拉普拉斯平滑
p0Num = np.ones(numWords);
p1Num = np.ones(numWords)
# 分母初始化为2.0,拉普拉斯平滑
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
# 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
if trainCategory[i] == 1:
p1Num += trainMtrix[i]
p1Denom += sum(trainMtrix[i])
# 统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
else:
p0Num += trainMtrix[i]
p0Denom += sum(trainMtrix[i])
# 相除
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p1Denom)
# 返回属于侮辱类的条件概率
return p0Vect, p1Vect, pAbusive
if __name__=='__main__':
postingList,classVec=loadDataSet()
myVocabList=createVocabList(postingList)
print('myVocabList:\n',myVocabList)
trainMat=[]
for postinDoc in postingList:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(trainMat,classVec)
print('p0V:\n',p0V)
print('p1V:\n', p1V)
print('classVec:\n', classVec)
print('pAb:\n', pAb)
结果:
此时已经不存在零概率了。
对classifyNB进行修改:
import numpy as np
from functools import reduce
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList, inputSet):
# 创建一个其中所含元素都为0的向量
returnVec = [0] * len(vocabList)
# 遍历每个词条
for word in inputSet:
# 如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
# 返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
# 创建一个空的不重复列表
vocabSet = set([])
for document in dataSet:
# 取并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix:训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory:训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect:侮辱类的条件概率数组 p1Vect:非侮辱类的条件概率数组 pAbusive:文档属于侮辱类的概率 Modify: 2018-03-14 """
def trainNB0(trainMtrix, trainCategory):
# 计算训练的文档数目
numTrainDocs = len(trainMtrix)
# 计算每篇文章的词条数
numWords = len(trainMtrix[0])
# 文档属于侮辱类的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 创建numpy.ones数组,词条初始化为1,拉普拉斯平滑
p0Num = np.ones(numWords);
p1Num = np.ones(numWords)
# 分母初始化为2.0,拉普拉斯平滑
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
# 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
if trainCategory[i] == 1:
p1Num += trainMtrix[i]
p1Denom += sum(trainMtrix[i])
# 统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
else:
p0Num += trainMtrix[i]
p0Denom += sum(trainMtrix[i])
# 相除
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p1Denom)
# 返回属于侮辱类的条件概率
return p0Vect, p1Vect, pAbusive
""" 函数说明:朴素贝叶斯分类器分类函数 Parameters: vec2Classifyaaa:待分类的词条数组 p0Vec:侮辱类的条件概率数组 p1Vec:非侮辱类的条件概率数组 pClass1:文档属于侮辱类的概率 Returns: 0 :属于非侮辱类 1 :属于侮辱类 Modify: 2018-03-14 """
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
#对应元素相乘,logA*B=logA+logB,所以要加上np.log(pClass1)
p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
""" 函数说明:测试朴素贝叶斯分类器 Parameters: 无 Returns: 无 Modify: 2018-03-14 """
def testingNB():
#创建实验样本
listOPosts,listClasses=loadDataSet()
#创建词汇表
myVocabList=createVocabList((listOPosts))
trainMat=[]
for postinDoc in listOPosts:
#将实验样本向量化
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
#训练朴素贝叶斯分类器
p0V,p1V,pAb=trainNB0(np.array(trainMat),np.array(listClasses))
#测试样本1
testEntry=['love','my','dalmation']
#测试样本向量化
thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
#执行分类并打印分类结果
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类')
# 执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类')
#测试样本2
testEntry=['stupid','garbage']
#测试样本向量化
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
# 执行分类并打印分类结果
if classifyNB(thisDoc, p0V, p1V, pAb):
print(testEntry, '属于侮辱类')
# 执行分类并打印分类结果
else:
print(testEntry, '属于非侮辱类')
if __name__ == '__main__':
testingNB()
结果:
朴素贝叶斯的最著名的应用——电子邮件垃圾过滤
步骤:
有两个文件夹,ham和spam,spam文件下的txt文件为垃圾邮件。
对于英文文本,我们可以以非字母、非数字作为符号进行切分,使用split函数即可。编写代码如下:
import re
""" 函数说明:接收一个大字符串并将其解析为字符串列表 Parameters: 无 Returns: 无 Modify: 2018-03-14 """
def textParse(bigString):
# 将特殊符号作为切分标志进行字符串切分,即非字母、非数字
listOfTokens = re.split(r'\W*', bigString)
# 除了单个字母,例如大写的I,其它单词变成小写
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet - 整理的样本数据集 Returns: vocabSet - 返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
# 创建一个空的不重复列表
vocabSet = set([])
for document in dataSet:
# 取并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
if __name__ == '__main__':
docList = []; classList = []
# 遍历25个txt文件
for i in range(1, 26):
# 读取每个垃圾邮件,并字符串转换成字符串列表
wordList = textParse(open('spam/%d.txt' % i, 'r').read())
docList.append(wordList)
# 标记垃圾邮件,1表示垃圾文件
classList.append(1)
# 读取每个非垃圾邮件,并字符串转换成字符串列表
wordList = textParse(open('ham/%d.txt' % i, 'r').read())
docList.append(wordList)
# 标记非垃圾邮件,1表示垃圾文件
classList.append(0)
# 创建词汇表,不重复
vocabList = createVocabList(docList)
print(vocabList)
词汇表结果:
根据词汇表,可以将每个文本向量化,此处将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性,代码如下:
import numpy as np
import re
import random
""" 函数说明:创建实验样本 Parameters: 无 Returns: postingList:实验样本切分的词条 classVec:类别标签向量 Modify: 2018-03-14 """
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']]
# 类别标签向量,1代表侮辱性词汇,0代表不是
classVec = [0, 1, 0, 1, 0, 1]
return postingList, classVec
""" 函数说明:更加vocabList词汇表,将inputSet向量化,向量的每个元素为1或0 Parameters: vocabList:createVocabList返回的列表 inputSet:切分的词条列表 Returns: returnVec:文档向量,词集模型 Modify: 2018-03-14 """
def setOfWords2Vec(vocabList, inputSet):
# 创建一个其中所含元素都为0的向量
returnVec = [0] * len(vocabList)
# 遍历每个词条
for word in inputSet:
# 如果词条存在于词汇表中,则置1
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
# 返回向量文档
return returnVec
"""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet:整理的样本数据集 Returns: vocabSet:返回不重复的词条列表,也就是词汇表 Modify: 2018-03-14 """
def createVocabList(dataSet):
# 创建一个空的不重复列表
vocabSet = set([])
for document in dataSet:
# 取并集
vocabSet = vocabSet | set(document)
return list(vocabSet)
""" 函数说明:根据vocabList词汇表,构建词袋模型 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的词条列表 Returns: returnVec - 文档向量,词袋模型 Modify: 2018-03-14 """
def bagOfWords2VecMN(vocabList, inputSet):
# 创建一个其中所含元素都为0的向量
returnVec = [0] * len(vocabList)
# 遍历每个词条
for word in inputSet:
# 如果词条存在于词汇表中,则计数加一
if word in vocabList:
returnVec[vocabList.index(word)] += 1
# 返回词袋模型
return returnVec
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix:训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory:训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect:侮辱类的条件概率数组 p1Vect:非侮辱类的条件概率数组 pAbusive:文档属于侮辱类的概率 Modify: 2018-03-14 """
def trainNB0(trainMtrix, trainCategory):
# 计算训练的文档数目
numTrainDocs = len(trainMtrix)
# 计算每篇文章的词条数
numWords = len(trainMtrix[0])
# 文档属于侮辱类的概率
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 创建numpy.ones数组,词条初始化为1,拉普拉斯平滑
p0Num = np.ones(numWords);
p1Num = np.ones(numWords)
# 分母初始化为2.0,拉普拉斯平滑
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
# 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
if trainCategory[i] == 1:
p1Num += trainMtrix[i]
p1Denom += sum(trainMtrix[i])
# 统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
else:
p0Num += trainMtrix[i]
p0Denom += sum(trainMtrix[i])
# 相除
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p1Denom)
# 返回属于侮辱类的条件概率
return p0Vect, p1Vect, pAbusive
""" 函数说明:朴素贝叶斯分类器分类函数 Parameters: vec2Classifyaaa:待分类的词条数组 p0Vec:侮辱类的条件概率数组 p1Vec:非侮辱类的条件概率数组 pClass1:文档属于侮辱类的概率 Returns: 0 :属于非侮辱类 1 :属于侮辱类 Modify: 2018-03-14 """
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
# 对应元素相乘,logA*B=logA+logB,所以要加上np.log(pClass1)
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
""" 函数说明:接收一个大字符串并将其解析为字符串列表 Parameters: 无 Returns: 无 Modify: 2018-03-14 """
def textParse(bigString):
# 将特殊符号作为切分标志进行字符串切分,即非字母、非数字
listOfTokens = re.split(r'\W*', bigString)
# 除了单个字母,例如大写的I,其它单词变成小写
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
""" 函数说明:测试朴素贝叶斯分类器 Parameters: 无 Returns: 无 Modify: 2018-03-14 """
def spamTest():
docList = []
classList = []
fullText = []
# 遍历25个txt文件
for i in range(1, 26):
# 读取每个垃圾邮件,并字符串转换成字符串列表
wordList = textParse(open('spam/%d.txt' % i, 'r').read())
docList.append(wordList)
fullText.append(wordList)
# 标记非垃圾邮件,1表示垃圾邮件
classList.append(1)
# 读取每个非垃圾邮件,并字符串转换为字符串列表
wordList = textParse(open('spam/%d.txt' % i, 'r').read())
docList.append(wordList)
fullText.append(wordList)
# 标记非垃圾邮件,1表示垃圾邮件
classList.append(0)
# 创建词汇表,不重复
vocabList = createVocabList(docList)
# 创建存储训练集的索引值的列表和测试集的索引值的列表
trainingSet = list(range(50));
testSet = []
# 从50个邮件中,随机挑选出40个作为训练集,10个做测试集
for i in range(10):
# 随机选取索索引值
randIndex = int(random.uniform(0, len(trainingSet)))
# 添加测试集的索引值
testSet.append(trainingSet[randIndex])
# 在训练集列表中删除添加到测试集的索引值
del (trainingSet[randIndex])
# 创建训练集矩阵和训练集类别标签系向量
trainMat = []
trainClasses = []
# 遍历训练集
for docIndex in trainingSet:
# 将生成的词集模型添加到训练矩阵中
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
# 将类别添加到训练集类别标签系向量中
trainClasses.append(classList[docIndex])
# 训练朴素贝叶斯模型
p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))
# 错误分类计数
errorCount = 0
# 遍历测试集
for docIndex in testSet:
# 测试集的词集模型
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
# 如果分类错误,错误计数加1
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
if __name__ == '__main__':
spamTest()
函数spamTest()会输出在10封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。
可以直接使用第三方分词组件,即jieba,也就是”结巴”。
在anaconda prompt中输入:
pip install jieba
即可直接安装。
官方教程:https://github.com/fxsjy/jieba
中文教程:https://www.oschina.net/p/jieba
数据集的分类结果如下:
切分中文语句代码:
""" 函数说明:切分中文语句 """
import os
import jieba
def TextProcessing(folder_path):
#查看folder_path下的文件
folder_list=os.listdir(folder_path)
#训练集
data_list=[]
class_list=[]
#遍历每个子文件夹
for folder in folder_list:
#根据子文件夹,生成新的路径
new_folder_path=os.path.join(folder_path,folder)
#存放子文件夹下的txt文件的列表
files=os.listdir(new_folder_path)
j=1
#遍历每个txt文件
for file in files:
#每类txt样本数最多100个
if j>100:
break
#打开txt文件
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:
raw=f.read()
#精简模式,返回一个可迭代的generator
word_cut=jieba.cut(raw,cut_all=False)
#generator转换为list
word_list=list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j+=1
print(data_list)
print(class_list)
if __name__=='__main__':
#文本预处理
#训练集存放地址
folder_path='E:\python\machine learning in action\My Code\chap 04\SogouC\Sample'
TextProcessing((folder_path))
结果:
我们将所有文本分成训练集和测试集,并对训练集中的所有单词进行词频统计,并按降序排序。也就是将出现次数多的词语在前,出现次数少的词语在后进行排序。编写代码如下:
""" 函数说明:切分中文语句 """
import os
import jieba
import random
def TextProcessing(folder_path,test_size = 0.2):
#查看folder_path下的文件
folder_list=os.listdir(folder_path)
#训练集
data_list=[]
class_list=[]
#遍历每个子文件夹
for folder in folder_list:
#根据子文件夹,生成新的路径
new_folder_path=os.path.join(folder_path,folder)
#存放子文件夹下的txt文件的列表
files=os.listdir(new_folder_path)
j=1
#遍历每个txt文件
for file in files:
#每类txt样本数最多100个
if j>100:
break
#打开txt文件
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:
raw=f.read()
#精简模式,返回一个可迭代的generator
word_cut=jieba.cut(raw,cut_all=False)
#generator转换为list
word_list=list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j+=1
#zip压缩合并,将数据与标签对应压缩
data_class_list=list(zip(data_list,class_list))
#将data_class_list乱序
random.shuffle(data_class_list)
#训练集与测试集切分的索引值
index=int(len(data_class_list)*test_size)+1
#训练集
train_list=data_class_list[index:]
#测试集
test_list=data_class_list[:index]
#训练集解压缩
train_data_list,train_class_list=zip(*train_list)
#测试集解压缩
test_data_list,test_class_list=zip(*test_list)
#统计训练集词频
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键值倒序排列
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda
f:f[1],reverse=True)
#解压缩
all_words_list,all_words_nums=zip(*all_words_tuple_list)
#转换成列表
all_words_list=list(all_words_list)
return all_words_list,train_data_list,test_data_list,train_class_list,\
test_class_list
if __name__=='__main__':
#文本预处理,训练集存放的地址
folder_path='E:\python\machine learning in action\My Code\chap 04\SogouC\Sample'
all_words_list, train_data_list, test_data_list, train_class_list, \
test_class_list=TextProcessing(folder_path,test_size=0.2)
print(all_words_list)
结果:
输出的all_word_list
就是将所有训练集的切分结果按照词频降序排列构成的单词集合,前面包含了很多标点符号,和“是”、“的”、“在”等词语,及数字。所以要将这些去掉。
去掉的规则:去掉高频词,至于去掉多少,则根据高频词个数和最终检测率的关系来确定。
如何去掉:可以使用已经整理好的stopwords_cn.txt
文本:
可以根据这个文档来去掉高频词,不作为分类的特征,首先去除100个,代码如下:
""" 函数说明:切分中文语句 """
import os
import jieba
import random
def TextProcessing(folder_path,test_size = 0.2):
#查看folder_path下的文件
folder_list=os.listdir(folder_path)
#训练集
data_list=[]
class_list=[]
#遍历每个子文件夹
for folder in folder_list:
#根据子文件夹,生成新的路径
new_folder_path=os.path.join(folder_path,folder)
#存放子文件夹下的txt文件的列表
files=os.listdir(new_folder_path)
j=1
#遍历每个txt文件
for file in files:
#每类txt样本数最多100个
if j>100:
break
#打开txt文件
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:
raw=f.read()
#精简模式,返回一个可迭代的generator
word_cut=jieba.cut(raw,cut_all=False)
#generator转换为list
word_list=list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j+=1
#zip压缩合并,将数据与标签对应压缩
data_class_list=list(zip(data_list,class_list))
#将data_class_list乱序
random.shuffle(data_class_list)
#训练集与测试集切分的索引值
index=int(len(data_class_list)*test_size)+1
#训练集
train_list=data_class_list[index:]
#测试集
test_list=data_class_list[:index]
#训练集解压缩
train_data_list,train_class_list=zip(*train_list)
#测试集解压缩
test_data_list,test_class_list=zip(*test_list)
#统计训练集词频
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键值倒序排列
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda
f:f[1],reverse=True)
#解压缩
all_words_list,all_words_nums=zip(*all_words_tuple_list)
#转换成列表
all_words_list=list(all_words_list)
return all_words_list,train_data_list,test_data_list,train_class_list,\
test_class_list
""" 函数说明:读取文件中的内容并去重 Parameters: words_file:文件路径 Returns: word_set:读取内容的set集合 Modify: 2018-03-15 """
def MakeWordSet(words_file):
#创建set集合
words_set=set()
#打开文件
with open(words_file,'r',encoding='utf-8') as f:
#一行一行读取
for line in f.readlines():
#去回车
word=line.strip()
#有文本,则添加到word_set中
if len(word)>0:
words_set.add(word)
#返回处理结果
return words_set
""" 函数说明:文本特征提取 Parameters: all_words_list - 训练集所有文本列表 deleteN - 删除词频最高的deleteN个词 stopwords_set - 指定的结束语 Returns: feature_words - 特征集 Modify: 2018-03-15 """
def words_dict(all_words_list,deleteN,stopWords_set=set()):
#特征列表
feature_words=[]
n=1
for t in range(deleteN,len(all_words_list),1):
#feature_words额维度为1000
if n>1000:
break
#如果这个词不是数字,且不是指定的结束语,并且单词长度大于1小于5,那么这个词就可以作为特征词
if not all_words_list[t].isdigit() and all_words_list[t] not in stopWords_set \
and 15:
feature_words.append(all_words_list[t])
n+=1
return feature_words
if __name__=='__main__':
#文本预处理,训练集存放的地址
folder_path='E:\python\machine learning in action\My Code\chap 04\SogouC\Sample'
all_words_list, train_data_list, test_data_list, train_class_list, \
test_class_list=TextProcessing(folder_path,test_size=0.2)
#生成stopwords_set
stopwords_file='stopwords_cn.txt'
stopwords_set=MakeWordSet(stopwords_file)
feature_words=words_dict(all_words_list,100,stopwords_set)
print(feature_words)
结果:
从结果可以看出,我们已经滤除了那些没有用的词组,这些feature_words就是我们最终选出用于新闻分类的特征,随后就可以根据特征词将文本向量化,然后用于训练朴素贝叶斯分类器。
官方文档
在scikit-learn
中,一共有3个朴素贝叶斯的分类算法类。分别是GaussianNB
,MultinomialNB
和BernoulliNB
。
GaussianNB
就是先验为高斯分布的朴素贝叶斯;MultinomialNB
就是先验为多项式分布的朴素贝叶斯;BernoulliNB
就是先验为伯努利分布的朴素贝叶斯。前面所讲的的先验概率模型就是先验概率为多项式分布的朴素贝叶斯。
对于新闻的分类,属于多分类问题,可以使用MultinomialNB
来完成,假设特征的先验概率为多项式分布:
class sklearn.naive_bayes.MultinomialNB(alpha=1.0, fit_prior=True, class_prior=None)
fit_prior | class_prior | 最终先验概率 |
---|---|---|
False | 无意义 | P(Y=C k )=1/k P ( Y = C k ) = 1 / k |
True | 不填 | P(Y=C k )=mk/m P ( Y = C k ) = m k / m |
True | 填 | P(Y=C k )=class_prior P ( Y = C k ) = c l a s s _ p r i o r |
提供的方法:
拟合:
fit
:一般的拟合
partial_fit
:一般用在训练集数据量非常大,一次不能全部载入内存的时候,这个时候可以把训练集分成若干等分,重复调用该方法来一步步学习训练集。
预测:
predict
:常用的预测方法,直接给出测试集的预测类别输出
predict_log_proba
:预测出的各个类别对数概率里的最大值对应的类别,也就是predict
方法得到类别
predict_proba
:它会给出测试集样本在各个类别上预测的概率,预测出的各个类别概率里的最大值对应的类别,也就是predict
方法得到类别。
确定要去掉的前deleteN个高频词的个数与最终检测准确率的关系,确定deleteN的取值:
""" 函数说明:切分中文语句 """
import os
import jieba
import random
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt
def TextProcessing(folder_path,test_size = 0.2):
#查看folder_path下的文件
folder_list=os.listdir(folder_path)
#训练集
data_list=[]
class_list=[]
#遍历每个子文件夹
for folder in folder_list:
#根据子文件夹,生成新的路径
new_folder_path=os.path.join(folder_path,folder)
#存放子文件夹下的txt文件的列表
files=os.listdir(new_folder_path)
j=1
#遍历每个txt文件
for file in files:
#每类txt样本数最多100个
if j>100:
break
#打开txt文件
with open(os.path.join(new_folder_path,file),'r',encoding='utf-8') as f:
raw=f.read()
#精简模式,返回一个可迭代的generator
word_cut=jieba.cut(raw,cut_all=False)
#generator转换为list
word_list=list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j+=1
#zip压缩合并,将数据与标签对应压缩
data_class_list=list(zip(data_list,class_list))
#将data_class_list乱序
random.shuffle(data_class_list)
#训练集与测试集切分的索引值
index=int(len(data_class_list)*test_size)+1
#训练集
train_list=data_class_list[index:]
#测试集
test_list=data_class_list[:index]
#训练集解压缩
train_data_list,train_class_list=zip(*train_list)
#测试集解压缩
test_data_list,test_class_list=zip(*test_list)
#统计训练集词频
all_words_dict={}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word]+=1
else:
all_words_dict[word]=1
#根据键值倒序排列
all_words_tuple_list=sorted(all_words_dict.items(),key=lambda
f:f[1],reverse=True)
#解压缩
all_words_list,all_words_nums=zip(*all_words_tuple_list)
#转换成列表
all_words_list=list(all_words_list)
return all_words_list,train_data_list,test_data_list,train_class_list,\
test_class_list
""" 函数说明:读取文件中的内容并去重 Parameters: words_file:文件路径 Returns: word_set:读取内容的set集合 Modify: 2018-03-15 """
def MakeWordSet(words_file):
#创建set集合
words_set=set()
#打开文件
with open(words_file,'r',encoding='utf-8') as f:
#一行一行读取
for line in f.readlines():
#去回车
word=line.strip()
#有文本,则添加到word_set中
if len(word)>0:
words_set.add(word)
#返回处理结果
return words_set
def TextFeatures(train_data_list, test_data_list, feature_words):
# 出现在特征集中,则置1
def text_features(text, feature_words):
text_words = set(text)
features = [1 if word in text_words else 0 for word in feature_words]
return features
train_feature_list = [text_features(text, feature_words) for text in train_data_list]
test_feature_list = [text_features(text, feature_words) for text in test_data_list]
# 返回结果
return train_feature_list, test_feature_list
""" 函数说明:文本特征提取 Parameters: all_words_list - 训练集所有文本列表 deleteN - 删除词频最高的deleteN个词 stopwords_set - 指定的结束语 Returns: feature_words - 特征集 Modify: 2018-03-15 """
def words_dict(all_words_list,deleteN,stopWords_set=set()):
#特征列表
feature_words=[]
n=1
for t in range(deleteN,len(all_words_list),1):
#feature_words额维度为1000
if n>1000:
break
#如果这个词不是数字,且不是指定的结束语,并且单词长度大于1小于5,那么这个词就可以作为特征词
if not all_words_list[t].isdigit() and all_words_list[t] not in stopWords_set \
and 15:
feature_words.append(all_words_list[t])
n+=1
return feature_words
""" 函数说明:新闻分类器 parameters: train_feature_list - 训练集向量化的特征文本 test_feature_list - 测试集向量化的特征文本 train_class_list - 训练集分类标签 test_class_list - 测试集分类标签 Returns: test_accuracy - 分类器精度 Modify: 2018-03-15 """
def TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list):
classifier=MultinomialNB().fit(train_feature_list,train_class_list)
test_accuracy=classifier.score(test_feature_list,test_class_list)
return test_accuracy
if __name__=='__main__':
#文本预处理,训练集存放的地址
folder_path='E:\python\machine learning in action\My Code\chap 04\SogouC\Sample'
all_words_list, train_data_list, test_data_list, train_class_list, \
test_class_list=TextProcessing(folder_path,test_size=0.2)
#生成stopwords_set
stopwords_file='stopwords_cn.txt'
stopwords_set=MakeWordSet(stopwords_file)
test_accuracy_list=[]
deleteNs=range(0,1000,20)
for deleteN in deleteNs:
feature_words=words_dict(all_words_list,deleteN,stopwords_set)
train_feature_list,test_feature_list=TextFeatures(train_data_list,
test_data_list,feature_words)
test_accuracy=TextClassifier(train_feature_list,test_feature_list,
train_class_list,test_class_list)
test_accuracy_list.append(test_accuracy)
plt.figure()
plt.plot(deleteNs,test_accuracy_list)
plt.title('Relationship of deleteNs and test_accuracy')
plt.xlabel('deleteNs')
plt.ylabel('test_accuracy')
plt.show()
结果:
将代码修改如下:
if __name__ == '__main__':
#文本预处理
folder_path = './SogouC/Sample' #训练集存放地址
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
# 生成stopwords_set
stopwords_file = './stopwords_cn.txt'
stopwords_set = MakeWordsSet(stopwords_file)
test_accuracy_list = []
feature_words = words_dict(all_words_list, 450, stopwords_set)
train_feature_list, test_feature_list = TextFeatures(train_data_list, test_data_list, feature_words)
test_accuracy = TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list)
test_accuracy_list.append(test_accuracy)
ave = lambda c: sum(c) / len(c)