写在前面
本次朴素贝叶斯算法是在Vscode+Anaconda(python3)的环境下进行的,环境还未搭建的可以参考下面这篇文章,详细介绍如何搭建环境,同时,本次代码用到的python第三方库,如下:
python
# Name Version
jieba 0.42.1
numpy 1.13.1
一、原理解析
在正式讲解朴素贝叶斯原理之前,需要先解释几个概念方便后续原理的讲解
概念解释
先验概率和后验概率(条件概率)
以下是引用维基百科中对于先验概率和后验概率的定义
先验概率 是指根据以往经验和分析得到的概率,如全概率公式,它往往作为"由因求果"问题中的"因"出现。
后验概率 是指通过调查或其它方式获取新的附加信息,利用贝叶斯公式对先验概率进行修正,而后得到的概率。
从上面的定义可以看出其实所谓先验概率其实是事情还没有发生,要求这件事情发生的可能性的大小 ,而后验概率则是事情已经发生,要求这件事情发生的原因是由某个因素引起的可能性的大小。
因此,形似P(C)用于表示"先验概率",而形似P(c|x)用于表示后验概率,也就是条件概率
判别式模型和生成式模型
判别式模型
判别模型,也称为条件模型,是一类用于统计分类的模型,尤其是在有监督的机器学习中。判别分类器试图通过仅依赖于观察到的数据进行建模,同时学习如何从给定的统计数据进行分类。监督学习中使用的方法可以分为判别模型或生成模型。
例如,给定一组狗和兔子的标记图片,辨别模型将新的未标记图片与最相似的标记图片匹配,然后给出标签类,狗或兔子。
生成式模型
生成式模型是一类可以学习数据分布并基于此生成新数据的深度学习模型。生成式模型通过大量样本数据来学习该数据集的概率分布,比如图像的像素分布规律或文本中的词汇分布等。学习到数据分布后,模型就可以基于此分布生成新的样本数据。新数据与训练数据具有相似的统计特征,但内容是新的。 典型的生成模型方法包含朴素贝叶斯,高斯混合模型等。
生成式模型通常采用无监督学习的方式,它不需要标签,仅通过输入的数据自行学习其分布。
贝叶斯公式
在上面的对于生成式模型和判别式模型概念阐述中也提到了,朴素贝叶斯是属于生成式模型的,而对于生成式模型,必然是围绕以下公式展开
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P ( c ∣ x ) = P ( x , c ) P ( x ) P(c|x)=\frac{P(x,c)}{P(x)} </math>P(c∣x)=P(x)P(x,c)
其中P(c|x)属于后验概率,P(c)属于先验概率
上述公式P(c|x)是我们要求的值,P(C)我们可以基于数据集可以预先算出,但是P(x,c)正常情况下是无法算出的,因此,这时我们考虑利用贝叶斯定律,将P(x,c)转换为我们可以计算的公式,由
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P ( A ∣ B ) = P ( A ∩ B ) p ( B ) P(A|B)=\frac{P({A}\cap{B})}{p(B)} </math>P(A∣B)=p(B)P(A∩B)
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P ( B ∣ A ) = P ( A ∩ B ) P ( A ) P(B|A)=\frac{P({A}\cap{B})}{P(A)} </math>P(B∣A)=P(A)P(A∩B)
结合上述两项公式,可以得出以下公式
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P ( c ∣ x ) = P ( c ) P ( x ∣ c ) P ( x ) P(c|x)=\frac{P(c)P(x|c)}{P(x)} </math>P(c∣x)=P(x)P(c)P(x∣c)
其中P(C)表示c类型的数据占总数据集的比例,P(x|c)表示在c类型下取到x的概率,P(x)表示x在总数据集出现的概率,在进行分类P(x)的值是相同的,因此,在实际实现朴素贝叶斯算法时,可以不用考虑。
由于在真正计算之前,P(c)的值已经知晓不需要重复计算,因此,在算法实现过程中,只有P(x|c)需要着重计算
在实际情况中一个类别不可能只有一个标签,应该是具有多个标签的,因此,在朴素贝叶斯中假设每个标签的概率互不影响,于是,可以将上述公式重写为
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> P ( c ∣ x ) = P ( c ) P ( x ) ∏ i = 1 d P ( x i ∣ c ) P(c|x)=\frac{P(c)}{P(x)}\prod^d_{i=1}{P(x_i|c)} </math>P(c∣x)=P(x)P(c)i=1∏dP(xi∣c)
其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> x i x_i </math>xi代表各个标签,d代表总的标签数。
二、案例实现
垃圾邮箱识别
导入相关库
python
import numpy as np
import os
import re
import math
数据准备
python
def getData(textclass):
datapath = dalist_path+"\email\\"+textclass # 文本存储路径
filename = os.listdir(datapath)
wordlist = [] # 存储拆分出来的单词
count = 0
for name in filename:
if count < 10:
fr = open(datapath+"\\"+name)
data = fr.readlines() # 读取文件内容
for i in data:
i = i.strip()
word = re.findall('[a-zA-Z]+',i) # 正则表达式提取单词
if len(word) != 0:
for j in word:
wordlist.append(j) # 将提取出来的单词添加到列表中
count = count + 1
return amount(wordlist=wordlist,textclass=textclass)
这里是预先导入文本数据,并通过正则表达式提取文本中的单词,为后续数据统计做准备。
统计各个单词在文本出现的频率
python
# 统计同类型文本数量以及文本中单词出现的次数,以及总文本数量
def amount(wordlist,textclass):
datapath = dalist_path+"\email\\"+textclass
classnum = len(os.listdir(datapath)) # 同类型文本数量
wordsum = len(wordlist) # 总的单词数
wordnum = {} # 每个单词出现的次数
for i in wordlist:
wordnum[i] = wordnum.get(i,0) + 1
return wordsum,wordnum,classnum
对前面数据进行进一步处理和统计
朴素贝叶斯核心实现
python
def bayes(testemail):
spam_wdsum,spam_wdnum,spam_num = getData("spam") # 计算训练集中的单词总数,每个单词出现的次数以及共有多少同类型文本
ham_wdsum,ham_wdnum,ham_num = getData("ham") # 计算训练集中的单词总数,每个单词出现的次数以及共有多少同类型文本
prob_ham = math.log(ham_num/(ham_num+spam_num)) # 计算正常邮件出现的概率
prob_spam = math.log(spam_num/(ham_num+spam_num)) # 计算垃圾邮件出现的概率
# print(prob_ham)
# prob_ham = ham_num/(ham_num+spam_num)
# prob_spam = spam_num/(ham_num+spam_num)
# print(computs(testemail=testemail,wdnum=ham_wdnum,wdsum=ham_wdsum))
# print(computs(testemail=testemail,wdnum=spam_wdnum,wdsum=spam_wdsum))
prob_ham = prob_ham + computs(testemail=testemail,wdnum=ham_wdnum,wdsum=ham_wdsum) # 计算属于该类别的概率
prob_spam = prob_spam + computs(testemail=testemail,wdnum=spam_wdnum,wdsum=spam_wdsum)
return "ham" if prob_ham<prob_spam else "spam"
在原始的贝叶斯公式中分子是连乘,容易造成结果下溢使最终结果出现差异,因此,对于分子取对数,对于结果没有任何影响同时可以避免下溢的风险。
测试
正常邮箱识别测试
python
text= ['10.txt','11.txt','12.txt','13.txt','14.txt','15.txt','16.txt','17.txt','23.txt','25.txt']
for i in text:
datapath = dalist_path+"\email\\"+"ham\\"+i
data = open(datapath).readlines()
print("预测结果%s,真实结果%s" % (bayes(data),"ham"))
测试结果

垃圾邮箱识别测试
python
text= ['10.txt','11.txt','12.txt','13.txt','14.txt','15.txt','16.txt','17.txt','23.txt','25.txt']
for i in text:
datapath = dalist_path+"\email\\"+"spam\\"+i
data = open(datapath).readlines()
print("预测结果%s,真实结果%s" % (bayes(data),"spam"))
测试结果

邮箱类型识别
三、内容总结
算法实现过程中出现的错误及解决方法
在对条件概率取对数后分类结果总是和真实结果相反
--->由于在取对数之前条件概率小于1,因此会越加越小,越小的越接近真实结果
算法特性总结
优点
- 算法简单,易于实现。朴素贝叶斯假设每个特征之间条件独立,这使得模型学习和分类都很简单
- 适用于大数据集。朴素贝叶斯可以处理高维特征空间,适用于大规模数据集。
缺点
- 假设所有特征条件独立,但实际上特征之间可能相关。这会影响分类效果。
- 对异常值和缺失值不太鲁棒。一个异常值可能会严重影响概率估计。
- 无法处理连续值特征。需要预处理,如将连续值离散化。
- 可能过拟合简单的数据集。对于小规模或低噪声数据集,可能会过拟合训练数据。
- 无法评估特征重要性。每个特征被视为平等重要。
四、补充
源码及数据集下载机器学习: 机器学习 学习记录过程 (gitee.com)