一个Python例子带你理解朴素贝叶斯算法原理
前言
前面我们在我们学习过KNN和决策树后,我们提出了一个问题,就是我们希望算法能够给我们一个分类概率,并不希望直接告诉我们分类情况。朴素贝叶斯将为我们解决这个问题。
源代码已上传GitHub,有需要的伙伴们自行去取,地址:朴素贝叶斯源代码
朴素贝叶斯原理解析
一、条件概率
概率我们都了解过吧,就是在某一件事情发生的可能性。
那条件概率呢,就是在概率的基础上我给它一个前置条件,举个例子:
现在有二个黑箱子,里面有一些形状大小相同的小球,分布情况如下图所示:
那么,我此时问,从箱子A中随机选一个小球,是红颜色的概率是多少?
很简单,答案是2/3.
也就是P(红|箱子A)=2/3 这里的箱子A 就是前置条件。
同理可得,P(红|箱子B)=1/4
如果我们将两个箱子的球合并在一起,此时不存在什么条件了,那么P(红) 就是一个普通的概率。
二、贝叶斯公式
贝叶斯公式是概率论中一个非常重要而且有用的公式,先看一下公式吧:
*P(A|B)=P(B|A)P(A)/P(B) (1)
如何去理解呢?咱们先看下面这个:
*P(A∩B) = P(A)P(B|A) (2)
P(A∩B) 表示事件A和事件B同时发生的概率,就等于事件A发生的概率乘以在事件A已发生的条件下,事件B发生的概率。
还是拿盒子小球作为例子,假设事件A为从箱子A中取球,事件B为取到红颜色的球。
那么,P(A∩B) 表示为从箱子A中取到红颜色球的概率
再看等号右边,P(A) 表示从箱子A中取球的概率,这里因为只有两个箱子,且形状大小无异,所以概率值为1/2;P(B|A) 表示从箱子A中取到红色球的概率,值为2/3.
这下大家能够明白上面式子(2)为什么相等了吧。
如果此时我们将A和B变量互换,此时有:
*P(B∩A) = P(B)P(A|B)
显然,P(B∩A)=P(A∩B) 均表示事件A和事件B同时发生的概率
所以有:
**P(B)P(A|B) = P(A)P(B|A) (3)
公式(3)就被称为贝叶斯公式,它揭示了条件概率中条件与结果的关系,简单的来说,这个公式让我们得以去根据条件去判断最终结果,也就是我们说的分类问题。
三、如何去应用贝叶斯公式?
到第二部分的时候,我会给大家使用源代码进行分析,这里主要是通过文字讲述一下如何应用。
我们要解决的问题是垃圾邮箱的分类问题,通过邮件内容,我们去判断这个邮箱是否是垃圾邮箱。
下图为正常邮箱内容:
这个为垃圾邮箱内容:
那么我们如何去分辨该邮件是否是垃圾邮件呢,听听这个思路
垃圾邮件中总是有一些词汇出现的频率比较高,如果我们针对邮件中的单词建立一个词向量(显示邮件中出现单词的数量),根据训练数据计算不同类型邮箱单词出现的概率,就能够利用这些数据,使用贝叶斯公式去预测邮件的归属问题。
上面不明白没关系,咱们将贝叶斯公式稍微改装一下:
图中Ci 表示邮件分类 i=0时,表示邮件是非垃圾邮件 i=1 表示邮件是垃圾邮件
d表示数据,也就是我们将要构建的每个邮件的词向量
词向量,就是用来记录单词出现次数的一个列表
是一个条件概率,表示在邮件类型为i的条件下,数据d的概率(这里理解为各个单词出现的概率)
同样也是一个条件概率,表示在数据d的情况下,邮件的属于i类的概率
表示邮件属于i的概率,就是一个普通的概率问题
好了,基本上我们就是通过这样一个公式来写我们代码,不过,写代码前,我们先说一下流程:
- 处理邮件数据,去除掉标点符号,将每个邮箱的单词放入列表中
- 统计邮件中所出现过的单词,存储到一个列表中,长度L就是我们所要创建词向量的大小
- 对每个邮件创建一个长度为L的列表(称之为词向量),初始值为1,遍历每个邮件的单词列表,统计单词出现次数.
- 分别对不同类型的邮件的词向量进行相加,再处理他们各自的单词总数,此时结果为
- 再将词向量的各个数相乘,最后再乘以数据集中邮件各个类别的概率
贝叶斯算法源代码解析
一、 加载处理数据
def textParse(text):
text1 = re.split(r'\W+',text)
text2 = [textfilter.lower() for textfilter in text1 if len(textfilter)>2]#返回字符长度大于2的字符
# to do -这里可以添加自定义过滤字符串的规则
return text2
def loadDataSet():
fileurl1 = 'datasets/email/ham'
fileurl2 = 'datasets/email/spam'#垃圾邮件
postingList=[]
classVec = []
fulltext=[]
for i in range(1,26):
f1 = open(fileurl1+'/{}.txt'.format(i),errors='ignore')
text1 = textParse(f1.read())
postingList.append(text1)
fulltext.extend(text1)
classVec.append(1)
f2 = open(fileurl2+'/{}.txt'.format(i),'r')
text2 = textParse(f2.read())
postingList.append(text2)
fulltext.extend(text2)
classVec.append(0)
return postingList,classVec,fulltext
这里一共有两个函数,textParse()和loadDataSet()
textParse()方法用来去筛选掉邮件中的标点符号和长度小于2的单词
loadDataSet() 加载不同文件夹下的txt文件,里面存放着邮件的内容。文件ham表示文件下面的邮件为非垃圾邮件;文件spam表示文件下面的邮件为垃圾邮件。
垃圾邮件和非垃圾邮件各有25个,一共有50个邮件
二、创建词向量
def setOfWords2Vec(vocabList,inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]+=1
else:
print("the word is not in my vocabulary:",word)
return returnVec
vocabList 就是步骤中咱们说到的所有邮件出现的单词列表
inputSet 输入数据,也就是经过咱们处理过后的邮件单词列表
这里主要就一个循环判断语句,用来统计inputSet 输入的数据中,单词出现的次数,以此来构建词向量
三、训练函数
def trainNB0(trainMatrix,trainCategory):
#print("trainMatrix",trainMatrix)
numTrainDocs = len(trainMatrix)#共有几个文本
numWords = len(trainMatrix[0])#共有多少单词
pAbusive = sum(trainCategory)/float(numTrainDocs)#文本中有多少是侮辱性文档
p0Num = np.ones(numWords)#创建全为1的数组
p1Num = np.ones(numWords)#
p0Denom = 1.0*numWords#
p1Denom = 1.0*numWords
for i in range(numTrainDocs):
print("trainCategory[i]",trainCategory[i])
if trainCategory[i]==1:#文档的分类
p1Num+=trainMatrix[i]#计算每个单词出现的次数
p1Denom+=sum(trainMatrix[i])#统计文档类型属于1的所有文档单词个数
else:
p0Num+=trainMatrix[i]
p0Denom+=sum(trainMatrix[i])
p1Vect = np.log(p1Num/p1Denom)
p0Vect = np.log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
输入数据:
trainMatrix :测试数据集的词向量列表
trainCategory:测试数据集的邮件分类情况,垃圾邮件为1 非垃圾邮件为0
处理过程:
这里通过一个for循环,根据测试数据的邮件分类情况,对各个类别的词向量进行相加,并统计所有的单词数量。
然后将相加后的向量除以单词总数,计算出单词出现的频率
最后使用log函数进行转化
返回数据:
p0Vect:类型为0的经log函数转化后的词向量频率
p1Vect:类型为1的经log函数转化后的词向量频率
pAbusive:数据集中垃圾邮件的概率
四、分类函数
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p0 = np.sum(vec2Classify*p0Vec)+np.log(pClass1)
p1 = np.sum(vec2Classify*p1Vec)+np.log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
输入数据:
vec2Classify 输入数据的词向量
p0Vec、p1Vec、pClass1 均为trainNB0()函数的返回值
处理过程:
先拓展一个知识点:
这个是咱们前面提到的一个条件概率,d是词向量。
我们直到,在代码中,词向量是使用列表来表示,但是这里要得出一个数,怎么计算呢
概率论中有一个这样的公式:
当然前提假设,词向量中的各个值之间是相互独立的。
如果这里不明白的,可以去看一下概率论基础补一下
由于前面咱们使用了log函数,所以有
log(axbxcxd) = log(a)+log(b)+log©+log(d)
所以在代码中使用求和进行计算
最后对p0和p1进行概率比较,值较大的就是输入数据的所属类
五、测试函数
def testingNB():
listOPosts,listClasses,fulltext = loadDataSet()
# myVocabList = createVocabList(listOPosts)
myVocabList = list(set(fulltext))
#print("单词列表:",myVocabList)
#随机取10个邮件作为测试使用
#print("listOPosts.length:",len(listOPosts))
trainsetnum = list(range(50))
testset = []
for i in range(10):
index = int(random.uniform(0,len(trainsetnum)))
testset.append(trainsetnum[index])
del(trainsetnum[index])
print("testset:",testset)
#重新组装训练数据和测试数据
traindata = []
trainclass=[]
for j in trainsetnum:
traindata.append(setOfWords2Vec(myVocabList,listOPosts[j]))#这里直接转化为词向量
trainclass.append(listClasses[j])
testdata=[]
testclass=[]
for k in testset:
testdata.append(setOfWords2Vec(myVocabList,listOPosts[k]))
testclass.append(listClasses[k])
p0V,p1V,pSpam = trainNB0(traindata,trainclass)
errorcount=0
for i in range(len(testdata)):
wordVector = testdata[i]
print("输出最终分类结果:",classifyNB(wordVector,p0V,p1V,pSpam))
print("输出原本结果-:",testclass[i])
if testclass[i]!=classifyNB(wordVector,p0V,p1V,pSpam):
errorcount+=1
print(" the error rate is:{}".format(errorcount))
这个函数只是进行测试模型的精准度,我测试了大概10次,一共出现错误没超过5个,也就是相当于正确率在95%以上,还是挺准的。
这里将训练数据为40个邮件,测试数据为10个邮件。
总结
这里咱们仅仅是贝叶斯公式的一个小小应用,我们实验的前提是这些单词之间没有什么联系,都是独立的个体。但是在实际中,有些单词总是会出现在某些单词后面或者前面,比如 am 总是出现在I后面,这些信息咱们没有充分利用到。
代码中,我们使用对数函数来解决下溢出问题(很多非常小的数相乘会失准),使用了将所有词出现的次数初始化为1,解决了一个概率为0总体乘积为0的情况。
总的来说,朴素贝叶斯公式还是一个比较优良的分类器,后面等我们学到更多分类算法后,再于其进行对比。
转载:https://blog.csdn.net/lzx159951/article/details/106211654