朴素贝叶斯简介及优缺点
朴素贝叶斯是有监督学习算法中的一种,解决的是分类问题。决策树算法和KNN算法都是给出的是最优类别的分类的预测的结果,没有给出一个预测的估计值。
“朴素”的意思是在整个分类过程中只做最原始、最简单的假设。贝叶斯是统计学中的一种方法。
优点
- 在数据较少的时候依然有效,可以处理多类别的问题
- 算法简单易懂
- 分类效果好
缺点
- 对于输入数据的准备方式较为敏感
- 只能进行分类
贝叶斯理论
假设现在我们有一个数据集,它由两类数据组成。
现在用p1(x,y)表示途中红色圆点的概率,并属于类别1.用p2(x,y)表示图中蓝色三角形的概率,并属于类别。那么就可以得到:
- 若p1(x,y)>p2(x,y) ,属于类别1
- 若p1(x,y)<p2(x,y) ,属于类别2
**贝叶斯的核心思想就是选择具有最高概率的决策。**但是如何计算p1,p2的概率呢?需要学会条件概率和全概率。
条件概率
条件概率就是在事件B发生的情况下,时间A发生的概率。用P(A|B)表示。
如下图所示:
从上图可以看出,在事件B发生的情况下,事件A发生的概率是P(A∩B)除以P(B)。公式如下:
得到P(A∩B)如下:
那么也可以得到P(A∩B):
让两个P(A∩B)相等得到:
也就是得到条件概率的计算公式:
全概率公式
假设样本空间S,是两个时间A和A`的和。如下图:
在这种情况下事件B可以划分成两部分。
那么P(B):
而:
所以P(B)等于:
上面那个是全概率公式,含义如下:
如果A和A’构成样本空间的一个划分,那么事件B的概率,就等于A和A’的概率分别乘以B对这两个事件的条件概率之和。
带入条件概率公式
可得:
对条件概率进行变形得到:
- P(A)就是先验概率。就是A事件的本来的概率。是在B事件发生之前,A事件的概率判断。
- P(A|B)是后验概率,是在B事件发生之后,对A事件概率的重新判断。
- P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
- 后验概率等于先验概率乘以调整因子。
这个调整因子和梯度中的那个步长的意思差不多。就是我们先给定一个预估的先验概率,这个先验概率不一定准确甚至完全不对,但是后来我们用调整因子不断去调整,直到最接近后验概率。
下面以Jack Cui博客上的一个例子理解贝叶斯:
两个碗里各有四十颗糖,H1碗里有30个水果糖和10个巧克力糖,H2碗里有20个水果糖和20个巧克力糖。如果任意摸一个碗,任意选择一个糖是水果糖,那么这个糖来自H1碗的概率有多大。
满足以下假设: - 两个碗一样
- 巧克力糖和水果糖大小一样。
如果调整因子P(B|A)/P(B)>1,意味着先验概率被增强,事件A发生的可能性变大。如果调整y因子P(B|A)/P(B)<1,意味着先验概率被削弱,事件A的可能性变小。
我们求一下先验概率:也就上面公式里的P(A)。在没有做实验之前,那么来自两个碗里的概率都是相同的,都是P(A)=P(B)=0.5.这个是先验概率。就是在没做实验之前,得到的概率。
假设知道拿到的糖E是水果糖。就是在已经知道糖是水果糖这个条件的情况下,来自H1碗的概率。也就是条件概率:P(H1|E)。这个就是后验概率。
条件概率:P(H1|E)得到公式如下:
P(H1|E)是知道糖是水果糖的前提下,然后碗是H1碗的概率。
P(H1)是先验概率,在没做实验前H1的概率。
P(E|H1)是碗是H1碗的前提下,是水果糖的概率。
根据公式计算:
先验概率P(H1)=0.5。
P(E|H1)=30/(30+10)=0.75
根据上面公式,我们还需要求出P(E)才能求出P(H1|E)。
根据全概率公式可得:
则P(E)为:
则P(H1|E)为:
其中调整因子P(B|A)/P(B)=(0.75/0.625)>1,那么H1事件的概率得到增强。
朴素贝叶斯
朴素贝叶斯对条件的概率分布做了条件独立性的假设。假设有n个条件,并且每个特征都是独立的。
由于每个特征都是独立的。拆分公式如下:
下面继续以Jack Cui的博客上的一个例子讲解:
一个医院来了六个门诊病人,情况如下图:
现在医院接诊了一个打喷嚏的建筑工人,那么患感冒的概率多大。
我们根据上面推导的贝叶斯公式(这里再推导一次):
条件概率:
变形得到:
所以:
得到贝叶斯公式如下:
那么计算出患感冒的打喷嚏的建筑工人的概率为:
因为朴素贝叶斯对条件的概率分布做了条件独立性的假设。每个特征都是独立的。这里打喷嚏和建筑工人两个特征是独立的。那么可以将它拆开如下:
得到结果如下:这个工人患感冒的概率为0.66
贝叶斯的分类方法就是依据某些特征,计算各个类别的概率,从而实现分类。
实例
为了过滤掉某些不当言论,我们需要构建一个快速过滤器过滤某些不当言论。言论的可以分为正当言论和非正当言论。正当言论用0表示,非正当言论用1表示。
构造数据
'''
创建实验样本
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']]
#标签,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)
'''
创建实验样本
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']]
#标签,1代表侮辱性的词汇,0代表非侮辱性的词汇
classVec=[0,1,0,1,0,1]
return postingList,classVec
'''
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表
Returns:
returnVec - 文档向量,词集模型
'''
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
'''
将切分的实验样本词条整理成不重复的词条列表,也就是整理出词汇表
postingList是原始的词条列表,myVocabList是词汇表。
Parameters:
dataSet - 整理的样本数据集
Returns:
vocabSet - 返回不重复的词条列表,也就是词汇表
'''
def createVocabList(dataSet):
#创建一个空的set列表
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向量话的词条向量。
接下来使用词条向量训练朴素贝叶斯分类器。
'''
创建实验样本
Returns:
postingList - 实验样本切分的词条
classVec - 类别标签向量
'''
import numpy as np
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 - 文档向量,词集模型
'''
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
'''
将切分的实验样本词条整理成不重复的词条列表,也就是整理出词汇表
postingList是原始的词条列表,myVocabList是词汇表。
Parameters:
dataSet - 整理的样本数据集
Returns:
vocabSet - 返回不重复的词条列表,也就是词汇表
'''
def createVocabList(dataSet):
#创建一个空的set列表
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数组,词条出现数初始化为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)
对算法进行测试
# -*- coding: UTF-8 -*-
import numpy as np
from functools import reduce
"""
函数说明:创建实验样本
Parameters:
无
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: #如果词条存在于词汇表中,则置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 - 文档属于侮辱类的概率
"""
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 - 属于侮辱类
"""
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:
无
"""
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()
经过测试之后发现,这个算法其实没有分类。因为上面计算的p0和p1都是0。为什么出现这种情况呢。
原因在于利用贝叶斯分类器进行分类使用的方法是计算多个概率的乘积来获得文档属于哪个类别的概率。也就是计算p(w0|1) * p(w1|1) * p(w3|1)的结果,但是这种乘积的形式中,只要有其中一个概率是0的话,那么最后的结果就是0。
但是实际情况中不是这样的,你不能说一个单词的概率是0,你就说这个句子是不对的。所以为了降低这种影响,我们将所有词的出现的次数初始化为1,并将分母初始化为2,这种就是拉普拉斯平滑。
拉普拉斯平滑也称为加1平滑,是为了解决0概率问题。
还有一个问题是下溢出问题。就是多个小数相乘,然后越乘越小,在程序里,会对相应小数位置进行四舍五入,导致最后概率又变成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 #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
"""
函数说明:朴素贝叶斯分类器分类函数
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 1
else:
return 0
学习资料
jack cui的个人博客