ML实战(四)——朴素贝叶斯

1、朴素贝叶斯

前两章要求分类器做出艰难决策,给出 “该数据实例属于哪一类”这类问题的明确答案。不过,分类器有时会产生错误结果,这时可以要求 分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值

概率论是许多机器学习算法的基础,所以深刻理解这一主题就显得十分重要。第3章(《机器学习实战》学习笔记(三):决策树)在计算特征值取某个值的概率时涉及了一些概率知识,在那里先统计特征在数据集中取某个特定值的次数,然后除以数据集的实例总数,就得到了特征取该值的概率。本章会给出一些使用概率论进行分类的方法。首先从一个最简单的概率分类器开始,然后给出一些假设来学习朴素贝叶斯分类器。称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设

朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法,是有监督的学习算法。现实生活中朴素贝叶斯算法应用非常广泛,如文本分类,垃圾邮件的分类,信用评估,钓鱼网站检测等等。

2、基于贝叶斯决策理论的分类方法

朴素贝叶斯
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据。

朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。

假设现在有一个数据集,它由两类数据组成(红色和蓝色),数据分布如下图所示。
在这里插入图片描述
我们现在用 p 1 ( x , y ) p1(x,y) p1(x,y)表示数据点 ( x , y ) (x,y) (x,y) 属于类别(图中红色圆点表示的类别)的概率,用 p 2 ( x , y ) p2(x,y) p2(x,y) 表示数据点 ( x , y ) (x,y) (x,y) 属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点 ( x , y ) (x,y) (x,y),可以用下面的规则来判断它的类别:

  • 如果 p 1 ( x , y ) > p 2 ( x , y ) p1(x,y)>p2(x,y) p1(x,y)>p2(x,y),那么类别为1
  • 如果 p 2 ( x , y ) > p 1 ( x , y ) p2(x,y)>p1(x,y) p2(x,y)>p1(x,y),那么类别为2

也就是说,会 选择高概率所对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。回到上图中,如果该图中的整个数据使用6个浮点数(整个数据由两类不同分布的数据构成,有可能只需要6个统计参数来描述)来表示,并且计算类别概率的Python代码只有两行,那么你会更倾向于使用下面哪种方法来对该数据点进行分类?

  1. 使用第1章的kNN,进行1000次距离计算;
  2. 使用第2章的决策树,分别沿x轴、y轴划分数据;
  3. 计算数据点属于每个类别的概率,并进行比较。

使用决策树不会非常成功;而和简单的概率计算相比,kNN的计算量太大。因此,对于上述问题,最佳选择是使用刚才提到的概率比较方法。

贝叶斯???
这里使用的概率解释属于贝叶斯概率理论的范畴,该理论非常流行且效果良好。贝叶斯概率以18世纪的一位神学家托马斯·贝叶斯(Thomas Bayes)的名字命名。贝叶斯概率 引入先验知识和逻辑推理 来处理不确定命题。另一种概率解释称为频数概率(frequency probability),它只从数据本身获得结论,并不考虑逻辑推理及先验知识。

3、数学知识准备

1) 条件概率

接下来,必须要详述p1及p1概率计算方法。为了能够计算p1与p2,有必要讨论一下条件概率。

举个例子来说明,假设现在有一个装了7块石头的罐子,其中3块是灰色的,4块是黑色的。如果从罐子中随机取出一块石头,那么是灰色石头的可能性是多少?由于取石头有7种可能,其中3种为灰色,所以取出灰色石头的概率为3/7。那么取到黑色石头的概率又是多少呢?很显然,是4/7。我们使用P(gray)来表示取到灰色石头的概率,其概率值可以通过灰色石头数目除以总的石头数目来得到。
在这里插入图片描述
如果这7块石头如下图所示放在两个桶中,那么上述概率应该如何计算?
在这里插入图片描述
要计算P(gray)或者P(black),事先得知道石头所在桶的信息会不会改变结果?你有可能已经想到计算从B桶中取到灰色石头的概率的办法,这就是所谓的 条件概率(conditionalprobability)。假定计算的是从B桶取到灰色石头的概率,这个概率可以记作P(gray|bucketB),我们称之为“在已知石头出自B桶的条件下,取出灰色石头的概率”。不难得到,P(gray|bucketA)值为2/4,P(gray|bucketB) 的值为1/3。
条件概率的计算公式如下所示:
在这里插入图片描述
来看看上述公式是否合理:

  • 首先,用B桶中灰色石头的个数除以两个桶中总的石头数,得到P(gray and bucketB) = 1/7。
  • 其次,由于B桶中有3块石头,而总石头数为7,于是P(bucketB)就等于3/7。
  • 最后,有P(gray|bucketB) = P(gray and bucketB)/P(bucketB) =(1/7) / (3/7) = 1/3。
    这个公式虽然对于这个简单例子来说有点复杂,但当存在更多特征时是非常有效的。用代数方法计算条件概率时,该公式也很有用。

另一种有效计算条件概率的方法称为 贝叶斯准则。贝叶斯准则告诉我们如何交换条件概率中的条件与结果,即如果已知P(x|c),要求P(c|x),那么可以使用下面的计算方法:
在这里插入图片描述

2) 全概率公式

除了条件概率以外,在计算p1和p2的时候,还要用到全概率公式,因此,这里继续讨论一下全概率公式。

举个例子,假设样本空间S是两个事件A与A’的和,其中红色部分是事件A,绿色部分是事件A’,如下图:
在这里插入图片描述
而事件B的位置如下图,和事件A有交集但是并不是包含关系,这个时候P(B)=???
在这里插入图片描述
很明显,P(B)是两部分,其中一部分是和事件A重合的,另一部分是和事件A’重合的,这个时候分别去求两个概率相加就是P(B)了。

  • 和事件A重合的部分,根据条件概率公式,等于A的概率乘上A条件下B的概率,即P1=P(B|A)P(A);
  • 和事件A’重合的部分,根据条件概率公式,等于A’的概率乘上A’条件下B的概率,即P1=P(B|A’)P(A’);

综上可以得出全概率公式:
在这里插入图片描述

其实全概率就是 表示达到某个目的的多种方式各自概率的和

3) 贝叶斯推断

对条件概率公式进行变形,可以得到如下形式:
在这里插入图片描述

  • P(A)称为"先验概率"(Prior probability),即在B事件发生之前,对A事件概率的一个判断。
  • P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,对A事件概率的重新评估。
  • P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。

所以,条件概率可以理解成下面的式子:后验概率 = 先验概率 x 调整因子

下面做一个实验来验证一下,先预估一个"先验概率",然后加入实验结果,这样到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。在这里,如果"可能性函数"P(B|A)/P(B)>1,意味着"先验概率"被增强,事件A的发生的可能性变大;如果"可能性函数"=1,意味着B事件无助于判断事件A的可能性;如果"可能性函数"<1,意味着"先验概率"被削弱,事件A的可能性变小。
在这里插入图片描述
两个一模一样的碗,一号碗有30颗水果糖和10颗巧克力糖,二号碗有水果糖和巧克力糖各20颗。现在随机选择一个碗,从中摸出一颗糖,发现是水果糖。请问这颗水果糖来自一号碗的概率有多大?

现在假定,H1表示一号碗,H2表示二号碗。由于这两个碗是一样的,在取出水果糖之前,这两个碗被选中的概率相同,也就是等概率的,所以P(H1)=P(H2)=0.5。这个概率就叫做"先验概率",即没有做实验之前,来自一号碗的概率是0.5。

再假定,E表示水果糖,所以问题就变成了在已知拿出来的是E的情况下,这个E来自一号碗的概率有多大?即求P(H1|E)。这个概率叫做"后验概率",即在E事件发生之后,对P(H1)的修正结果。

根据条件概率变形公式,可以得到
在这里插入图片描述
现在这个题就是一个纯粹的数学问题了,已知P(H1)等于0.5,P(E|H1)为从一号碗中取出的糖是水果糖的概率,等于
30 / (30+10)=0.75,那么求出P(E)就可以得到答案。根据全概率公式
在这里插入图片描述
在这里插入图片描述
所以,得出P(H1|E)。
在这里插入图片描述
本来是0.5,但是通过调整之后,来自一号碗的概率是0.6。也就是说,取出水果糖之后,H1事件的可能性得到了增强。

调整因子 = 0.75 / 0.625 = 1.2,是 > 1的,意味着"先验概率"被增强,事件A的发生的可能性变大。

4、使用条件概率来分类

贝叶斯决策理论要求计算两个概率 p 1 ( x , y ) p1(x, y) p1(x,y) p 2 ( x , y ) p2(x, y) p2(x,y)

如果p1(x, y) > p2(x, y),那么属于类别1;
如果p2(x, y) > p1(x, y),那么属于类别2。
但这两个准则并不是贝叶斯决策理论的所有内容。使用p1( )和p2( )只是为了尽可能简化描述,而真正需要计算和比较的是 p ( c 1 ∣ x , y ) p(c_1|x, y) p(c1x,y) p ( c 2 ∣ x , y ) p(c_2|x, y) p(c2x,y)这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别 c 1 c_1 c1的概率是多少?数据点来自类别 c 2 c_2 c2的概率又是多少?注意这些概率与刚才给出的概率 p ( x , y ∣ c 1 ) p(x, y|c_1) p(x,yc1)并不一样,不过可以使用贝叶斯准则来交换概率中条件与结果。具体地,应用贝叶斯准则得到:
在这里插入图片描述
使用这些定义,可以定义贝叶斯分类准则为:

  • 如果 P ( c 1 ∣ x , y ) > P ( c 2 ∣ x , y ) P(c_1|x, y) > P(c_2|x, y) P(c1x,y)>P(c2x,y),那么属于类别 c 1 c_1 c1
  • 如果 P ( c 1 ∣ x , y ) < P ( c 2 ∣ x , y ) P(c_1|x, y) < P(c_2|x, y) P(c1x,y)<P(c2x,y),那么属于类别 c 2 c_2 c2

使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。

5、朴素贝叶斯推断

我们来看一看朴素贝叶斯推断,对比之前讲过的贝叶斯推断,你可以发现贝叶斯和朴素贝叶斯的概念是不同的,区别就在于“朴素”二字,朴素贝叶斯对条件概率分布做了条件独立性的假设。 比如下面的公式,假设有n个特征:
在这里插入图片描述
由于每个特征都是独立的,我们可以进一步拆分公式:

在这里插入图片描述
这样就可以分布计算了,举一个例子,某个医院早上来了六个门诊的病人,他们的情况如下表所示:
在这里插入图片描述
现在又来了第七个病人,是一个打喷嚏的建筑工人。请问他患上感冒的概率有多大?

根据贝叶斯定理:
在这里插入图片描述
应用实例可得:
在这里插入图片描述
根据朴素贝叶斯条件独立性的假设可知,"打喷嚏"和"建筑工人"这两个特征是独立的,因此:
在这里插入图片描述
这里带入数字可以计算:
在这里插入图片描述
因此,这个打喷嚏的建筑工人,有66%的概率是得了感冒。同理,可以得到
P ( 过 敏 ∣ 打 喷 嚏 x 建 筑 工 人 ) = ( P ( 打 喷 嚏 ∣ 过 敏 ) x P ( 建 筑 工 人 ∣ 过 敏 ) x P ( 过 敏 ) ) / ( P ( 打 喷 嚏 ) x P ( 建 筑 工 人 ) ) P(过敏 | 打喷嚏 x 建筑工人) = (P(打喷嚏 | 过敏) x P(建筑工人 | 过敏) x P(过敏)) / (P(打喷嚏) x P(建筑工人)) P(x)=(P()xP()xP())/(P()xP())
= ( 1 x 0 x 1 / 6 ) / ( 0.5 x 0.33 ) = 0 = (1 x 0 x 1/6) / (0.5 x 0.33)= 0 =(1x0x1/6)/(0.5x0.33)=0

P ( 脑 震 荡 ∣ 打 喷 嚏 x 建 筑 工 人 ) = ( P ( 打 喷 嚏 ∣ 脑 震 荡 ) x P ( 建 筑 工 人 ∣ 脑 震 荡 ) x P ( 脑 震 荡 ) ) / ( P ( 打 喷 嚏 ) x P ( 建 筑 工 人 ) ) P(脑震荡 | 打喷嚏 x 建筑工人) = (P(打喷嚏 | 脑震荡) x P(建筑工人 | 脑震荡) x P(脑震荡)) / (P(打喷嚏) x P(建筑工人)) P(x)=(P()xP()xP())/(P()xP())
= ( 0 x 0.5 x 1 / 3 ) / ( 0.5 x 0.33 ) = 0 = (0 x 0.5 x 1/3) / (0.5 x 0.33)= 0 =(0x0.5x1/3)/(0.5x0.33)=0
比较这几个概率,就可以知道他最可能得的是感冒。

这就是贝叶斯分类器的基本方法:在统计资料的基础上,依据某些特征,计算各个类别的概率,从而实现分类

6、使用朴素贝叶斯进行文档分类

机器学习的一个重要应用就是 文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但同样也可以对新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。朴素贝叶斯是上节介绍的贝叶斯分类器的一个扩展,是用于文档分类的常用算法

使用每个词作为特征并观察它们是否出现,这样得到的特征数目会有多少呢?针对的是哪一种人类语言呢?当然不止一种语言。据估计,仅在英语中,单词的总数就有500000之多。为了能进行英文阅读,估计需要掌握数千单词。

朴素贝叶斯的一般过程
(1) 收集数据:可以使用任何方法。本章使用RSS源。
(2) 准备数据:需要数值型或者布尔型数据。
(3)分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
(4)训练算法:计算不同的独立特征的条件概率。
(5)测试算法:计算错误率。
(6) 使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

假设词汇表中有1000个单词。要得到好的概率分布,就需要足够的数据样本,假定样本数为N。前面讲到的约会网站示例中有1000个实例,手写识别示例中每个数字有200个样本,而决策树示例中有24个样本。其中,24个样本有点少,200个样本好一些,而1000个样本就非常好了。约会网站例子中有三个特征。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要 N 10 N^{10} N10个样本,对于包含1000个特征的词汇表将需要 N 1000 N^{1000} N1000个样本。可以看到,所需要的样本数会随着特征数目增大而迅速增长。

如果特征之间相互独立,那么样本数就可以从 N 1000 N^{1000} N1000减少到1000×N。所谓 独(independence) 指的是统计意义上的独立,即 一个特征或者单词出现的可能性与它和其他单词相邻没有关系

举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素(naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。其实这个假设也有问题。 如果要判断留言板的留言是否得当,那么可能不需要看完所有的1000个单词,而只需要看10~20个特征就足以做出判断了。尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。

7、使用Python 进行文本分类

要从文本中获取特征,需要先拆分文本。具体如何做呢?这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条想象为单词,也可以使用非单词词条,如URL、IP地址或者任意其他字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。

以在线社区的留言板为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。

1) 准备数据:从文本中构建词向量

我们将把文本看成 单词向量 或者 词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。简单起见,先假设已经将本文切分完毕,存放到列表中,并对词汇向量进行分类标注。

编写代码如下:

'''
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:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
'''
# 函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
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:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
'''
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    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:
 [['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']]
myVocabList:
 ['stop', 'please', 'problems', 'worthless', 'mr', 'is', 'I', 'help', 'love', 'maybe', 'has', 'take', 'quit', 'ate', 'licks', 'buying', 'how', 'my', 'so', 'food', 'stupid', 'dog', 'not', 'to', 'dalmation', 'flea', 'him', 'park', 'garbage', 'steak', 'posting', 'cute']
trainMat:
 [[0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1], [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0], [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

从运行结果可以看出:

  • postingList 是原始的 词条列表;
  • myVocabList 是 词汇表,是所有单词出现的集合,没有重复的元素;
  • trainMat 是所有的词条向量组成的列表,它里面存放的是根据 myVocabList 向量化的 词条向量。

词汇表是用来干什么的?没错,它是用来将词条向量化的,一个单词在词汇表中出现过一次,那么就在相应位置记作1,如果没有出现就在相应位置记作0。

2) 训练算法:从词向量计算概率

前面介绍了如何将一组单词转换为一组数字,接下来看看如何使用这些数字计算概率。现在已经知道一个词是否出现在一篇文档中,也知道该文档所属的类别。还记得 贝叶斯准则?我们重写贝叶斯准则,将之前的x、y 替换为w。粗体w表示这是一个向量,即它由多个数值组成。在这个例子中,数值个数与词汇表中的词个数相同。
在这里插入图片描述
使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。如何计算呢?首先可以通过类别 iii(侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算概率 p ( c i ) p(c_i) p(ci)
)。接下来计算 p ( w ∣ c i ) p(w|c_i) p(wci),这里就要用到 朴素贝叶斯假设。如果将w展开为一个个独立特征,那么就可以将上述概率写作 p ( w 0 , w 1 , w 2 . . w N ∣ c i ) p(w_0,w_1,w_2..w_N|c_i) p(w0,w1,w2..wNci)。这里假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用 p ( w 0 ∣ c i ) p ( w 1 ∣ c i ) p ( w 2 ∣ c i ) . . . p ( w N ∣ c i ) p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)...p(w_N|c_i) p(w0ci)p(w1ci)p(w2ci)...p(wNci)来计算上述概率,这就极大地简化了计算的过程。

接下来,通过词条向量训练朴素贝叶斯分类器。代码如下:

import numpy as np

'''
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:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
'''
# 函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
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:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
'''
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    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)

在这里插入图片描述
运行结果如下,p0V存放的是属于类别0的单词的概率,也就是非侮辱类词汇的概率。比如p0V的正数第5个概率,就是love这个单词属于非侮辱类的概率为0.04166667,换算成百分比,也就是4.17%。同理,p1V的正数第5个概率,就是love这个单词属于侮辱类的概率为0。简单的单词love,大家都知道是属于非侮辱类的,这么看,分类还是比较准确的。pAb是所有侮辱类的样本占所有样本的概率,从classVec中可以看出,一用有3个侮辱类,3个非侮辱类。所以侮辱类的概率是0.5。

因此,p0V和p1V存放的就是myVocabList中单词的条件概率,而pAb就是先验概率。

3) 测试算法:根据现实情况修改分类器

利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算:
p ( w 0 ∣ 1 ) p ( w 1 ∣ 1 ) p ( w 2 ∣ 1 ) p(w_0∣1)p(w _1∣1)p(w _2∣1) p(w01)p(w11)p(w21)
如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。这种做法就叫做 拉普拉斯平滑(Laplace Smoothing) 又被称为 加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。

除了这个问题之外,另一个遇到的问题是下溢出, 这是由于太多很小的数相乘造成的。当计算乘积:

p ( w 0 ∣ c i ) p ( w 1 ∣ c i ) p ( w 2 ∣ c i ) . . . p ( w N ∣ c i ) p(w_0|c_i)p(w_1|c_i)p(w_2|c_i)...p(w_N|c_i) p(w0ci)p(w1ci)p(w2ci)...p(wNci)

由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(读者可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0。)一种解决办法是对乘积取 自然对数。在代数中有 l n ( a ∗ b ) = l n ( a ) + l n ( b ) ln(a*b) = ln(a)+ln(b) ln(ab)=ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用 自然对数 进行处理不会有任何损失。

下图给出函数 f ( x ) f(x) f(x) l n ( f ( x ) ) ln(f(x)) ln(f(x)) 的曲线:
在这里插入图片描述
检查这两条曲线,就会发现它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。

替换trainNB0这个函数的代码如下:

'''
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                       

在这里插入图片描述
没有0概率了,完美的解决了。

增加一个测试函数,对我们的分类器进行测试。

'''
Parameters:
    vec2Classify - 待分类的词条数组
    p0Vec - 侮辱类的条件概率数组
    p1Vec -非侮辱类的条件概率数组
    pClass1 - 文档属于侮辱类的概率
Returns:
    0 - 属于非侮辱类
    1 - 属于侮辱类
'''
# 函数说明:朴素贝叶斯分类器分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)      #element-wise mult
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: 
        return 0

# 函数说明:朴素贝叶斯分类器测试函数
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']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))

>>> testingNB()
['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1

8、示例:使用朴素贝叶斯过滤垃圾邮件

在前面那个简单的例子中,我们引入了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面这个例子中,我们将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下如何使用通用框架来解决该问题。

示例:使用朴素贝叶斯对电子邮件进行分类
(1) 收集数据:提供文本文件。
(2) 准备数据:将文本文件解析成词条向量。
(3)分析数据:检查词条确保解析的正确性。
(4) 训练算法:使用我们之前建立的trainNB0()函数。
(5)测试算法:使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率。
(6)使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

1) 准备数据:切分文本

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对于英文文本,我们可以以非字母、非数字作为符号进行切分,使用split函数即可。编写代码如下:

# -*- coding: UTF-8 -*-
import re

# 函数说明:接收一个大字符串并将其解析为字符串列表
def textParse(bigString):                                      #将字符串转换为字符列表
    #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]#除了单个字母,例如大写的I,其它单词变成小写

'''
Parameters:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
'''
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

if __name__ == '__main__':
    docList = []; classList = []
    for i in range(1, 26):                                             #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())#读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(1)                                            #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read()) #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        classList.append(0)                                            #标记非垃圾邮件,1表示垃圾文件   
    vocabList = createVocabList(docList)                               #创建词汇表,不重复
    print(vocabList)

直接运行程序会出现报错的情况,UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xae in position 199: illegal multib,这时候发现报错位置是:wordList = textParse(open('./email/ham/%d.txt' % i, 'r').read()),是文件读取有问题,这个时候打开相应的文件,发现是这个问号的问题,先删除再输入即可解决错误。
在这里插入图片描述
这样就得到了词汇表,结果如下:

>>>
['email', 'assistance', 'treat', 'forward', 'codeine', 'edit', 'wallets', 'looking', '396', 'this', 'had', 'storedetailview_98', 'vicodin', 'message', 'thirumalai', 'methods', 'modelling', 'canadian', 'mandelbrot', 'days', 'link', 'can', 'status', 'them', 'private', 'tesla', 'earn', 'transformed', '156', '588', 'may', 'business', 'over', 'style', 'province', 'help', 'jewerly', 'spaying', 'focusing', 'the', 'was', '325', 'giants', 'working', 'who', 'huge', 'quality', 'logged', 'release', 'analgesic', 'rude', 'financial', 'will', 'acrobat', 'http', 'tour', 'way', 'came', 'attaching', '130', '0nline', 'pictures', 'sorry', 'food', 'another', 'need', 'dusty', '129', 'come', 'louis', 'office', 'upload', 'drugs', 'works', 'derivatives', 'talked', 'troy', '492', 'supporting', 'ideas', 'specifications', 'two', 'sent', 'copy', 'has', '2007', 'magazine', 'thailand', 'changing', 'his', 'mba', 'work', 'once', 'held', 'full', 'perhaps', 'guaranteeed', 'least', 'approved', '291', 'cs5', 'computer', 'percocet', 'customized', '195', 'said', 'hope', 'group', 'ems', 'roofer', 'members', 'night', 'ups', 'bike', 'add', 'fractal', 'item', 'cats', 'died', 'automatically', 'window', '100', 'photoshop', 'either', 'couple', 'jar', 'finder', 'trusted', 'improving', '50092', 'mail', 'party', 'low', 'using', 'done', 'hotels', 'moderate', 'incoming', 'inches', 'brained', 'effective', 'jocelyn', 'google', 'today', 'things', 'regards', 'runs', '120', 'success', 'download', 'cheap', 'about', 'items', 'please', 'file', 'going', 'pick', 'lunch', 'capabilities', 'focus', 'design', 'than', 'source', 'model', 'tabs', 'permanantly', 'prices', 'pages', 'viagranoprescription', 'ofejacu1ate', 'finance', 'mom', 'nvidia', 'ready', 'drunk', 'buy', 'mathematics', 'would', 'told', 'specifically', 'reputable', 'announcement', 'went', 'ferguson', 'from', 'fans', 'page', 'natural', 'borders', 'update', 'parallel', 'because', '219', 'credit', 'inform', 'let', 'superb', 'development', '90563', 'strategy', 'home', 'new', 'discreet', 'com', 'take', '86152', 'each', 'care', 'creative', 'while', 'volume', 'dozen', 'pricing', 'running', 'yeah', 'severepain', 'enjoy', 'placed', 'coast', 'endorsed', 'train', 'peter', 'vivek', 'cca', 'gain', 'car', '30mg', 'groups', 'notification', 'starting', 'speedpost', 'you', 'when', 'wrote', 'warranty', 'you抮e', 'articles', 'phentermin', 'cat', 'yay', 'julius', 'door', '292', 'much', 'could', 'sliding', 'located', 'glimpse', 'class', 'genuine', 'great', 'want', 'place', 'benoit', 'team', 'website', 'jpgs', 'length', '570', 'wholesale', 'might', 'visa', 'tokyo', 'cost', 'with', 'issues', 'now', 'grow', 'thickness', 'out', 'holiday', 'have', 'february', 'chapter', 'latest', 'possible', 'school', 'are', 'withoutprescription', 'files', 'arvind', 'hotel', 'everything', 'safest', 'freeviagra', 'jquery', 'of_penisen1argement', 'yesterday', 'insights', 'commented', 'zolpidem', 'service', 'increase', 'some', 'code', 'town', 'aged', 'eugene', 'quantitative', 'expo', 'narcotic', 'how', 'income', 'designed', 'fedex', 'wednesday', '750', '119', '625', 'delivery', 'professional', 'such', 'your', 'one', 'see', 'turd', 'thread', 'butt', 'those', '2010', 'phone', 'check', 'intenseorgasns', 'heard', 'selected', 'inconvenience', 'betterejacu1ation', 'jose', 'behind', 'learn', 'stepp', '203', 'don抰', 'owner', 'online', 'access', 'adobe', 'cannot', 'not', 'grounds', 'requested', 'opportunity', 'instead', 'biggerpenis', 'amazing', 'note', 'museum', 'far', '138', 'most', 'extended', 'plane', 'scifinance', 'mailing', '366', 'ultimate', 'cheers', 'gucci', 'recieve', 'father', 'wilmott', 'pill', 'high', 'but', 'pro', '50mg', '322', 'jqplot', 'been', 'creation', 'information', 'find', 'experience', 'use', 'game', '100mg', 'lined', 'keep', 'top', 'individual', 'site', 'haloney', 'kerry', 'faster', 'there', 'approach', 'bad', 'herbal', 'thing', 'microsoft', 'plus', 'safe', '562', 'discussions', 'sites', 'major', 'generation', 'doors', 'plugin', '25mg', 'address', 'prepared', 'shape', 'vuitton', 'competitive', 'where', 'ones', 'since', 'tent', 'used', 'hangzhou', 'price', 'these', 'meet', '225', 'significantly', 'methylmorphine', 'answer', 'softwares', 'girl', 'october', 'year', 'cards', 'www', 'example', 'core', 'well', 'includes', 'here', 'invitation', 'life', 'support', 'also', 'share', 'series', 'opioid', 'horn', 'assigning', 'fda', '100m', 'discount', 'more', 'strategic', '14th', 'featured', 'chance', 'and', 'tickets', 'fundamental', '2011', 'inspired', 'writing', 'location', 'wasn', 'sky', '66343', 'watches', 'saw', 'then', 'like', 'doing', 'good', 'easily', 'bathroom', 'money', 'dhl', 'mandarin', 'hommies', 'features', 'fbi', 'needed', 'cartier', 'serial', 'call', 'amex', 'welcome', 'carlo', 'accepted', 'doctor', 'hold', '15mg', 'network', 'mandatory', 'close', 'winter', 'right', 'ambiem', 'forum', 'reply', 'rent', 'pretty', '200', 'often', 'same', 'create', 'rain', 'riding', 'bettererections', 'millions', 'ma1eenhancement', 'watson', 'china', 'bin', '385', 'stuff', 'certified', 'guy', 'bargains', 'supplement', 'gas', 'fermi', 'inside', 'contact', 'web', 'cold', 'suggest', 'connection', 'time', 'hello', '199', 'having', 'level', 'worldwide', 'signed', 'noprescription', 'tool', '10mg', 'sounds', 'wilson', 'number', 'germany', 'station', 'gains', 'back', 'important', 'python', 'past', 'mathematician', 'femaleviagra', 'being', 'blue', 'hermes', 'lists', 'pain', '180', 'retirement', 'any', 'computing', 'that', 'docs', 'knocking', 'job', 'sure', 'don', 'generates', 'fine', 'definitely', 'book', 'moderately', 'net', 'via', 'color', 'got', 'thousand', 'programming', 'favorite', 'reservation', 'nature', 'survive', 'gpu', 'prototype', 'experts', 'does', 'through', 'based', 'think', 'titles', 'required', 'per', 'fast', 'off', 'jay', 'uses', 'comment', 'products', 'launch', 'bags', 'oris', 'received', 'expertise', '513', 'doggy', 'scenic', 'john', 'monte', 'just', 'enabled', 'control', 'both', 'harderecetions', 'what', 'view', 'away', 'ordercializviagra', 'watchesstore', 'oem', 'dior', 'hours', 'reliever', 'storage', 'brandviagra', 'shipping', 'sophisticated', 'foaming', 'brands', 'all', 'interesting', '5mg', 'buyviagra', 'cuda', '174623', 'thank', 'ryan', 'arolexbvlgari', 'know', 'must', '430', 'save', 'differ', 'automatic', 'program', 'hydrocodone', 'hamm', 'too', 'rock', 'longer', 'store', 'try', 'yourpenis', 'order', 'pharmacy', 'risk', 'courier', 'free', 'art', 'incredib1e', 'chinese', 'accept', 'they', 'changes', 'pills', 'explosive', 'pavilion', 'pls', '1924', 'zach', 'moneyback', 'express', 'encourage', 'functionalities', 'others', 'shipment', 'only', 'knew', 'decision', 'follow', 'get', 'tiffany', 'leaves', 'below', 'thanks', 'for', 'should', 'york', 'trip', 'naturalpenisenhancement', 'exhibit', 'whybrew', 'famous', 'concise', 'questions', 'advocate', 'name', 'day', 'brand', 'management', 'listed', 'proven', 'made', 'windows', '300x', 'linkedin', 'enough', 'thought']
2) 测试算法:使用朴素贝叶斯进行交叉验证

根据词汇表就可以将每个文本向量化。首先将数据集分为训练集和测试集,使用 交叉验证 的方式测试朴素贝叶斯分类器的准确性。编写代码如下:

import numpy as np
import random
import re

'''
Parameters:
    dataSet - 整理的样本数据集
Returns:
    vocabSet - 返回不重复的词条列表,也就是词汇表
'''
# 函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
def createVocabList(dataSet):
    vocabSet = set([])                      #创建一个空的不重复列表
    for document in dataSet:               
        vocabSet = vocabSet | set(document) #取并集
    return list(vocabSet)

'''
Parameters:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词集模型
'''
# 函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
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:
    vocabList - createVocabList返回的列表
    inputSet - 切分的词条列表
Returns:
    returnVec - 文档向量,词袋模型
'''
# 函数说明:根据vocabList词汇表,构建词袋模型
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)                          #创建一个其中所含元素都为0的向量
    for word in inputSet:                                   #遍历每个词条
        if word in vocabList:                               #如果词条存在于词汇表中,则计数加一
            returnVec[vocabList.index(word)] += 1
    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.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

'''
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#返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

# 函数说明:接收一个大字符串并将其解析为字符串列表
def textParse(bigString):                                       #将字符串转换为字符列表
    listOfTokens = re.split(r'\W*', bigString)                  #将特殊符号作为切分标志进行字符串切分,即非字母、非数字
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]#除了单个字母,例如大写的I,其它单词变成小写

# 函数说明:测试朴素贝叶斯分类器
def spamTest():
    docList = []; classList = []; fullText = []
    for i in range(1, 26):                                             #遍历25个txt文件
        wordList = textParse(open('email/spam/%d.txt' % i, 'r').read())#读取每个垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(1)                                            #标记垃圾邮件,1表示垃圾文件
        wordList = textParse(open('email/ham/%d.txt' % i, 'r').read()) #读取每个非垃圾邮件,并字符串转换成字符串列表
        docList.append(wordList)
        fullText.append(wordList)
        classList.append(0)                    		#标记非垃圾邮件,1表示垃圾文件   
    vocabList = createVocabList(docList)       		#创建词汇表,不重复
    trainingSet = list(range(50)); 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:                                          #遍历训练集
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))     #将生成的词集模型添加到训练矩阵中
        trainClasses.append(classList[docIndex])                          #将类别添加到训练集类别标签系向量中
    p0V, p1V, pSpam = trainNB0(np.array(trainMat), np.array(trainClasses))#训练朴素贝叶斯模型
    errorCount = 0                                                        #错误分类计数
    for docIndex in testSet:                                              #遍历测试集
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])         #测试集的词集模型
        if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:#如果分类错误
            errorCount += 1                                               #错误计数加1
            print("分类错误的测试集:",docList[docIndex])
    print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))


if __name__ == '__main__':
    spamTest()
>>> 
分类错误的测试集: ['scifinance', 'now', 'automatically', 'generates', 'gpu', 'enabled', 'pricing', 'risk', 'model', 'source', 'code', 'that', 'runs', '300x', 'faster', 'than', 'serial', 'code', 'using', 'new', 'nvidia', 'fermi', 'class', 'tesla', 'series', 'gpu', 'scifinance', 'derivatives', 'pricing', 'and', 'risk', 'model', 'development', 'tool', 'that', 'automatically', 'generates', 'and', 'gpu', 'enabled', 'source', 'code', 'from', 'concise', 'high', 'level', 'model', 'specifications', 'parallel', 'computing', 'cuda', 'programming', 'expertise', 'required', 'scifinance', 'automatic', 'gpu', 'enabled', 'monte', 'carlo', 'pricing', 'model', 'source', 'code', 'generation', 'capabilities', 'have', 'been', 'significantly', 'extended', 'the', 'latest', 'release', 'this', 'includes']
分类错误的测试集: ['home', 'based', 'business', 'opportunity', 'knocking', 'your', 'door', 'don抰', 'rude', 'and', 'let', 'this', 'chance', 'you', 'can', 'earn', 'great', 'income', 'and', 'find', 'your', 'financial', 'life', 'transformed', 'learn', 'more', 'here', 'your', 'success', 'work', 'from', 'home', 'finder', 'experts']
分类错误的测试集: ['benoit', 'mandelbrot', '1924', '2010', 'benoit', 'mandelbrot', '1924', '2010', 'wilmott', 'team', 'benoit', 'mandelbrot', 'the', 'mathematician', 'the', 'father', 'fractal', 'mathematics', 'and', 'advocate', 'more', 'sophisticated', 'modelling', 'quantitative', 'finance', 'died', '14th', 'october', '2010', 'aged', 'wilmott', 'magazine', 'has', 'often', 'featured', 'mandelbrot', 'his', 'ideas', 'and', 'the', 'work', 'others', 'inspired', 'his', 'fundamental', 'insights', 'you', 'must', 'logged', 'view', 'these', 'articles', 'from', 'past', 'issues', 'wilmott', 'magazine']
错误率:30.00%

函数spamTest()会输出在10封随机选择的电子邮件上的分类错误概率。所以存在误判的情况,将垃圾邮件误判为正常邮件要比将正常邮件归为垃圾邮件好。为了避免错误,有多种方式可以用来修正分类器,这些内容会在后续文章中进行讨论。

9、Sklearn构建朴素贝叶斯分类器用于新浪新闻分类

1) 中文语句切分

前面我们说,英文的语句可以通过非字母和非数字进行切分,但是汉语句子呢,该如何进行切分呢?

答案是可以直接使用第三方分词组件,即jieba。

新闻分类数据集下载地址:https://github.com/Jack-Cherish/Machine-Learning/tree/master/Naive%20Bayes/SogouC

数据集已经做好分类,分文件夹保存,分类结果如下:
在这里插入图片描述
切分中文语句,编写如下代码:

# -*- coding: UTF-8 -*-
import os
import jieba

def TextProcessing(folder_path):
    folder_list = os.listdir(folder_path)                  #查看folder_path下的文件
    data_list = []                                         #训练集
    class_list = []

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)#根据子文件夹,生成新的路径
        files = os.listdir(new_folder_path)                #存放子文件夹下的txt文件的列表

        j = 1
        #遍历每个txt文件
        for file in files:
            if j > 100:                                    #每类txt样本数最多100个
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:#打开txt文件
                raw = f.read()

            word_cut = jieba.cut(raw, cut_all = False)    #精简模式,返回一个可迭代的generator
            word_list = list(word_cut)                    #generator转换为list

            data_list.append(word_list)
            class_list.append(folder)
            j += 1
        print(data_list)
        print(class_list)


if __name__ == '__main__':
    #文本预处理
    folder_path = './SogouC/Sample'                       #训练集存放地址
    TextProcessing(folder_path)

代码运行结果如下,可以看到已经顺利将每个文本进行切分,并进行了类别标记。
在这里插入图片描述

2) 文本特征选择

将所有文本分成训练集和测试集,并对训练集中的所有单词进行词频统计,并按降序排序。也就是将出现次数多的词语在前,出现次数少的词语在后进行排序。编写代码如下:

# -*- coding: UTF-8 -*-
import os
import random
import jieba

"""
函数说明:中文文本处理

Parameters:
    folder_path - 文本存放的路径
    test_size - 测试集占比,默认占所有数据集的百分之20
Returns:
    all_words_list - 按词频降序排序的训练集列表
    train_data_list - 训练集列表
    test_data_list - 测试集列表
    train_class_list - 训练集标签列表
    test_class_list - 测试集标签列表
"""
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                          #查看folder_path下的文件
    data_list = []                                                 #数据集数据
    class_list = []                                                #数据集类别

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根据子文件夹,生成新的路径
        files = os.listdir(new_folder_path)                        #存放子文件夹下的txt文件的列表

        j = 1
        #遍历每个txt文件
        for file in files:
            if j > 100:                                            #每类txt样本数最多100个
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打开txt文件
                raw = f.read()

            word_cut = jieba.cut(raw, cut_all = False)             #精简模式,返回一个可迭代的generator
            word_list = list(word_cut)                             #generator转换为list

            data_list.append(word_list)                            #添加数据集数据
            class_list.append(folder)                              #添加数据集类别
            j += 1

    data_class_list = list(zip(data_list, class_list))             #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                                #将data_class_list乱序
    index = int(len(data_class_list) * test_size) + 1              #训练集和测试集切分的索引值
    train_list = data_class_list[index:]                           #训练集
    test_list = data_class_list[:index]                            #测试集
    train_data_list, train_class_list = zip(*train_list)           #训练集解压缩
    test_data_list, test_class_list = zip(*test_list)              #测试集解压缩

    all_words_dict = {}                                            #统计训练集词频
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1

    #根据键的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)   #解压缩
    all_words_list = list(all_words_list)                         #转换成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list

if __name__ == '__main__':
    #文本预处理
    folder_path = './SogouC/Sample'                              #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)
    print(all_words_list)

在这里插入图片描述
all_words_list就是将所有训练集的切分结果通过词频降序排列构成的单词合集。观察打印结果,发现包含了很多标点符号,很显然,这些标点符号是不能作为新闻分类的特征的。为了降低这些高频的符号对分类结果的影响,需要放弃这些符号,除了这些,还有”在”,”了”这样对新闻分类无关痛痒的词,并且还有一些数字,数字显然也不能作为分类新闻的特征。

所以要消除它们对分类结果的影响,可以定制一个规则:首先去掉高频词,至于去掉多少个高频词,可以通过观察去掉高频词个数和最终检测准确率的关系来确定。除此之外,去除数字,不把数字作为分类特征。同时,去除一些特定的词语,比如:”的”,”一”,”在”,”不”,”当然”,”怎么”这类的对新闻分类无影响的介词、代词、连词。怎么去除这些词呢?可以使用整理好的stopwords_cn.txt文本。

下载地址:https://github.com/Jack-Cherish/Machine-Learning/blob/master/Naive%20Bayes/stopwords_cn.txt

文件内容如下:
在这里插入图片描述
所以可以根据文档去除单词。我们先去除前100个高频词汇,然后编写代码如下:

# -*- coding: UTF-8 -*-
import os
import random
import jieba

"""
函数说明:中文文本处理

Parameters:
    folder_path - 文本存放的路径
    test_size - 测试集占比,默认占所有数据集的百分之20
Returns:
    all_words_list - 按词频降序排序的训练集列表
    train_data_list - 训练集列表
    test_data_list - 测试集列表
    train_class_list - 训练集标签列表
    test_class_list - 测试集标签列表
"""
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                          #查看folder_path下的文件
    data_list = []                                                 #数据集数据
    class_list = []                                                #数据集类别

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根据子文件夹,生成新的路径
        files = os.listdir(new_folder_path)                        #存放子文件夹下的txt文件的列表

        j = 1
        #遍历每个txt文件
        for file in files:
            if j > 100:                                            #每类txt样本数最多100个
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打开txt文件
                raw = f.read()

            word_cut = jieba.cut(raw, cut_all = False)            #精简模式,返回一个可迭代的generator
            word_list = list(word_cut)                            #generator转换为list

            data_list.append(word_list)                           #添加数据集数据
            class_list.append(folder)                             #添加数据集类别
            j += 1

    data_class_list = list(zip(data_list, class_list))            #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                               #将data_class_list乱序
    index = int(len(data_class_list) * test_size) + 1             #训练集和测试集切分的索引值
    train_list = data_class_list[index:]                          #训练集
    test_list = data_class_list[:index]                           #测试集
    train_data_list, train_class_list = zip(*train_list)          #训练集解压缩
    test_data_list, test_class_list = zip(*test_list)             #测试集解压缩

    all_words_dict = {}                                           #统计训练集词频
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1

    #根据键的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)  #解压缩
    all_words_list = list(all_words_list)                        #转换成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list

"""
函数说明:读取文件里的内容,并去重

Parameters:
    words_file - 文件路径
Returns:
    words_set - 读取的内容的set集合
"""
def MakeWordsSet(words_file):
    words_set = set()                                            #创建set集合
    with open(words_file, 'r', encoding = 'utf-8') as f:         #打开文件
        for line in f.readlines():                               #一行一行读取
            word = line.strip()                                  #去回车
            if len(word) > 0:                                    #有文本,则添加到words_set中
                words_set.add(word)                               
    return words_set                                             #返回处理结果

"""
函数说明:文本特征选取

Parameters:
    all_words_list - 训练集所有文本列表
    deleteN - 删除词频最高的deleteN个词
    stopwords_set - 指定的结束语
Returns:
    feature_words - 特征集
"""
def words_dict(all_words_list, deleteN, stopwords_set = set()):
    feature_words = []                                      #特征列表
    n = 1
    for t in range(deleteN, len(all_words_list), 1):
        if n > 1000:                                        #feature_words的维度为1000
            break                               
        #如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词就可以作为特征词
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(all_words_list[t]) < 5:
            feature_words.append(all_words_list[t])
        n += 1
    return feature_words

if __name__ == '__main__':
    #文本预处理
    folder_path = './SogouC/Sample'                #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)

    #生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)

    feature_words = words_dict(all_words_list, 100, stopwords_set)
    print(feature_words)

运行结果如下:
在这里插入图片描述
可以看到,已经滤除了那些没有用的词组,这个feature_words就是最终选出的用于新闻分类的特征。随后就可以根据feature_words,将文本向量化,然后用于训练朴素贝叶斯分类器。

通过观察取不同的去掉前deleteN个高频词的个数与最终检测准确率的关系,确定deleteN的取值:

# -*- coding: UTF-8 -*-
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt
import os
import random
import jieba

"""
函数说明:中文文本处理

Parameters:
    folder_path - 文本存放的路径
    test_size - 测试集占比,默认占所有数据集的百分之20
Returns:
    all_words_list - 按词频降序排序的训练集列表
    train_data_list - 训练集列表
    test_data_list - 测试集列表
    train_class_list - 训练集标签列表
    test_class_list - 测试集标签列表
"""
def TextProcessing(folder_path, test_size = 0.2):
    folder_list = os.listdir(folder_path)                          #查看folder_path下的文件
    data_list = []                                                 #数据集数据
    class_list = []                                                #数据集类别

    #遍历每个子文件夹
    for folder in folder_list:
        new_folder_path = os.path.join(folder_path, folder)        #根据子文件夹,生成新的路径
        files = os.listdir(new_folder_path)                        #存放子文件夹下的txt文件的列表

        j = 1
        #遍历每个txt文件
        for file in files:
            if j > 100:                                            #每类txt样本数最多100个
                break
            with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f:    #打开txt文件
                raw = f.read()

            word_cut = jieba.cut(raw, cut_all = False)            #精简模式,返回一个可迭代的generator
            word_list = list(word_cut)                            #generator转换为list

            data_list.append(word_list)                           #添加数据集数据
            class_list.append(folder)                             #添加数据集类别
            j += 1

    data_class_list = list(zip(data_list, class_list))            #zip压缩合并,将数据与标签对应压缩
    random.shuffle(data_class_list)                               #将data_class_list乱序
    index = int(len(data_class_list) * test_size) + 1             #训练集和测试集切分的索引值
    train_list = data_class_list[index:]                          #训练集
    test_list = data_class_list[:index]                           #测试集
    train_data_list, train_class_list = zip(*train_list)          #训练集解压缩
    test_data_list, test_class_list = zip(*test_list)             #测试集解压缩

    all_words_dict = {}                                           #统计训练集词频
    for word_list in train_data_list:
        for word in word_list:
            if word in all_words_dict.keys():
                all_words_dict[word] += 1
            else:
                all_words_dict[word] = 1

    #根据键的值倒序排序
    all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True)
    all_words_list, all_words_nums = zip(*all_words_tuple_list)  #解压缩
    all_words_list = list(all_words_list)                        #转换成列表
    return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list

"""
函数说明:读取文件里的内容,并去重

Parameters:
    words_file - 文件路径
Returns:
    words_set - 读取的内容的set集合
"""
def MakeWordsSet(words_file):
    words_set = set()                                           #创建set集合
    with open(words_file, 'r', encoding = 'utf-8') as f:        #打开文件
        for line in f.readlines():                              #一行一行读取
            word = line.strip()                                 #去回车
            if len(word) > 0:                                   #有文本,则添加到words_set中
                words_set.add(word)                               
    return words_set                                            #返回处理结果

"""
函数说明:根据feature_words将文本向量化

Parameters:
    train_data_list - 训练集
    test_data_list - 测试集
    feature_words - 特征集
Returns:
    train_feature_list - 训练集向量化列表
    test_feature_list - 测试集向量化列表
"""
def TextFeatures(train_data_list, test_data_list, feature_words):
    def text_features(text, feature_words):                     #出现在特征集中,则置1                                               
        text_words = set(text)
        features = [1 if word in text_words else 0 for word in feature_words]
        return features
    train_feature_list = [text_features(text, feature_words) for text in train_data_list]
    test_feature_list = [text_features(text, feature_words) for text in test_data_list]
    return train_feature_list, test_feature_list                #返回结果


"""
函数说明:文本特征选取

Parameters:
    all_words_list - 训练集所有文本列表
    deleteN - 删除词频最高的deleteN个词
    stopwords_set - 指定的结束语
Returns:
    feature_words - 特征集
"""
def words_dict(all_words_list, deleteN, stopwords_set = set()):
    feature_words = []                            #特征列表
    n = 1
    for t in range(deleteN, len(all_words_list), 1):
        if n > 1000:                             #feature_words的维度为1000
            break                               
        #如果这个词不是数字,并且不是指定的结束语,并且单词长度大于1小于5,那么这个词就可以作为特征词
        if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(all_words_list[t]) < 5:
            feature_words.append(all_words_list[t])
        n += 1
    return feature_words

"""
函数说明:新闻分类器

Parameters:
    train_feature_list - 训练集向量化的特征文本
    test_feature_list - 测试集向量化的特征文本
    train_class_list - 训练集分类标签
    test_class_list - 测试集分类标签
Returns:
    test_accuracy - 分类器精度
"""
def TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list):
    classifier = MultinomialNB().fit(train_feature_list, train_class_list)
    test_accuracy = classifier.score(test_feature_list, test_class_list)
    return test_accuracy

if __name__ == '__main__':
    #文本预处理
    folder_path = './SogouC/Sample'                #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)

    # 生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)


    test_accuracy_list = []
    deleteNs = range(0, 1000, 20)                #0 20 40 60 ... 980
    for deleteN in deleteNs:
        feature_words = words_dict(all_words_list, deleteN, stopwords_set)
        train_feature_list, test_feature_list = TextFeatures(train_data_list, test_data_list, feature_words)
        test_accuracy = TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list)
        test_accuracy_list.append(test_accuracy)

    plt.figure()
    plt.plot(deleteNs, test_accuracy_list)
    plt.title('Relationship of deleteNs and test_accuracy')
    plt.xlabel('deleteNs')
    plt.ylabel('test_accuracy')
    plt.show()

运行结果如下:
在这里插入图片描述
绘制出deleteNs和test_accuracy的关系,这样就可以大致确定去掉前多少的高频词汇了。每次运行程序,绘制的图形可能不尽相同,可以通过多次测试,来决定这个deleteN的取值,然后确定这个参数,这样就可以顺利构建出用于新闻分类的朴素贝叶斯分类器了。

我测试感觉450还不错,最差的分类准确率也可以达到百分之50以上。将if __name__ == '__main__'下的代码修改如下:

if __name__ == '__main__':
    #文本预处理
    folder_path = './SogouC/Sample'                #训练集存放地址
    all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2)

    # 生成stopwords_set
    stopwords_file = './stopwords_cn.txt'
    stopwords_set = MakeWordsSet(stopwords_file)

    test_accuracy_list = []
    feature_words = words_dict(all_words_list, 450, stopwords_set)
    train_feature_list, test_feature_list = TextFeatures(train_data_list, test_data_list, feature_words)
    test_accuracy = TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list)
    test_accuracy_list.append(test_accuracy)
    ave = lambda c: sum(c) / len(c)

运行结果:
在这里插入图片描述

10、sklearn.naive_bayes.MultinomialNB

sklearn.naive_bayes.MultinomialNB是一个很好的模型,决策树算法就是通过它实现的,详细的看这个博客——sklearn.naive_bayes.MultinomialNB()函数解析

11、总结

对于分类而言,使用 概率 有时要比使用 硬规则 更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。可以通过 特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。这就是之所以称为 朴素贝叶斯 的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。

利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出 就是其中一个问题,它可以通过对概率取对数来解决。还有其他一些方面的改进,比如说移除停用词,当然也可以花大量时间对切分器进行优化。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值