朴素贝叶斯是有监督学习的一种分类算法,它基于“贝叶斯定理”实现,故在学习“朴素贝叶斯算法”前,有必要先了解“贝叶斯定理”。
定义:贝叶斯算法是在概率框架下实施决策的基本方法,对分类任务来说,在所有相关概率都已知的理想情形下,贝叶斯算法考虑的是如何基于这些概率和误判损失来选择最优的类别标记。
目的:解决“逆向概率问题”
贝叶斯公式:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)=\frac{P(B|A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)
P(A) :概率中最基本的符号,表示 A 出现的概率。例:在投掷骰子时,P(2) 指的是骰子出现数字“2”的概率,这个概率是 1/6。
P(B|A): 条件概率的符号,表示事件 A 发生的条件下,事件 B 发生的概率,条件概率是“贝叶斯公式”的关键所在,它也被称为“似然度”。
P(A|B) :条件概率的符号,表示事件 B 发生的条件下,事件 A 发生的概率,这个计算结果也被称为“后验概率”。
优缺点:
优点: 稳定的分类效率、对缺失数据不敏感、算法简单、分类精确度高、速度快。
缺点:对训练数据依赖性强、需要知道先验概率(基于假设或者已有训练集训练所得)
下面用两个例子说明正向概率和逆向概率的区别
正向概率:假设袋子又N个白球、M个黑球,求从中摸出黑球的概率。
逆向概率:不知道袋子中黑白球比例,从中摸出一个(或几个)球,观察去除球的颜色,由此推测袋子中黑白球的比例。
条件概率定义:指在事件B发生的情况下,事件A发生的概率。
公式:
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B)=\frac{P(A\cap B)}{P(B)} P(A∣B)=P(B)P(A∩B)
图示入下:
这里的条件概率就是图示中紫色部分占蓝色部分+紫色部分的大小。
全概率定义:如果事件A1、A2(这里仅用两个表示)构成一个完备事件组,即它们两两互不相容,其和为全集;并且P(Ai)大于0,则对任一事件B有:
P ( B ) = P ( B ∣ A 1 ) P ( A 1 ) + P ( B ∣ A 2 ) P ( A 2 ) P(B) = P(B|A_{1})P(A_{1})+P(B|A_{2})P(A_{2}) P(B)=P(B∣A1)P(A1)+P(B∣A2)P(A2)
通过上述信息相信你对条件概率已经有了一定的了解,那么接下来咋们就来推导一下贝叶斯公式吧。
首先我们回顾一下贝叶斯公式:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)=\frac{P(B|A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)
根据我们上述所讲的条件概率可知:
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B)=\frac{P(A\cap B)}{P(B)} P(A∣B)=P(B)P(A∩B)P ( B ∣ A ) = P ( B ∩ A ) P ( A ) P(B|A)=\frac{P(B\cap A)}{P(A)} P(B∣A)=P(A)P(B∩A)
由我们概率论所学(高中的概率与统计也有涉及):
P ( B ∩ A ) = P ( A ∩ B ) P(B\cap A) = P(A\cap B) P(B∩A)=P(A∩B)
所以有:
P ( A ∣ B ) P ( B ) = P ( A ) P ( B ∣ A ) P(A|B)P(B)=P(A)P(B|A) P(A∣B)P(B)=P(A)P(B∣A)
根据换项可知:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)=\frac{P(B|A)P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)
得证。
原理:利用已知的样本结果信息,反推最具有可能(最大概率)导致这些样本结果出现的模型参数值!
求解过程:
求解极大似然函数:
ML估计:求使得出现该组样本的概率最大的θ值。
θ ^ = a r g m a x θ ∏ i = 1 N p ( x i ∣ θ ) \hat{\theta} = arg\underset{\theta }{max} \prod_{i=1}^{N}p(x_{i} |\theta) θ^=argθmaxi=1∏Np(xi∣θ)
实际中为了便于分析,定义了对数似然函数:
H ( θ ) = ln l ( θ ) H(\theta )= \ln_{}{l(\theta )} H(θ)=lnl(θ)θ ^ = a r g m a x θ H ( θ ) = a r g m a x θ ∑ i = 1 N ln p ( x i ∣ θ ) \hat{\theta} = arg\underset{\theta }{max}H(\theta )=arg\underset{\theta }{max}\sum_{i=1}^{N}\ln_{}{p(x_{i}|\theta )} θ^=argθmaxH(θ)=argθmaxi=1∑Nlnp(xi∣θ)
未知参数只有一个(θ为标量)的情况。在似然函数满足连续、可微的正则条件下,极大似然估计量是下面微分方程的解:
未知参数有多个(θ为向量)则θ可表示为具有S个分量的未知向量:
记梯度算子:
若似然函数满足连续可导的条件,则最大似然估计量就是如下方程的解。
方程的解只是一个估计值,只有在样本数趋于无限多的时候,它才会接近于真实值。
定义:朴素贝叶斯分类(NBC)是以贝叶斯定理为基础并且假设特征条件之间相互独立的方法,先通过已给定的训练集,以特征词之间独立作为前提假设,学习从输入到输出的联合概率分布,再基于学习到的模型,输入X求出使得后验概率最大的输出 Y。
朴素贝叶斯的基础假设:
①每个特征相互独立;
②每个特征的权重(或重要性)都相等,即对结果的影响程度都相同。
由朴素贝叶斯算法可得:
P ( Y ∣ X ) = P ( X ∣ Y ) P ( Y ) P ( X ) P(Y|X)=\frac{P(X|Y)P(Y)}{P(X)} P(Y∣X)=P(X)P(X∣Y)P(Y)
P(Y)称为”先验概率”:即在B事件发生之前,我们对A事件概率的一个判断。
P(Y|X)称为”后验概率”:在B事件发生之后,我们对A事件概率的重新评估。
P(X|Y)/P(X)称为”可能性函数”:这是一个调整因子,使得预估概率更接近真实概率。
所以可以得到以下结论:
后验概率=先验概率∗调整因子(可能性函数)
朴素贝叶斯基于各特征之间相互独立,在给定类别为y的情况下,上式可以进一步表示为下式:
P ( X ∣ Y = y ) = ∏ i = 1 d P ( x i ∣ Y = y ) P(X|Y=y)=\prod_{i=1}^{d}P(x_{i}|Y=y) P(X∣Y=y)=i=1∏dP(xi∣Y=y)
由以上两式可以计算出后验概率为:
P p o s t = P ( Y ∣ X ) = P ( Y ) ∏ i = 1 d P ( x i ∣ Y ) P ( X ) P_{post}=P(Y|X)=\frac{P(Y) {\textstyle \prod_{i=1}^{d}}P(x_{i}|Y) }{P(X)} Ppost=P(Y∣X)=P(X)P(Y)∏i=1dP(xi∣Y)
即:
P ( Y ∣ X ) = P ( x 1 ∣ Y ) P ( x 2 ∣ Y ) P ( x 3 ∣ Y ) ⋅ ⋅ ⋅ P ( x n ∣ Y ) P ( Y ) P ( X ) P(Y|X)=\frac{P(x_{1}|Y)P(x_{2}|Y)P(x_{3}|Y)···P(x_{n}|Y)P(Y)}{P(X)} P(Y∣X)=P(X)P(x1∣Y)P(x2∣Y)P(x3∣Y)⋅⋅⋅P(xn∣Y)P(Y)
由于P(X)的大小是固定不变的,因此在比较后验概率时,只比较上式的分子部分即可。因此可以得到一个样本数据属于类别yi的朴素贝叶斯计算:
P ( y i ∣ x 1 , x 2 , ⋅ ⋅ ⋅ , x d ) = p ( y i ) ∏ j = 1 d P ( x j ∣ y i ) ∏ j = 1 d P ( x j ) P(y_{i}|x_{1},x_{2},···,x_{d})=\frac{p(y_{i}) {\textstyle \prod_{j=1}^{d}P(x_{j}|y_{i})} }{ {\textstyle \prod_{j=1}^{d}P(x_{j})} } P(yi∣x1,x2,⋅⋅⋅,xd)=∏j=1dP(xj)p(yi)∏j=1dP(xj∣yi)
说了那么多,好像公式也就是原本的贝叶斯公式中一维变量变成了多维变量,然后再结合全概率公式进行计算得到的结果。
那“朴素”体现在哪里呢?
朴素:假设各个特征之间相互独立,这个很重要,不然一切都是白费,不独立,条件概率和全概率就不成立了。所以“朴素”是朴素贝叶斯成立的大前提。
P(X|Y)/P(X)我们定义它为可能性函数,除了用于后验概率的计算,还可以判断对先验概率的效果,我们当然希望对先验概率增强的效果越大越好。
如果”可能性函数” >1,意味着”先验概率”被增强,事件A的发生的可能性变大;
如果”可能性函数” =1,意味着B事件无助于判断事件A的可能性;
如果”可能性函数” <1,意味着”先验概率”被削弱,事件A的可能性变小。
若某个属性值在训练集中没有与某个类同时出现过,则训练后的模型会出现 over-fitting 现象。例如:出现一个训练集中没有出现的测试样例,那么该样例的概率就为0。因为概率是0的原因,那么对判断结果起到的影响非常大,会直接排除某些类别的预测。所以为了避免其他属性携带的信息,被训练集中未出现的属性值“ 抹去” ,在估计概率值时通常要进行“拉普拉斯修正”。
d:属性数目
xi:x在第i个属性上的取值
c:类别
P ( c ∣ x ) = P ( c ) P ( c ∣ x ) p ( x ) = p ( c ) p ( x ) ∏ i = 1 d p ( x i ∣ c ) P(c|\mathbf{x} )=\frac{P(c)P(c|\mathbf{x} )}{p(\mathbf{x} )} =\frac{p(c)}{p(\mathbf{x}) } \prod_{i=1}^{d}p(x_{i}|c) P(c∣x)=p(x)P(c)P(c∣x)=p(x)p(c)i=1∏dp(xi∣c)
条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。
在代数中有 ,因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。
接下来,我们通过一个例子来更好的理解朴素贝叶斯算法。
li学委收集班级同学作业,发现收到的30份作业中既有作业答案又有对自己的检举信,其中有15份作业,14份检举信,但是有一个人X的作业他没看懂,于是li学委想可不可以根据收到作业的关键词来个X的作业分个类,看一下是答案还是检举信。
li学委对已有的作业进行关键词提取,取了四个关键词:
上报 明天 答案 辛苦 作业答案(16/29) 3 4 2 6 检举信(13/29) 7 2 4 0 于是可以得到以下条件概率:
P ( 上 报 ∣ 作 业 答 案 ) = 3 15 、 P ( 明 天 ∣ 作 业 答 案 ) = 4 15 、 P ( 答 案 ∣ 作 业 答 案 ) = 2 15 、 P ( 辛 苦 ∣ 作 业 答 案 ) = 6 15 P(上报|作业答案) = \frac{3}{15}、 P(明天|作业答案) = \frac{4}{15}、 P(答案|作业答案) = \frac{2}{15}、 P(辛苦|作业答案) = \frac{6}{15} P(上报∣作业答案)=153、P(明天∣作业答案)=154、P(答案∣作业答案)=152、P(辛苦∣作业答案)=156P ( 上 报 ∣ 检 举 信 ) = 7 15 、 P ( 明 天 ∣ 检 举 信 ) = 4 15 、 P ( 答 案 ∣ 检 举 信 ) = 2 15 、 P ( 辛 苦 ∣ 检 举 信 ) = 6 15 P(上报|检举信) = \frac{7}{15}、 P(明天|检举信) = \frac{4}{15}、 P(答案|检举信) = \frac{2}{15}、 P(辛苦|检举信) = \frac{6}{15} P(上报∣检举信)=157、P(明天∣检举信)=154、P(答案∣检举信)=152、P(辛苦∣检举信)=156
- li学委为了报名,从X的作业中提取到两个关键词:上报和答案两个关键词,他开始计算X的作业是作业答案还是对自己检举信的概率:(朴素贝叶斯公式得:)
P ( 作 业 答 案 ) P ( 上 报 ∣ 作 业 答 案 ) p ( 答 案 ∣ 作 业 答 案 ) = 0.0183 P(作业答案)P(上报|作业答案)p(答案|作业答案)=0.0183 P(作业答案)P(上报∣作业答案)p(答案∣作业答案)=0.0183
P ( 检 举 信 ) P ( 上 报 ∣ 检 举 信 ) p ( 答 案 ∣ 检 举 信 ) = 0.0506 P(检举信)P(上报|检举信)p(答案|检举信)=0.0506 P(检举信)P(上报∣检举信)p(答案∣检举信)=0.0506
根据比较上述两个概率,得到X搞得是一封检举信,于是li学委开始盘他了!!!
假设,li学委收到了一封来历不明的作业,作业内容为:
“ 我要上报老师,上报学校、上报社会,辛苦你交一下。”
那么上述的算法计算结果为:
P ( 作 业 答 案 ) P ( 上 报 ∣ 作 业 答 案 ) 3 p ( 辛 苦 ∣ 作 业 答 案 ) = 0.000177 P(作业答案)P(上报|作业答案)^{3}p(辛苦|作业答案)=0.000177 P(作业答案)P(上报∣作业答案)3p(辛苦∣作业答案)=0.000177P ( 检 举 信 ) P ( 上 报 ∣ 检 举 信 ) 3 p ( 辛 苦 ∣ 检 举 信 ) = 0 P(检举信)P(上报|检举信)^{3}p(辛苦|检举信)=0 P(检举信)P(上报∣检举信)3p(辛苦∣检举信)=0
哎嘿,竟然是一份作业,结果:li学委——寄。
为了让算法更好得帮助li学委,那么要怎么做呢?
我们就可以用到上述得拉普拉斯修正了:
上报 明天 答案 辛苦 作业答案(16/29) 4 5 3 7 检举信(13/29) 8 3 5 1 那么得到新的条件概率为:
P ( 上 报 ∣ 作 业 答 案 ) = 4 19 、 P ( 明 天 ∣ 作 业 答 案 ) = 5 19 、 P ( 答 案 ∣ 作 业 答 案 ) = 3 19 、 P ( 辛 苦 ∣ 作 业 答 案 ) = 7 19 P(上报|作业答案) = \frac{4}{19}、 P(明天|作业答案) = \frac{5}{19}、 P(答案|作业答案) = \frac{3}{19}、 P(辛苦|作业答案) = \frac{7}{19} P(上报∣作业答案)=194、P(明天∣作业答案)=195、P(答案∣作业答案)=193、P(辛苦∣作业答案)=197P ( 上 报 ∣ 检 举 信 ) = 8 17 、 P ( 明 天 ∣ 检 举 信 ) = 3 17 、 P ( 答 案 ∣ 检 举 信 ) = 5 17 、 P ( 辛 苦 ∣ 检 举 信 ) = 1 17 P(上报|检举信) = \frac{8}{17}、 P(明天|检举信) = \frac{3}{17}、 P(答案|检举信) = \frac{5}{17}、 P(辛苦|检举信) = \frac{1}{17} P(上报∣检举信)=178、P(明天∣检举信)=173、P(答案∣检举信)=175、P(辛苦∣检举信)=171
这样通过修正后得计算结果就是:
P ( 作 业 答 案 ) P ( 上 报 ∣ 作 业 答 案 ) 3 p ( 辛 苦 ∣ 作 业 答 案 ) = 0.000217 P(作业答案)P(上报|作业答案)^{3}p(辛苦|作业答案)=0.000217 P(作业答案)P(上报∣作业答案)3p(辛苦∣作业答案)=0.000217P ( 检 举 信 ) P ( 上 报 ∣ 检 举 信 ) 3 p ( 辛 苦 ∣ 检 举 信 ) = 0.000311 P(检举信)P(上报|检举信)^{3}p(辛苦|检举信)=0.000311 P(检举信)P(上报∣检举信)3p(辛苦∣检举信)=0.000311
选大的概率,得到这是一封检举信,盘他!!!
现实生活中,公司或者个人总是会收到大量的邮件,大部分都是垃圾邮件,那么我们如何从众多垃圾邮件中挑选出我们所需要的邮件呢?这就可以通过本次博文内容——朴素贝叶斯算法来解决。
将文本划分成一个个词组短语,然后通过类别标签对其分类,行程一个文档向量。
def loadDataSet():
"""
Returns: postingList:词条切分后的文档集合
classVec:类别标签的集合
"""
#词条切分后的文档集合
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 is abusive, 0 not
#词条切分后的文档集合和类别标签结合
return postingList,classVec
# 创建不重复词的列表 ———— 词汇表
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空集
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建两个集合的并集
return list(vocabSet) # 返回不重复的词条列表
# 输出文档向量
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("单词 %s 不在词汇表中!" % word)
return returnVec
# 测试函数效果
# 创建实验样本
listPosts, listClasses = loadDataSet()
print('数据集\n', listPosts)
# 创建词汇表
myVocabList = createVocabList(listPosts)
print('词汇表:\n', myVocabList)
# 输出文档向量
print('文档向量',setOfWords2Vec(myVocabList, listPosts[5]))
到了计算,我们也用上了朴素贝叶斯算法,通过词向量来计算概率,伪代码如下:
该函数的伪代码如下:
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中→ 增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
from numpy import *
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 获得训练的文档总数
numWords = len(trainMatrix[0]) # 获得每篇文档的词总数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算文档是侮辱类的概率
p0Num = zeros(numWords) # 创建numpy.zeros数组,初始化概率
p1Num = zeros(numWords) # 创建numpy.zeros数组,初始化概率
p0Denom = 0.0 # 初始化为0
p1Denom = 0.0 # 初始化为0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i] # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Denom += sum(trainMatrix[i])
p1Vect = p1Num / p1Denom # 侮辱类,每个元素除以该类别中的总词数
p0Vect = p0Num / p0Denom # 非侮辱类,每个元素除以该类别中的总词数
return p0Vect, p1Vect, pAbusive # p0Vect非侮辱类的条件概率数组、p1Vect侮辱类的条件概率数组、pAbusive文档属于侮辱类的概率
# 测试代码
listPosts, listClasses = loadDataSet() # 创建实验样本
myVocabList = createVocabList(listPosts) # 创建词汇表
trainMat = []
for postinDoc in listPosts: # for循环使用词向量来填充trainMat列表
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('pAb:\n', pAb)
结果:
但是,会出现以下问题:
因此,需要对其修正,提出将所有词的出现数初始化为1,并将分母初始化为2。
# 解决办法:
p0Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p1Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p0Denom = 2.0 # 初始化为2
p1Denom = 2.0 # 初始化为2
这里就要用到防溢出策略了,代码如下:
# 解决办法:
p1Vect = log(p1Num/p1Denom) # 使用log函数
p0Vect = log(p0Num/p0Denom)
改进后的朴素贝叶斯分类器训练函数:
# 改进后的朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 获得训练的文档总数
numWords = len(trainMatrix[0]) # 获得每篇文档的词总数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算文档是侮辱类的概率
p0Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p1Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p0Denom = 2.0 # 初始化为2.0
p1Denom = 2.0 # 初始化为2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i] # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # 侮辱类,每个元素除以该类别中的总词数
p0Vect = log(p0Num / p0Denom) # 非侮辱类,每个元素除以该类别中的总词数
return p0Vect, p1Vect, pAbusive # p0Vect非侮辱类的条件概率数组、p1Vect侮辱类的条件概率数组、pAbusive文档属于侮辱类的概率
# 测试代码
listPosts, listClasses = loadDataSet() # 创建实验样本
myVocabList = createVocabList(listPosts) # 创建词汇表
trainMat = []
for postinDoc in listPosts: # for循环使用词向量来填充trainMat列表
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('pAb:\n', pAb)
我们可以发现,已经没有概率为0的项了。
# 朴素贝叶斯分类器分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # 元素相乘,侮辱类概率
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) # 非侮辱类概率
if p1 > p0:
return 1
else:
return 0
代码展示:
from numpy import *
# 创建不重复词的列表 ———— 词汇集合
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空集
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建两个集合的并集
return list(vocabSet) # 返回不重复的词条列表
# 输出文档向量
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("单词 %s 不在词汇表中!" % word)
return returnVec
# 朴素贝叶斯分类器训练函数
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 获得训练的文档总数
numWords = len(trainMatrix[0]) # 获得每篇文档的词总数
pAbusive = sum(trainCategory) / float(numTrainDocs) # 计算文档是侮辱类的概率
p0Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p1Num = ones(numWords) # 创建numpy.ones数组,初始化概率
p0Denom = 2.0 # 初始化为2.0
p1Denom = 2.0 # 初始化为2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i] # 向量相加,统计侮辱类的条件概率的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i] # 向量相加,统计非侮辱类的条件概率的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom) # 侮辱类,每个元素除以该类别中的总词数
p0Vect = log(p0Num / p0Denom) # 非侮辱类,每个元素除以该类别中的总词数
return p0Vect, p1Vect, pAbusive # p0Vect非垃圾邮件的条件概率数组、p1Vect是垃圾邮件的条件概率数组、pAbusive文档属于垃圾邮件的概率
# 朴素贝叶斯分类器分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) # 元素相乘
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
# 朴素贝叶斯词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
# 文件解析
def textParse(bigString): # 输入字符串, 输出单词列表
import re
listOfTokens = re.split(r'[\W*]', bigString) # 字符串切分,去掉除单词、数字外的任意字符串
return [tok.lower() for tok in listOfTokens if len(tok) > 2] # 除了单个字母外,其他字符串全部转换成小写
# 完整的垃圾邮件测试函数
def spamTest():
docList = [] # 文档列表
classList = [] # 文档标签
fullText = [] # 全部文档内容集合
for i in range(1, 25): # 遍历垃圾邮件和非垃圾邮件各25个
wordList = textParse(open('D:/桌面/email/Spam/%d.txt' % i, encoding='utf-8').read()) # 读取垃圾邮件,并生成单词向量
docList.append(wordList) # 垃圾邮件加入文档列表
fullText.extend(wordList) # 把当前垃圾邮件加入文档内容集合
classList.append(1) # 1表示垃圾邮件,标记垃圾邮件
wordList = textParse(open('D:/桌面/email/Ham/%d.txt' % i,encoding='utf-8').read()) # 读非垃圾邮件,并生成单词向量
docList.append(wordList) # 非垃圾邮件加入文档列表
fullText.extend(wordList) # 把当前非垃圾邮件加入文档内容集合
classList.append(0) # 0表示垃圾邮件,标记非垃圾邮件,
vocabList = createVocabList(docList) # 创建不重复的词汇表
trainingSet = list(range(40)) # 为训练集添加索引
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: # for循环使用词向量来填充trainMat列表t
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex])) # 把词集模型添加到训练矩阵中
trainClasses.append(classList[docIndex]) # 把类别添加到训练集类别标签中
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
print('词表:\n', vocabList)
print('p0V:\n', p0V)
print('p1V:\n', p1V)
print('pSpam:\n', pSpam)
errorCount = 0 # 用于计数错误分类
for docIndex in testSet: # 循环遍历训练集
wordVector = bagOfWords2VecMN(vocabList, docList[docIndex]) # 获得测试集的词集模型
if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1 # 预测值和真值不一致,则错误分类计数加1
print("分类错误集", docList[docIndex])
print('错误率: ', float(errorCount) / len(testSet))
# return vocabList,fullText
这里说明:数据集很重要,因为要分词,而且中文分词会比英文难,因为英文单词用空格“ ”分隔每个单词,而中文的标点符号,em,自己感受吧。
因为我看了以下我的邮箱,没有是那么垃圾文件,或者说垃圾文件都是一样的,我就在网上找了一些垃圾邮件,而好的邮件,我发现我也没有,于是,我在网络上的优秀作文摘抄了25份。跑出来下面恐怖的东西。
上、下部分省略1w字…。
# 邮件分类器
def classifyEmail():
docList = [] # 文档列表
classList = [] # 文档标签
fullText = [] # 全部文档内容集合
for i in range(1, 21): # 遍历垃圾邮件和非垃圾邮件各20个
wordList = textParse(open('D:/桌面/email/Spam/%d.txt' % i, encoding='utf-8').read()) # 读取垃圾邮件,将大字符串并将其解析为字符串列表
docList.append(wordList) # 垃圾邮件加入文档列表
fullText.extend(wordList) # 把当前垃圾邮件加入文档内容集合
classList.append(1) # 1表示垃圾邮件,标记垃圾邮件
wordList = textParse(open('D:/桌面/email/Ham/%d.txt' % i, encoding='utf-8').read()) # 读非垃圾邮件,将大字符串并将其解析为字符串列表
docList.append(wordList) # 非垃圾邮件加入文档列表
fullText.extend(wordList) # 把当前非垃圾邮件加入文档内容集合
classList.append(0) # 0表示垃圾邮件,标记非垃圾邮件,
vocabList = createVocabList(docList) # 创建不重复的词汇表
trainingSet = list(range(40)) # 为训练集添加索引
trainMat = [] # 创建训练集矩阵训练集类别标签系向量
trainClasses = [] # 训练集类别标签
for docIndex in trainingSet: # for循环使用词向量来填充trainMat列表
trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex])) # 把词集模型添加到训练矩阵中
trainClasses.append(classList[docIndex]) # 把类别添加到训练集类别标签中
p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 朴素贝叶斯分类器训练函数
testList = textParse(open('D:/桌面/email/test/5.txt', encoding='utf-8').read()) # 读取邮件,将大字符串并将其解析为字符串列表
testVector = bagOfWords2VecMN(vocabList, testList) # 获得测试集的词集模型
if classifyNB(array(testVector), p0V, p1V, pSpam):
result = "垃圾邮件"
else:
result = "正常邮件"
print("输入邮件内容为: ")
print(' '.join(testList))
print('该邮件被分类为: ', result)
if __name__ == '__main__':
classifyEmail()
首先,就是朴素贝叶斯的缺点:使用的样本属性真实情况中并不是相互独立性的,那么这样的分类效果可能不会很好。而且需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候,会由于假设的先验模型的原因导致预测效果不佳。
当然,我们可以通过优化我们的模型或者是采用更好的算法去降低他们的相关性,直到降低到一定的值,那么他的相关性其实就可以忽略不计。
附上:网盘:
链接:https://pan.baidu.com/s/1Pk1CY9hlfMSVe4L77-PItQ
提取码:ahsg
–来自百度网盘超级会员V3的分享