机器学习实战——第四章(分类):朴素贝叶斯
前言
首先感谢博主:Jack-Cui
主页:http://blog.csdn.net/c406495762
朴素贝叶斯博文地址:
https://blog.csdn.net/c406495762/article/details/77341116
https://blog.csdn.net/c406495762/article/details/77500679
这篇博文对书上的内容很形象的进行了表达,通俗易懂,用自己的实例来进行讲解,比书上讲的清楚太多,于是我才开始了学习,感激不尽,真心推荐。
我这篇博文大多从它的博文中摘抄,但也是我一个字一个敲出来的,算法我也是自己算过的,算是学完它的博文的一个总结吧,如果还看不明白的可以直接看他的吧。
明天我会继续按照它的博客学习。
进入正题。
朴素贝叶斯理论
朴素贝叶斯是贝叶斯决策理论的一部分,所以在讲述朴素贝叶斯之前有必要快速了解一下朴素贝叶斯决策论。
贝叶斯决策理论
假设现在我们有一个数据集,由两类数据组成,分布如下图所示:
我们现在用p1(x,y)表示数据点(x,y)属于类别1(红色点)的概率,用p2(x,y)表示数据点(x,y)属于类别2(蓝色三角形)的概率,那么对于我们输入的一个新的点(x,y),可以用下面的规则来判断该点所属类别:
- 如果p1(x,y)>p2(x,y),则点(x,y)属于类别1
- 如果p1(x,y)<p2(x,y),则点(x,y)属于类别2
也就是说,我们会选择概率更高的对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。
那么现在的问题就是p1和p2的概率如何计算得出。
条件概率
在学习计算p1和p2概率之前,我们需要了解什么是条件概率,就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。推导如下图:
注:请忽略我写字写的丑这个事实,后面有的图也是。
全概率公式
除了条件概率以外,在计算p1和p2的时候,还要用到全概率公式,因此,这里继续推导全概率公式。推导如下:
贝叶斯推断
对条件概率公式变形,可以得到如下式:
我们把P(A)称为“先验概率”,即在事件B发生之前,我们对事件A概率的一个判断。
P(A|B)称为“后验概率”,即在事件B发生之后,我们对事件A概率的重新评估。
P(B|A)/P(B)称为“可能性函数”,这是一个调整因子,使得预估概率更接近真实概率。
所以:条件概率可以理解成下面的式子:
后验概率 = 先验概率 x 调整因子
这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。
在这里,如果"可能性函数"P(B|A)/P(B)>1,意味着**"先验概率"被增强**,事件A的发生的可能性变大;如果**“可能性函数”=1**,意味着B事件无助于判断事件A的可能性;如果**“可能性函数”<1**,意味着**"先验概率"被削弱**,事件A的可能性变小。
举例
为了加深对贝叶斯推断的理解,我们一个例子。
如上图,两个一模一样的碗,一号碗有30颗水果糖和10颗巧克力糖,二号碗有水果糖和巧克力糖各20颗。现在随机选择一个碗,从中摸出一水果糖。请问这颗水果糖来自一号碗的概率有多大?
我们假定,H1表示一号碗,H2表示二号碗。由于这两个碗是一样的,所以P(H1)=P(H2),也就是说,在取出水果糖之前,这两个碗被选中的概率相同。因此,P(H1)=0.5,我们把这个概率就叫做**“先验概率”,即没有做实验之前,来自一号碗的概率是0.5。再假定,E表示水果糖,所以问题就变成了在已知E的情况下,来自一号碗的概率有多大,即求P(H1|E)。我们把这个概率叫做"后验概率"**,即在E事件发生之后,对P(H1)的修正。
根据条件概率公式,得到
已知,P(H1)等于0.5,P(E|H1)为一号碗中取出水果糖的概率,等于30÷(30+10)=0.75,那么求出P(E)就可以得到答案。根据全概率公式,
所以,
将数字代入原方程,得到
这个就是后验概率,为0.6,也就是说取出水果糖之后,H1事件的可能性得到了增强。
同时再思考一个问题,在使用该算法的时候,如果不需要知道具体的类别概率,即上面P(H1|E)=0.6,只需要知道所属类别,即来自一号碗,我们有必要计算P(E)这个全概率吗?要知道我们只需要比较 P(H1|E)和P(H2|E)的大小,找到那个最大的概率就可以。既然如此,两者的分母都是相同的,那我们只需要比较分子即可。即比较P(E|H1)P(H1)和P(E|H2)P(H2)的大小,所以为了减少计算量,全概率公式在实际编程中可以不使用。
朴素贝叶斯推断
贝叶斯和朴素贝叶斯的概念是不同的,区别就在于**“朴素”二字,朴素贝叶斯对条件个概率分布做了条件独立性的假设**。 比如下面的公式,假设有n个特征:
由于每个特征都是独立的,我们可以进一步拆分公式
这样我们就可以进行计算了。如果有些迷糊,让我们从一个例子开始讲起,你会看到贝叶斯分类器很好懂,一点都不难。
举例
某个医院早上来了六个门诊的病人,他们的情况如下表所示:
症状 | 职业 | 疾病 |
---|---|---|
打喷嚏 | 护士 | 感冒 |
打喷嚏 | 农夫 | 过敏 |
头痛 | 建筑工人 | 脑震荡 |
头痛 | 建筑工人 | 感冒 |
打喷嚏 | 教师 | 感冒 |
头痛 | 教师 | 脑震荡 |
现在又来了第七个病人,是一个打喷嚏的建筑工人。请问他患上感冒的概率有多大?
根据贝叶斯定理:
用文字表示有:
再根据朴素贝叶斯的条件独立性的假设可知,“打喷嚏”和“建筑工人”两个特征是独立的,则根据朴素贝叶斯的定理有:
直接进行计算有:
具体计算(根据数据集表算出):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WbXmlCpi-1594642398997)()
因此,这个打喷嚏的建筑工人,有66%的概率是得了感冒。同理,可以计算这个病人患上过敏或脑震荡的概率。
比较这几个概率,就可以知道他最可能得什么病。
这就是贝叶斯分类器的基本方法:在统计资料的基础上,依据某些特征,计算各个类别的概率,从而实现分类。
同样,在编程的时候,如果不需要求出所属类别的具体概率,P(打喷嚏) = 0.5和P(建筑工人) = 0.33的概率是可以不用求的。
实战
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。
我们把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。
代码如下所示:
"""
创建实验样本
返回值:postingList - 实验样本切分的词条,classVec - 类别标签向量
"""
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,classVecif __name__ == '__main__':postingLIst, classVec = loadDataSet()for each in postingLIst:print(each)print(classVec)
打印结果如下:
从运行结果可以看出,我们已经将postingList是存放词条列表中,classVec是存放每个词条的所属类别,1代表侮辱类 ,0代表非侮辱类。继续编写代码,前面我们已经说过我们要先创建一个词汇表,并将切分好的词条转换为词条向量。
代码如下所示:
"""
函数说明:创建实验样本
Returns:postingList - 实验样本切分的词条classVec - 类别标签向量
"""
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"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:vocabList - createVocabList返回的列表inputSet - 切分的词条列表
Returns:returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量for word in inputSet: #遍历每个词条if word in vocabList: #如果词条存在于词汇表中,则置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec #返回文档向量"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:dataSet - 整理的样本数据集
Returns:vocabSet - 返回不重复的词条列表,也就是词汇表
"""
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))#根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0print('trainMat:\n', trainMat)
代码的步骤是:首先将数据集切分成不重复的词汇表,也就是把数据集中的重复数据删除,再组合成一个新的列表,就是词汇表。然后根据数据集中的某一个列表,相对于词汇表而言出现的次数进行记录,如果数据集中的某一个列表在词汇表中出现过,那么将标记为1,否则还是为0。打印结果如下:
从运行结果可以看出,postingList是原始的词条列表,myVocabList是词汇表。myVocabList是所有单词出现的集合,没有重复的元素。词汇表是用来干什么的?没错,它是用来将词条向量化的,一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。trainMat是所有的词条向量组成的列表。它里面存放的是根据myVocabList向量化的词条向量。
现在,我们已经得到了词条向量。接下来,我们就可以通过词条向量训练朴素贝叶斯分类器。
代码如下所示:
import numpy as np"""
函数说明:创建实验样本
Returns:postingList - 实验样本切分的词条classVec - 类别标签向量
"""
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"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:vocabList - createVocabList返回的列表inputSet - 切分的词条列表
Returns:returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量for word in inputSet: #遍历每个词条if word in vocabList: #如果词条存在于词汇表中,则置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec #返回文档向量"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:dataSet - 整理的样本数据集
Returns:vocabSet - 返回不重复的词条列表,也就是词汇表
"""
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 - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #计算训练的文档数目numWords = len(trainMatrix[0]) #计算每篇文档的词条数pAbusive = sum(trainCategory)/float(numTrainDocs) #文档属于侮辱类的概率p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #创建numpy.zeros数组,词条出现数初始化为0p0Denom = 0.0; p1Denom = 0.0 #分母初始化为0for i in range(numTrainDocs):if trainCategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i])else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = p1Num/p1Denom p0Vect = p0Num/p0Denom 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,也就是非侮辱类词汇的概率。比如p0V的倒数第6个概率,就是stupid这个单词属于非侮辱类的概率为0。同理,p1V的倒数第6个概率,就是stupid这个单词属于侮辱类的概率为0.15789474,也就是约等于15.79%的概率。我们知道stupid的中文意思是蠢货,难听点的叫法就是傻逼。显而易见,这个单词属于侮辱类。pAb是所有侮辱类的样本占所有样本的概率,从classVec中可以看出,一用有3个侮辱类,3个非侮辱类。所以侮辱类的概率是0.5。因此p0V存放的就是P(him|非侮辱类) = 0.0833、P(is|非侮辱类) = 0.0417,一直到P(dog|非侮辱类) = 0.0417,这些单词的条件概率。同理,p1V存放的就是各个单词属于侮辱类的条件概率。pAb就是先验概率。
已经训练好了分类器,接下来将使用分类器进行分类。
代码如下:
# -*- coding: UTF-8 -*-
import numpy as np
from functools import reduce"""
函数说明:创建实验样本
Returns:postingList - 实验样本切分的词条classVec - 类别标签向量
"""
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 #返回实验样本切分的词条和类别标签向量"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:dataSet - 整理的样本数据集
Returns:vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):vocabSet = set([]) #创建一个空的不重复列表for document in dataSet: vocabSet = vocabSet | set(document) #取并集return list(vocabSet)"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:vocabList - createVocabList返回的列表inputSet - 切分的词条列表
Returns:returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量for word in inputSet: #遍历每个词条if word in vocabList: #如果词条存在于词汇表中,则置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec #返回文档向量"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:p0Vect - 侮辱类的条件概率数组p1Vect - 非侮辱类的条件概率数组pAbusive - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #计算训练的文档数目numWords = len(trainMatrix[0]) #计算每篇文档的词条数pAbusive = sum(trainCategory)/float(numTrainDocs) #文档属于侮辱类的概率p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #创建numpy.zeros数组,p0Denom = 0.0; p1Denom = 0.0 #分母初始化为0.0for i in range(numTrainDocs):if trainCategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i])else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = p1Num/p1Denom #相除 p0Vect = p0Num/p0Denom return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率"""
函数说明:朴素贝叶斯分类器分类函数
Parameters:vec2Classify - 待分类的词条数组p0Vec - 侮辱类的条件概率数组p1Vec -非侮辱类的条件概率数组pClass1 - 文档属于侮辱类的概率
Returns:0 - 属于非侮辱类1 - 属于侮辱类
"""
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 1else: return 0"""
函数说明:测试朴素贝叶斯分类器
"""
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)) #训练朴素贝叶斯分类器testEntry = ['love', 'my', 'dalmation'] #测试样本1thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化if classifyNB(thisDoc,p0V,p1V,pAb):print(testEntry,'属于侮辱类') #执行分类并打印分类结果else:print(testEntry,'属于非侮辱类') #执行分类并打印分类结果testEntry = ['stupid', 'garbage'] #测试样本2thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化if classifyNB(thisDoc,p0V,p1V,pAb):print(testEntry,'属于侮辱类') #执行分类并打印分类结果else:print(testEntry,'属于非侮辱类') #执行分类并打印分类结果if __name__ == '__main__':testingNB()
我们测试了两个词条,testEntry = [‘love’, ‘my’, ‘dalmation’]和testEntry = [‘stupid’, ‘garbage’],最终结果为:
我们发现p0额p1的概率计算结果都为0,这显然是有问题的,是什么原因呢?
原因就是:利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的乘积也为0。
那如何改进呢?需要进行拉普拉斯平滑。
朴素贝叶斯改进之拉普拉斯平滑
在上面我们已经知道了利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中有一个概率值为0,那么最后的乘积也为0。
从上图可以看出,在计算的时候已经出现了概率为0的情况。如果我们输入的实例文本中,包含这种概率为0的词,那么最终的文本属于某个类别的概率也就是0了。显然这样是不合理的,为了改进这种情况,我们可以将所有词出现数初始化为1,并将分母初始化为2。这种做法就叫拉普拉斯平滑,又称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。
除此之外,另外一个遇到的问题就是下溢出,这是由于太多很小的数相乘造成的。学过数学的人都知道,两个小数相乘,越乘越小,这样就造成了下溢出。在程序中,在相应小数位置进行四舍五入,计算结果可能就变成0了。为了解决这个问题,对乘积结果取自然对数。通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。下图给出函数f(x)和ln(f(x))的曲线。
检查这两条曲线,就会发现它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。因此我们可以对上篇文章的trainNB0(trainMatrix, trainCategory)函数进行更改,修改如下:
def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #计算训练的文档数目numWords = len(trainMatrix[0]) #计算每篇文档的词条数pAbusive = sum(trainCategory)/float(numTrainDocs) #文档属于侮辱类的概率p0Num = np.ones(numWords); p1Num = np.ones(numWords) #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑p0Denom = 2.0; p1Denom = 2.0 #分母初始化为2,拉普拉斯平滑for i in range(numTrainDocs):if trainCategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i])else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = np.log(p1Num/p1Denom) #取对数,防止下溢出 p0Vect = np.log(p0Num/p0Denom) return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
结果如下:
这样就没有任何问题了,不存在0概率了。
当然我们还需要对classifyNB(vec2Classify, p0Vec, p1Vec, pClass1)函数进行修改,因为我们取了自然对数log,在logab = loga + logb。具体代码如下:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)if p1 > p0:return 1else:return 0
结果如下:
这样我们的朴素贝叶斯分类器就改进完毕了。
朴素贝叶斯项目举例(过滤垃圾邮件)
在前面我们讲过的那个简单的例子中,我们引入了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下使用朴素贝叶斯对电子邮件进行分类的步骤:
- 收集数据:提供文本文件。
- 准备数据:将文本文件解析成词条向量。
- 分析数据:检查词条确保解析的正确性。
- 训练算法:使用我们之前建立的trainNB0()函数。
- 测试算法:使用classifyNB(),并构建一个新的测试函数来计算文档集的错误率。
- 使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
收集数据
文本数据去Jack-Cui博主的Github上去下载,地址为:https://github.com/Jack-Cherish/Machine-Learning/tree/master/Naive%20Bayes/email
有两个文件夹ham和spam,spam文件下的txt文件为垃圾邮件。
准备数据
对于英文文本,我们可以以非字母、非数字作为符号进行切分,使用split函数即可。编写代码如下:
# -*- coding: UTF-8 -*-
import re"""
函数说明:接收一个大字符串并将其解析为字符串列表
"""
def textParse(bigString): #将字符串转换为字符列表listOfTokens = re.split(r'\W+', bigString) #将特殊符号作为切分标志进行字符串切分,即非字母、非数字return [tok.lower() for tok in listOfTokens if len(tok) > 2] #除了单个字母,例如大写的I,其它单词变成小写"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:dataSet - 整理的样本数据集
Returns:vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):vocabSet = set([]) #创建一个空的不重复列表for document in dataSet: vocabSet = vocabSet | set(document) #取并集return list(vocabSet)if __name__ == '__main__':docList = []; classList = []for i in range(1, 26): #遍历25个txt文件wordList = textParse(open('email/spam/%d.txt' % i, 'r').read()) #读取每个垃圾邮件,并字符串转换成字符串列表docList.append(wordList)classList.append(1) #标记垃圾邮件,1表示垃圾文件wordList = textParse(open('email/ham/%d.txt' % i, 'r').read()) #读取每个非垃圾邮件,并字符串转换成字符串列表docList.append(wordList)classList.append(0) #标记非垃圾邮件,1表示垃圾文件 vocabList = createVocabList(docList) #创建词汇表,不重复print(vocabList)
得到的结果按理来说应该是如下图所示:
注:但是在我电脑上啥也读取不出,问题在textParse函数内,我还在找错误。 listOfTokens = re.split(r’\W+’, bigString) 函数里,把*改成+就好。
根据词汇表,我们就可以将每个文本向量化。我们将数据集分为训练集和测试集,使用交叉验证的方式测试朴素贝叶斯分类器的准确性。编写代码如下:
# -*- coding: UTF-8 -*-
import numpy as np
import random
import re"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:dataSet - 整理的样本数据集
Returns:vocabSet - 返回不重复的词条列表,也就是词汇表
"""
def createVocabList(dataSet):vocabSet = set([]) #创建一个空的不重复列表for document in dataSet: vocabSet = vocabSet | set(document) #取并集return list(vocabSet)"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:vocabList - createVocabList返回的列表inputSet - 切分的词条列表
Returns:returnVec - 文档向量,词集模型
"""
def setOfWords2Vec(vocabList, inputSet):returnVec = [0] * len(vocabList) #创建一个其中所含元素都为0的向量for word in inputSet: #遍历每个词条if word in vocabList: #如果词条存在于词汇表中,则置1returnVec[vocabList.index(word)] = 1else: print("the word: %s is not in my Vocabulary!" % word)return returnVec #返回文档向量"""
函数说明:根据vocabList词汇表,构建词袋模型
Parameters:vocabList - createVocabList返回的列表inputSet - 切分的词条列表
Returns:returnVec - 文档向量,词袋模型
"""
def bagOfWords2VecMN(vocabList, inputSet):returnVec = [0]*len(vocabList) #创建一个其中所含元素都为0的向量for word in inputSet: #遍历每个词条if word in vocabList: #如果词条存在于词汇表中,则计数加一returnVec[vocabList.index(word)] += 1return returnVec #返回词袋模型"""
函数说明:朴素贝叶斯分类器分类函数
Parameters:vec2Classify - 待分类的词条数组p0Vec - 侮辱类的条件概率数组p1Vec -非侮辱类的条件概率数组pClass1 - 文档属于侮辱类的概率
Returns:0 - 属于非侮辱类1 - 属于侮辱类
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) #对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)if p1 > p0:return 1else:return 0"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:p0Vect - 侮辱类的条件概率数组p1Vect - 非侮辱类的条件概率数组pAbusive - 文档属于侮辱类的概率
"""
def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix) #计算训练的文档数目numWords = len(trainMatrix[0]) #计算每篇文档的词条数pAbusive = sum(trainCategory)/float(numTrainDocs) #文档属于侮辱类的概率p0Num = np.ones(numWords); p1Num = np.ones(numWords) #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑p0Denom = 2.0; p1Denom = 2.0 #分母初始化为2,拉普拉斯平滑for i in range(numTrainDocs):if trainCategory[i] == 1: #统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Num += trainMatrix[i]p1Denom += sum(trainMatrix[i])else: #统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Num += trainMatrix[i]p0Denom += sum(trainMatrix[i])p1Vect = np.log(p1Num/p1Denom) #取对数,防止下溢出 p0Vect = np.log(p0Num/p0Denom) return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率"""
函数说明:接收一个大字符串并将其解析为字符串列表
"""
def textParse(bigString): #将字符串转换为字符列表listOfTokens = re.split(r'\W*', bigString) #将特殊符号作为切分标志进行字符串切分,即非字母、非数字return [tok.lower() for tok in listOfTokens if len(tok) > 2] #除了单个字母,例如大写的I,其它单词变成小写"""
函数说明:测试朴素贝叶斯分类器
"""
def spamTest():docList = []; classList = []; fullText = []for i in range(1, 26): #遍历25个txt文件wordList = textParse(open('email/spam/%d.txt' % i, 'r').read()) #读取每个垃圾邮件,并字符串转换成字符串列表docList.append(wordList)fullText.append(wordList)classList.append(1) #标记垃圾邮件,1表示垃圾文件wordList = textParse(open('email/ham/%d.txt' % i, 'r').read()) #读取每个非垃圾邮件,并字符串转换成字符串列表docList.append(wordList)fullText.append(wordList)classList.append(0) #标记非垃圾邮件,1表示垃圾文件 vocabList = createVocabList(docList) #创建词汇表,不重复trainingSet = list(range(50)); testSet = [] #创建存储训练集的索引值的列表和测试集的索引值的列表 for i in range(10): #从50个邮件中,随机挑选出40个作为训练集,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]) #测试集的词集模型if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]: #如果分类错误errorCount += 1 #错误计数加1print("分类错误的测试集:",docList[docIndex])print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))if __name__ == '__main__':spamTest()
运行结果如下所示:
函数spamTest()会输出在10封随机选择的电子邮件上的分类错误概率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错误的文档的此表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。为了避免错误,有多种方式可以用来修正分类器,这些内容会在后续文章中进行讨论。
朴素贝叶斯之新浪新闻分类(基于Sklearn)
由于我的jieba包一直装不了,可以直接参考博客https://blog.csdn.net/c406495762/article/details/77500679
的第四章:朴素贝叶斯之新浪新闻分类(Sklearn)
总结
- 在训练朴素贝叶斯分类器之前,要处理好训练集,文本的清洗还是有很多学习的地方。
- 根据提取的分类特征将文本向量化,然后训练朴素贝叶斯
- 去高频词汇数量的不同,对结果也是有影响的
- 拉普拉斯平滑对于改善朴素贝叶斯分类器的分类效果有着积极的作用
机器学习实战——第四章(分类):朴素贝叶斯相关推荐
- 【机器学习实战(三):朴素贝叶斯】
我的个人网站:天风的人工智能小站 我的CSDN账号:**Tian-Feng的博客_CSDN博客-机器学习领域博主 我的github账号:zhangwei668 - Overview 我的知乎账号:天风 ...
- 统计学习方法笔记(李航)———第四章(朴素贝叶斯法)
推荐阅读:小白之通俗易懂的贝叶斯定理(Bayes' Theorem) 朴素贝叶斯法是一种多分类算法,它的基础是"朴素贝叶斯假设"(假设实例的各个特征具有条件独立性).根据训练集估计 ...
- 机器学习实战刻意练习 —— Task 02. 朴素贝叶斯
机器学习实战刻意练习 第 1 周任务 分类问题:K-邻近算法 分类问题:决策树 第 2 周任务 分类问题:朴素贝叶斯 分类问题:逻辑回归 第 3 周任务 分类问题:支持向量机 第 ...
- 【阿旭机器学习实战】【10】朴素贝叶斯模型原理及3种贝叶斯模型对比:高斯分布朴素贝叶斯、多项式分布朴素贝叶斯、伯努利分布朴素贝叶斯
[阿旭机器学习实战]系列文章主要介绍机器学习的各种算法模型及其实战案例,欢迎点赞,关注共同学习交流. 本文介绍了机器学习中的朴素贝叶斯的基本原理,并对3种贝叶斯模型根据鸢尾花实例进行了比较. 目录 朴 ...
- Python手撸机器学习系列(四):朴素贝叶斯(华强买瓜版)
目录 一.原理 1.1 买瓜 1.2 算瓜 二.代码实现 三.参考文献 一.原理 1.1 买瓜 大家好,我叫刘华强.我现在手里有一堆西瓜,我希望通过观察我这一堆西瓜中好瓜的特征来总结出判断好瓜的标准, ...
- 机器学习实验:使用3种朴素贝叶斯算法对iris()、breast_cancer()和wine()数据集进行训练
机器学习实验:使用3种朴素贝叶斯算法对iris().breast_cancer()和wine()数据集进行训练 实验内容: 分别使用3种朴素贝叶斯算法对iris().breast_cancer()和w ...
- Python3《机器学习实战》学习笔记(四):朴素贝叶斯基础篇之言论过滤器
转载请注明作者和出处: http://blog.csdn.net/c406495762 运行平台: Windows Python版本: Python3.x IDE: Sublime text3 文章目 ...
- 机器学习实战教程(四):朴素贝叶斯基础篇之言论过滤器
一.前言 朴素贝叶斯算法是有监督的学习算法,解决的是分类问题,如客户是否流失.是否值得投资.信用等级评定等多分类问题.该算法的优点在于简单易懂.学习效率高.在某些领域的分类问题中能够与决策树.神经网络 ...
- 《机器学习实战》学习笔记(四) : 朴素贝叶斯的基本原理
在看了书.CSDN上的博客.B站上的视频后整理而成,超级感谢前辈提供的宝贵知识- Table of Contents 1 基础数学知识 1.1 条件概率公式 1.2 贝叶斯公式 1.3 全概率 ...
最新文章
- 《Mysql数据库及应用》_MySQL数据库及应用
- 倒序查10条数据_王者荣耀对抗路数据公布,尖端局吕布倒第一,夏洛特真的很意外...
- 神经病啊!——微信同层播放器接(踩)入(坑)总结
- LoadRunner中的参数与变量
- JQuery播放器代理--IE下支持wma格式
- java对象序列化和反序列化,redis存入和获取对象
- 使用FastReport 3.0及以上版本创建动态报表的几个技巧(转)
- DirectX组件和安装在系统上驱动程序
- H.265与H.264对比有哪些优势
- Android Banner图片轮播
- AGA8压缩因子算法C语言
- Hadoop集群中HDFS的API测试案例以及MapReduce的多种提交Job方式案例
- 阿里云域名实名认证状态查询
- 比较:strcmp(),strncmp(),strcasecmp(),strncasecmp()
- Linux系统部署wordpress@林
- 数字IC后端设计实现floorplan及powerplan规划
- 【NCL】shea_util.ncl只能load一次
- 百度网盘下载速度慢问题解决方法
- 中柏zpad6 android x86,Windows平板轻应用 中柏EZpad 6评测
- 计算机考研408每日一题 day121