原文连接: Jack-Cui,cuijiahua.com/blog/2017/1…html
朴素贝叶斯算法是有监督的学习算法,解决的是分类问题,如客户是否流失、是否值得投资、信用等级评定等多分类问题。该算法的优势在于简单易懂、学习效率高、在某些领域的分类问题中可以与决策树、神经网络相媲美。但因为该算法以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会致使算法精度在某种程度上受影响。python
本篇文章将从朴素贝叶斯推断原理开始讲起,经过实例进行辅助讲解。最后,使用Python3编程实现一个简单的言论过滤器。git
朴素贝叶斯是贝叶斯决策理论的一部分,因此在讲述朴素贝叶斯以前有必要快速了解一下贝叶斯决策理论。github
假设如今咱们有一个数据集,它由两类数据组成,数据分布以下图所示:算法
咱们如今用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的几率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的几率,那么对于一个新数据点(x,y),能够用下面的规则来判断它的类别:编程
也就是说,咱们会选择高几率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具备最高几率的决策。已经了解了贝叶斯决策理论的核心思想,那么接下来,就是学习如何计算p1和p2几率。数组
在学习计算p1 和p2几率以前,咱们须要了解什么是条件几率(Condittional probability),就是指在事件B发生的状况下,事件A发生的几率,用P(A|B)来表示。bash
根据文氏图,能够很清楚地看到在事件B发生的状况下,事件A发生的几率就是P(A∩B)除以P(B)。网络
所以,app
同理可得,
因此,
即
这就是条件几率的计算公式。
除了条件几率之外,在计算p1和p2的时候,还要用到全几率公式,所以,这里继续推导全几率公式。
假定样本空间S,是两个事件A与A'的和。
上图中,红色部分是事件A,绿色部分是事件A',它们共同构成了样本空间S。
在这种状况下,事件B能够划分红两个部分。
即
在上一节的推导当中,咱们已知
因此,
这就是全几率公式。它的含义是,若是A和A'构成样本空间的一个划分,那么事件B的几率,就等于A和A'的几率分别乘以B对这两个事件的条件几率之和。
将这个公式代入上一节的条件几率公式,就获得了条件几率的另外一种写法:
对条件几率公式进行变形,能够获得以下形式:
咱们把P(A)称为"先验几率"(Prior probability),即在B事件发生以前,咱们对A事件几率的一个判断。
P(A|B)称为"后验几率"(Posterior probability),即在B事件发生以后,咱们对A事件几率的从新评估。
P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估几率更接近真实几率。
因此,条件几率能够理解成下面的式子:
后验几率 = 先验几率 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个特征:
因为每一个特征都是独立的,咱们能够进一步拆分公式 :
这样咱们就能够进行计算了。若是有些迷糊,让咱们从一个例子开始讲起,你会看到贝叶斯分类器很好懂,一点都不难。
某个医院早上来了六个门诊的病人,他们的状况以下表所示:
如今又来了第七个病人,是一个打喷嚏的建筑工人。请问他患上感冒的几率有多大?
根据贝叶斯定理:
可得:
根据朴素贝叶斯条件独立性的假设可知,"打喷嚏"和"建筑工人"这两个特征是独立的,所以,上面的等式就变成了
这里能够计算:
所以,这个打喷嚏的建筑工人,有66%的几率是得了感冒。同理,能够计算这个病人患上过敏或脑震荡的几率。比较这几个几率,就能够知道他最可能得什么病。
这就是贝叶斯分类器的基本方法:在统计资料的基础上,依据某些特征,计算各个类别的几率,从而实现分类。
一样,在编程的时候,若是不须要求出所属类别的具体几率,P(打喷嚏) = 0.5和P(建筑工人) = 0.33的几率是能够不用求的。
说了这么多,没点实践编程怎么行?
以在线社区留言为例。为了避免影响社区的发展,咱们要屏蔽侮辱性的言论,因此要构建一个快速过滤器,若是某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题创建两个类型:侮辱类和非侮辱类,使用1和0分别表示。
咱们把文本当作单词向量或者词条向量,也就是说将句子转换为向量。考虑出现全部文档中的单词,再决定将哪些单词归入词汇表或者说所要的词聚集合,而后必需要将每一篇文档转换为词汇表上的向量。简单起见,咱们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。编写代码以下:
# -*- coding: UTF-8 -*-
""" 函数说明:建立实验样本 Parameters: 无 Returns: postingList - 实验样本切分的词条 classVec - 类别标签向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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
if __name__ == '__main__':
postingLIst, classVec = loadDataSet()
for each in postingLIst:
print(each)
print(classVec)
复制代码
从运行结果能够看出,咱们已经将postingList是存放词条列表中,classVec是存放每一个词条的所属类别,1表明侮辱类 ,0表明非侮辱类。
继续编写代码,前面咱们已经说过咱们要先建立一个词汇表,并将切分好的词条转换为词条向量。
# -*- coding: UTF-8 -*-
""" 函数说明:建立实验样本 Parameters: 无 Returns: postingList - 实验样本切分的词条 classVec - 类别标签向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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 - 文档向量,词集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一个其中所含元素都为0的向量
for word in inputSet: #遍历每一个词条
if word in vocabList: #若是词条存在于词汇表中,则置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文档向量
""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet - 整理的样本数据集 Returns: vocabSet - 返回不重复的词条列表,也就是词汇表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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向量化的词条向量。
咱们已经获得了词条向量。接下来,咱们就能够经过词条向量训练朴素贝叶斯分类器。
# -*- coding: UTF-8 -*-
import numpy as np
""" 函数说明:建立实验样本 Parameters: 无 Returns: postingList - 实验样本切分的词条 classVec - 类别标签向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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 - 文档向量,词集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一个其中所含元素都为0的向量
for word in inputSet: #遍历每一个词条
if word in vocabList: #若是词条存在于词汇表中,则置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文档向量
""" 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表 Parameters: dataSet - 整理的样本数据集 Returns: vocabSet - 返回不重复的词条列表,也就是词汇表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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 - 文档属于侮辱类的几率 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
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数组,词条出现数初始化为0
p0Denom = 0.0; p1Denom = 0.0 #分母初始化为0
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 = 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
""" 函数说明:建立实验样本 Parameters: 无 Returns: postingList - 实验样本切分的词条 classVec - 类别标签向量 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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 - 返回不重复的词条列表,也就是词汇表 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
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 - 文档向量,词集模型 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-11 """
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) #建立一个其中所含元素都为0的向量
for word in inputSet: #遍历每一个词条
if word in vocabList: #若是词条存在于词汇表中,则置1
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec #返回文档向量
""" 函数说明:朴素贝叶斯分类器训练函数 Parameters: trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵 trainCategory - 训练类别标签向量,即loadDataSet返回的classVec Returns: p0Vect - 非侮辱类的条件几率数组 p1Vect - 侮辱类的条件几率数组 pAbusive - 文档属于侮辱类的几率 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
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.0
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 = p1Num/p1Denom #相除
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件几率数组,属于非侮辱类的条件几率数组,文档属于侮辱类的几率
""" 函数说明:朴素贝叶斯分类器分类函数 Parameters: vec2Classify - 待分类的词条数组 p0Vec - 侮辱类的条件几率数组 p1Vec -非侮辱类的条件几率数组 pClass1 - 文档属于侮辱类的几率 Returns: 0 - 属于非侮辱类 1 - 属于侮辱类 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
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: 无 Author: Jack Cui Blog: http://blog.csdn.net/c406495762 Modify: 2017-08-12 """
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'] #测试样本1
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类') #执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类') #执行分类并打印分类结果
testEntry = ['stupid', 'garbage'] #测试样本2
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类') #执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类') #执行分类并打印分类结果
if __name__ == '__main__':
testingNB()
复制代码
咱们测试了两个词条,在使用分类器前,也须要对词条向量化,而后使用classifyNB()函数,用朴素贝叶斯公式,计算词条向量属于侮辱类和非侮辱类的几率。运行结果以下:
你会发现,这样写的算法没法进行分类,p0和p1的计算结果都是0,显然结果错误。这是为何呢?下一篇文章继续讲解~
朴素贝叶斯推断的一些优势:
朴素贝叶斯推断的一些缺点:
其它:
PS: 若是以为本篇本章对您有所帮助,欢迎关注、评论、赞!
本文出现的全部代码和数据集,都可在个人github上下载,欢迎Follow、Star:github.com/Jack-Cheris…
圆方圆学院聚集 Python + AI 名师,打造精品的 Python + AI 技术课程。 在各大平台都长期有优质免费公开课,欢迎报名收看。 公开课地址: ke.qq.com/course/3627…
加入python学习讨论群 78486745 ,获取资料,和广大群友一块儿学习。