声明:参考书目《机器学习实战》作者: Peter Harrington 出版社: 人民邮电出版社 译者: 李锐 / 李鹏 / 曲亚东 / 王斌
声明:参考书目《统计学习方法》作者: 李航 出版社: 清华大学出版社 ISBN: 9787302275954
声明:参考书目《机器学习》作者: 周志华 出版社: 清华大学出版社 ISBN: 9787302423287
参考博客 Jack-Cui 作者个人网站:http://cuijiahua.com/
参考博客 深入理解朴素贝叶斯(Naive Bayes)
参考博客 带你彻彻底底搞懂朴素贝叶斯公式
目录
一 基于概率论的分类方法:朴素贝叶斯
二 概率论
2.1 条件概率
2.2 贝叶斯准则
2.3 使用条件概率进行分类
2.4 习题
三 使用朴素贝叶斯进行文档分类
3.1 准备数据:从文本中构建词向量
3.2 训练算法:从词向量计算概率
3.3 测试算法:根据实际情况修改分类器
3.3.1 修改分类器
3.3.2 朴素贝叶斯分类函数
概率论是许多机器学习的基础,所以深刻理解这一主题就显得十分重要。我们这一节会给出利用概率论进行分类的方法——朴素贝叶斯分类器。我们称之为“朴素”,是因为整个形式化过程只进行最原始、最简单的假设。我们会利用python的文本处理能力将文档划分成词向量,然后利用词向量对文档进行分类。我们还将构建另外一个分类器,进行垃圾邮件的分类。
接下来我们需要讲解一下概率论的知识。
条件概率是指事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为:P(A|B),读作“在B的条件下A的概率”。其公式表示为:
我们对其进行推导可得
其中 A 为数据的类别,B 为数据的具体特征。则上述公式的实际意义可以表示为:在特征 B 存在的情况下,数据分到类别 A 的概率为多少。那么我们接下来就是要想办法求取公式的分子以及分母。P(A)为类别 A 占总数据特征的概率。
对上面的公式进行变形就可以得到公式:
我们把P(A)称为"先验概率"(Prior probability), 即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability), 即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likelyhood), 这是一个调整因子,使得预估概率更接近真实概率。
所以,条件概率可以理解成下面的式子: 后验概率 = 先验概率 x 调整因子
这就是贝叶斯推断的含义。我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。
假设我们利用贝叶斯分类器来计算两个概率 P1(x,y)和 P2(x,y):
如果 P1(x,y)> P2(x,y),那么属于类别1;
如果 P1(x,y)< P2(x,y),那么属于类别2。
但这两个准则并不是贝叶斯决策理论的所有内容。使用 p1,p2 只是为了尽可能简化描述,而真正需要计算和比较的是 P1(c1 | x,y)和 P2(c2 | x,y)。这些符号代表的具体意义是:给定某个由 x,y 表示的数据点,那么该数据点来自类别 c1或者c2的概率是多少,那么我们就可以利用贝叶斯准则来交换概率中条件与结果。具体的,应用贝叶斯准则可以得到:
使用这些定义,可定义贝叶斯分类准则为:
如果 P(c1 | x,y)> P(c2 | x,y),那么属于类别 c1;
如果 P(c1 | x,y)< P(c2 | x,y),那么属于类别 c2。
朴素贝叶斯的朴素指的是各特征之间是独立的,所谓独立,就是可以将两个特征相乘的概率拆成两个相乘的特征概率,即 P( x y ) = P( x ) * P( y )。所以朴素贝叶斯准则可以变成:P(Xi,Yi| C0)=P(x0,y0 | C0)P(x1,y1 | C0)...P(xn,yn | C0)
纯理论没有啥效果,现在就让我们利用一个实例来进行讲解,实例来源:《机器学习》
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 密度 | 含糖率 | 好瓜 |
1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | — | — | 是 |
2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | — | — | 是 |
3 | 乌黑 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | — | — | 是 |
4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | — | — | 是 |
5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | — | — | 是 |
6 | 青绿 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | — | — | 是 |
7 | 乌黑 | 稍蜷 | 浊响 | 稍糊 | 稍凹 | 软粘 | — | — | 是 |
8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | — | — | 是 |
9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | — | — | 否 |
10 | 青绿 | 硬挺 | 清脆 | 清晰 | 平坦 | 软粘 | — | — | 否 |
11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | — | — | 否 |
12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | — | — | 否 |
13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | — | — | 否 |
14 | 浅白 | 稍蜷 | 沉闷 | 稍糊 | 凹陷 | 硬滑 | — | — | 否 |
15 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | — | — | 否 |
16 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 硬滑 | — | — | 否 |
17 | 青绿 | 蜷缩 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | — | — | 否 |
现在我们利用这个数据集来测试一下朴素贝叶斯准则:对下面的数据进行分类
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 密度 | 含糖率 | 好瓜 |
测1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | — | — | ? |
我们先来写一下每个类别下的计算公式:
(1)P(好瓜=是 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)=P(青绿,蜷缩,浊响,清晰,凹陷,硬滑 | 好瓜=是)P(好瓜=是) / P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
=P(青绿 | 好瓜=是)* P(蜷缩 | 好瓜=是)* P(浊响 | 好瓜=是)* P(清晰 | 好瓜=是)* P(凹陷 | 好瓜=是)* P(硬滑 | 好瓜=是)* P(好瓜=是)/ P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
其中分母(全概率公式):P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
=P(好瓜=是)* P(青绿 | 好瓜=是)* P(蜷缩 | 好瓜=是)* P(浊响 | 好瓜=是)P(清晰 | 好瓜=是)* P(凹陷 |好瓜=是)* P(硬滑 | 好瓜=是) + P(好瓜=否)* P(青绿 | 好瓜=否)* P(蜷缩 | 好瓜=否)* P(浊响 | 好瓜=否)* P(清晰 | 好瓜=否)* P(凹陷 | 好瓜=否)* P(硬滑 | 好瓜=否)* P(好瓜=否)
(2)P(好瓜=否 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)=P(青绿,蜷缩,浊响,清晰,凹陷,硬滑 | 好瓜=否)P(好瓜=否) / P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
=P(青绿 | 好瓜=否)* P(蜷缩 | 好瓜=否)* P(浊响 | 好瓜=否)* P(清晰 | 好瓜=否)* P(凹陷 | 好瓜=否)* P(硬滑 | 好瓜=否)* P(好瓜=否)/ P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
其中分母(全概率公式):P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
=P(好瓜=是)* P(青绿 | 好瓜=是)* P(蜷缩 | 好瓜=是)* P(浊响 | 好瓜=是)P(清晰 | 好瓜=是)* P(凹陷 |好瓜=是)* P(硬滑 | 好瓜=是) + P(好瓜=否)* P(青绿 | 好瓜=否)* P(蜷缩 | 好瓜=否)* P(浊响 | 好瓜=否)* P(清晰 | 好瓜=否)* P(凹陷 | 好瓜=否)* P(硬滑 | 好瓜=否)* P(好瓜=否)
如果
P(好瓜=是 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)> P(好瓜=否 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑) 那么这个瓜是好瓜
P(好瓜=是 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)< P(好瓜=否 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑) 那么这个瓜不是好瓜
首先来估计先验概率: P(c),显然有
P(好瓜=是)= 8 / 17 ≈ 0.471
P(好瓜=否)= 9 / 17 ≈ 0.529
P(青绿 | 好瓜=是)= 3 / 8 = 0.375 P(青绿 | 好瓜=否)= 3 / 9 ≈ 0.333
P(蜷缩 | 好瓜=是)= 5 / 8 = 0.625 P(蜷缩 | 好瓜=否)= 3 / 9 ≈ 0.333
P(浊响 | 好瓜=是)= 6 / 8 = 0.750 P(浊响 | 好瓜=否)= 4 / 9 ≈ 0.444
P(清晰 | 好瓜=是)= 7 / 8 = 0.875 P(清晰 | 好瓜=否)= 2 / 9 ≈ 0.222
P(凹陷 | 好瓜=是)= 5 / 8 = 0.625 P(凹陷 | 好瓜=否)= 2 / 9 ≈ 0.222
P(硬滑 | 好瓜=是)= 6 / 8 = 0.750 P(硬滑 | 好瓜=否)= 6 / 9 ≈ 0.667
分母:
P(青绿,蜷缩,浊响,清晰,凹陷,硬滑)
= 0.471 * 0.375 * 0.625 * 0.750 * 0.875 * 0.625 * 0.750 + 0.529 * 0.333 * 0.333 * 0.444 * 0.222 * 0.222 * 0.667
≈ 0.471 * 0.072 + 0.529 * 1.618*10^(-4) = 0.034806
故:
P(好瓜=是 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)
= 0.375 * 0.625 * 0.750 * 0.875 * 0.625 * 0.750 * 0.471 / 0.034806 = 0.9754 = 97.54%
P(好瓜=否 | 青绿,蜷缩,浊响,清晰,凹陷,硬滑)
= 0.333 * 0.333 * 0.444 * 0.222 * 0.222 * 0.667 * 0.529 / 0.034806 = 0.0246 = 2.46%
由于 97.54% > 2.46% 所以我们刚刚的例子说明,这个瓜是好瓜
机器学习的一个重要应用就是文档的自动分类。在文档中,整个文档是实例,而电子邮件中的某些元素则构成特征。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。
成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现所有文档中的单词,再决定将哪些单词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,我们先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。代码如下:
import numpy as np
"""
Function:提供训练数据集,以及训练数据集的标签
"""
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 #返回实验样本切分的词条和类别标签向量
"""
Function:创建一个词典,这个词典包含文档内所有词条
dataSet:输入的是训练数据集,即所有的文档
return:返回一个包含所有单词的词典
"""
def creatVocabList(dataSet):
vocabSet = set([]) #创建一个空集
for document in dataSet: #从数据集循环读入每一条数据
vocabSet = vocabSet | set(document) #采用集合将一条数据的不重复单词放入vocablist,并采用并集操作
#将曾哥数据集的单词都放入vocabset中
return list(vocabSet) #返回词典,这个词典包含数据集内所有单词,并且将其list序列化
"""
Function:如果数据中的单词在词典中,那么就将词典对应的位置置1
vocabList:输入数据集的词典
inputSet:输入的数据集的一条数据
return:返回的是检测之后的列表
"""
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList) #创建一个全0列表,列表长度跟词典长度一样
for word in inputSet: #从单条数据中循环读取所有单词
if word in vocabList: #如果单词在词典中,那么就将全0列表returnvec相应的位置置1
returnVec[vocabList.index(word)] = 1
return returnVec #返回的是检测之后的列表
if __name__ == "__main__":
dataSet, Labels = loadDataSet()
vocabList = creatVocabList(dataSet)
for item in dataSet:
returnVec = setOfWords2Vec(vocabList, item)
print(item)
print(returnVec)
我们将数据集,以及数据集在词典中对应的数据打印出来,结果如下:
我们在获得词典,即词汇表之后,就可以使用 etOfWordsVecc()函数,该函数的输入参数为词汇表及某个文档,输出的是文档向量,向量的每一个元素为 1 或 0,分别表示词汇表中的单词在输入文档中是否出现。函数首先创建一个和词汇表等长的向量,并将其元素全部都设置为 0 。接着,遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出文档向量中的对应值设置为1.一切顺利的话,就不需要检查某个词是否还在vocabList 中。
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
[0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0]
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid']
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1]
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him']
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0]
['stop', 'posting', 'stupid', 'worthless', 'garbage']
[0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him']
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']
[0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
Process finished with exit code 0
我们现在已经知道了一个单词是否在文档中,接下来我们就需要使用这些文字来计算概率。我们现在用到的是就是上面使用的贝叶斯准则 来对每个类计算概率,然后比较概率值的大小。那么我们如何来计算呢?
首先可以通过类别 i (侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算 P(Ci)。接下来计算 P(w|Ci),这里就会用到朴素贝叶斯假设。将 w 展开为一个个独立的特征,那么就可以将上述概率写作 P(w0,w1,w2...wn | Ci)。由于我们假设每个特征之间是独立的,那么就意味着我们可以将上述计算变为P(w0 | Ci)P(w1 | Ci)P(w2 | Ci). . . P(wn | Ci)。这就极大地简化了计算过程:
该函数的伪代码为:
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中 —> 增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
现在我们来实现上面的伪代码:我们下面代码实现的是计算先验概率 P(Ci)以及调整函数 例:P(x,y,z|C) = P(x|c)P(y|C)P(z|C)
import numpy as np
"""
Function:提供训练数据集,以及训练数据集的标签
"""
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 #返回实验样本切分的词条和类别标签向量
"""
Function:创建一个词典,这个词典包含文档内所有词条
dataSet:输入的是训练数据集,即所有的文档
return:返回一个包含所有单词的词典
"""
def creatVocabList(dataSet):
vocabSet = set([]) #创建一个空集
for document in dataSet: #从数据集循环读入每一条数据
vocabSet = vocabSet | set(document) #采用集合将一条数据的不重复单词放入vocablist,并采用并集操作
#将曾哥数据集的单词都放入vocabset中
return list(vocabSet) #返回词典,这个词典包含数据集内所有单词,并且将其list序列化
"""
Function:如果数据中的单词在词典中,那么就将词典对应的位置置1
vocabList:输入数据集的词典
inputSet:输入的数据集的一条数据
return:返回的是检测之后的列表
"""
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList) #创建一个全0列表,列表长度跟词典长度一样
for word in inputSet: #从单条数据中循环读取所有单词
if word in vocabList: #如果单词在词典中,那么就将全0列表returnvec相应的位置置1
returnVec[vocabList.index(word)] = 1
return returnVec #返回的是检测之后的列表
"""
朴素贝叶斯分类器训练函数
Function:计算词条在相应类别下的条件概率
trainMattrix:setOfWords2Vec的返回值,不过是整个训练数据集的返回集而不是单调数据的
trainCategory:loadDataSet()返回的classVec,即数据集对应的类别
return:返回的是两个矩阵一个概率。两个矩阵分别是对应类别下词条的条件概率;概率是侮辱性文档占总文档的概率
"""
def trainNB0(trainMattrix, trainCategory):
numTrainDocs = len(trainMattrix) #计算训练数据集总共包含多少文档,为了循环遍历所有文档用的
numWords = len(trainMattrix[0]) #计算训练数据集有多少个词条,即词典包含多少个词,为了后面建矩阵用的
pAbusive = sum(trainCategory) / float(numTrainDocs) #获得类别A,即侮辱性文档占总文档的概率,即先验概率
p0Num = np.zeros(numWords) #创建两个一维矩阵,大小为词典长度。用于统计每个类别下对应词条的个数
p1Num = np.zeros(numWords) #便于后续计算概率,即P(x,y,z|C)=P(x|c)P(y|C)P(z|C),这个例子中只有一个特征
p0Denom = 0.0 #这两个数是记录对应类别里面总共有多少个词条的,即分母
p1Denom = 0.0
for i in range(numTrainDocs): #循环遍历数据集所有文档
if trainCategory[i ] == 1: #如果文档对应label =1 ,说明是侮辱性文档
p1Num += trainMattrix[i] #运用矩阵加法运算,将同一类别下的每个对应词条个数相加
p1Denom += sum(trainMattrix[i]) #运用数字加法,记录目前总共有多少个词条
else:
p0Num += trainMattrix[i]
p0Denom += sum(trainMattrix[i])
p1Vect = p1Num / p1Denom #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
p0Vect = p0Num / p0Denom #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
return p0Vect, p1Vect, pAbusive
if __name__ == "__main__":
dataSet, Labels = loadDataSet()
vocabList = creatVocabList(dataSet)
trainMat = []
for item in dataSet:
returnVec = setOfWords2Vec(vocabList, item)
trainMat.append(returnVec)
p0Vect, p1Vect, pAbusive = trainNB0(trainMat, Labels)
print("非侮辱性文档概率矩阵:")
print(p0Vect)
print("侮辱性文档概率矩阵:")
print(p1Vect)
print("侮辱性文档类别概率:")
print(pAbusive)
然后下面就是我们对应的输出结果:输入出的是每个词条(即单词)在对应类别下的条件概率
非侮辱性文档概率矩阵:
[0.04166667 0. 0.04166667 0.04166667 0.04166667 0.04166667
0.04166667 0.04166667 0.04166667 0. 0.04166667 0.
0.04166667 0. 0.04166667 0. 0. 0.
0. 0.04166667 0.04166667 0.04166667 0. 0.04166667
0. 0.125 0.04166667 0.04166667 0.08333333 0.04166667
0. 0.04166667]
侮辱性文档概率矩阵:
[0. 0.05263158 0. 0. 0. 0.
0. 0. 0.05263158 0.05263158 0. 0.05263158
0. 0.05263158 0. 0.05263158 0.05263158 0.05263158
0.15789474 0. 0. 0. 0.05263158 0.
0.10526316 0. 0. 0.05263158 0.05263158 0.10526316
0.05263158 0. ]
侮辱性文档类别概率:
0.5
Process finished with exit code 0
由于我们计算的是P(w0 | Ci)P(w1 | Ci)P(w2 | Ci). . . P(wn | Ci),只要其中有一个参数为 0 ,那么整个算式都将为0,因此我们可以将上述初始化函数初始化为 1,即
p0Num = np.ones(numWords) #创建两个一维矩阵,大小为词典长度。用于统计每个类别下对应词条的个数
p1Num = np.ones(numWords) #便于后续计算概率,即P(x,y,z|C)=P(x|c)P(y|C)P(z|C),这个例子中只有一个特征
然后为了抵消这种影响,分母就要初始化为 2.0,即
p0Denom = 2.0 #这两个数是记录对应类别里面总共有多少个词条的,即分母
p1Denom = 2.0
然后我们还会遇到一个问题,那就是下溢出。因为我们会进行和很多个小数相乘,为了削弱这种影响,我们需要对概率取对数,即
p1Vect = log(p1Num / p1Denom) #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
p0Vect = log(p0Num / p0Denom) #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
现在我们已经构建好完整的分类器了,下面我们就构建分类函数,顺便构建一个测试函数类似测试一下我们的分类器是否有效果。代码如下:
import numpy as np
from math import log
"""
Function:提供训练数据集,以及训练数据集的标签
"""
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 #返回实验样本切分的词条和类别标签向量
"""
Function:创建一个词典,这个词典包含文档内所有词条
dataSet:输入的是训练数据集,即所有的文档
return:返回一个包含所有单词的词典
"""
def creatVocabList(dataSet):
vocabSet = set([]) #创建一个空集
for document in dataSet: #从数据集循环读入每一条数据
vocabSet = vocabSet | set(document) #采用集合将一条数据的不重复单词放入vocablist,并采用并集操作
#将曾哥数据集的单词都放入vocabset中
return list(vocabSet) #返回词典,这个词典包含数据集内所有单词,并且将其list序列化
"""
Function:如果数据中的单词在词典中,那么就将词典对应的位置置1
vocabList:输入数据集的词典
inputSet:输入的数据集的一条数据
return:返回的是检测之后的列表
"""
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList) #创建一个全0列表,列表长度跟词典长度一样
for word in inputSet: #从单条数据中循环读取所有单词
if word in vocabList: #如果单词在词典中,那么就将全0列表returnvec相应的位置置1
returnVec[vocabList.index(word)] = 1
return returnVec #返回的是检测之后的列表
"""
朴素贝叶斯分类器训练函数
Function:计算词条在相应类别下的条件概率
trainMattrix:setOfWords2Vec的返回值,不过是整个训练数据集的返回集而不是单调数据的
trainCategory:loadDataSet()返回的classVec,即数据集对应的类别
return:返回的是两个矩阵一个概率。两个矩阵分别是对应类别下词条的条件概率;概率是侮辱性文档占总文档的概率
"""
def trainNB0(trainMattrix, trainCategory):
numTrainDocs = len(trainMattrix) #计算训练数据集总共包含多少文档,为了循环遍历所有文档用的
numWords = len(trainMattrix[0]) #计算训练数据集有多少个词条,即词典包含多少个词,为了后面建矩阵用的
pAbusive = sum(trainCategory) / float(numTrainDocs) #获得类别A,即侮辱性文档占总文档的概率,即先验概率
p0Num = np.ones(numWords) #创建两个一维矩阵,大小为词典长度。用于统计每个类别下对应词条的个数
p1Num = np.ones(numWords) #便于后续计算概率,即P(x,y,z|C)=P(x|c)P(y|C)P(z|C),这个例子中只有一个特征
p0Denom = 2.0 #这两个数是记录对应类别里面总共有多少个词条的,即分母
p1Denom = 2.0
for i in range(numTrainDocs): #循环遍历数据集所有文档
if trainCategory[i ] == 1: #如果文档对应label =1 ,说明是侮辱性文档
p1Num += trainMattrix[i] #运用矩阵加法运算,将同一类别下的每个对应词条个数相加
p1Denom += sum(trainMattrix[i]) #运用数字加法,记录目前总共有多少个词条
else:
p0Num += trainMattrix[i]
p0Denom += sum(trainMattrix[i])
p1Vect = p1Num / p1Denom #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
p0Vect = p0Num / p0Denom #返回矩阵,矩阵每个元素为对应词条在相应类别下的的条件概率
return p0Vect, p1Vect, pAbusive
"""
Function:将给定的数据按照概率的大小非为对应的类别
vec2Classify:待分类的向量
p0Vec:trainNB0返回的三个参数之一
p1Vec:trainNB0返回的三个参数之一
pClass1:trainNB0返回的三个参数之一
return:返回的是分类结果
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) #因为采用了对数运算,对数乘可以拆分为多个对数相加
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p0 > p1:
return 0
else:
return 1
if __name__ == "__main__":
dataSet, Labels = loadDataSet()
vocabList = creatVocabList(dataSet)
trainMat = []
for item in dataSet:
returnVec = setOfWords2Vec(vocabList, item)
trainMat.append(returnVec)
p0Vect, p1Vect, pAbusive = trainNB0(trainMat, Labels)
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(vocabList, testEntry))
print(testEntry, 'classifed as:', classifyNB(thisDoc,p0Vect, p1Vect, pAbusive))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(vocabList, testEntry))
print(testEntry, 'classifed as:', classifyNB(thisDoc, p0Vect, p1Vect, pAbusive))
然后下面是我们的输出结果:
['love', 'my', 'dalmation'] classifed as: 0
['stupid', 'garbage'] classifed as: 1
Process finished with exit code 0