基于概率论的分类方法:朴素贝叶斯及其Python实现

本文会给出一些使用概率论进行分类的方法。首先从一个最简单的概率分类器开始,然后给出一些假设来学习朴素贝叶斯分类器。我们称之为“朴素”,是因为整个形式化过程只做最原始、最简单的假设。不必担心,你会详细了解到这些假设。我们将充分利用Python的文本处理能力将文档切分成词向量,然后利用词向量对文档进行分类。我们还将构建另一个分类器,观察其在真实的垃圾邮件数据集中的过滤效果,必要时还会回顾一下条件概率。最后,我们将介绍如何从个人发布的大量广告中学习分类器,并将学习结果转换成人类可理解的信息。

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

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

朴素贝叶斯是贝叶斯决策理论的一部分,所以讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。假设现在我们有一个数据集,它由两类数据组成,数据分布如图1所示。

图1 两个参数已知的概率分布,参数决定了分布的形状

我们现在用p1(x, y)表示数据点(x, y)属于类别1(图中用圆点表示的类别)的概率,用p2(x, y)表示数据点(x, y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x, y),可以用下面的规则来判断它的类别:

❏ 如果p1(x, y) > p2(x, y),那么类别为1。

❏ 如果p2(x, y) > p1(x, y),那么类别为2。

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

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

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

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

使用条件概率来分类

前面提到贝叶斯决策理论要求计算两个概率p1(x, y)和p2(x, y):

❏ 如果p1(x, y) > p2(x, y),那么属于类别1;

❏ 如果p2(x, y) > p1(x, y),那么属于类别2。

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

使用这些定义,可以定义贝叶斯分类准则为:

❏ 如果P(c1|x, y) > P(c2|x, y),那么属于类别c1;

❏ 如果P(c1|x, y) < P(c2|x, y),那么属于类别c2。

使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。后面就会给出利用贝叶斯准则来计算概率并对数据进行分类的代码。现在介绍了一些概率理论,你也了解了基于这些理论构建分类器的方法,接下来就要将它们付诸实践。

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

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

使用每个词作为特征并观察它们是否出现,这样得到的特征数目会有多少呢?针对的是哪一种人类语言呢?当然不止一种语言。据估计,仅在英语中,单词的总数就有500000[http://hypertextbook.com/facts/2001/JohnnyLing.shtml, 2010年10月20日检索结果。]之多。为了能进行英文阅读,估计需要掌握数千单词。

朴素贝叶斯的一般过程

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

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

如果特征之间相互独立,那么样本数就可以从N1000减少到1000×N。所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素(naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征同等重要[注:朴素贝叶斯分类器通常有两种实现方式:一种基于贝努利模型实现,一种基于多项式模型实现。这里采用前一种实现方式。该实现方式中并不考虑词在文档中出现的次数,只考虑出不出现,因此在这个意义上相当于假设词是等权重的。]。其实这个假设也有问题。如果要判断留言板的留言是否得当,那么可能不需要看完所有的1000个单词,而只需要看10~20个特征就足以做出判断了。尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。

使用Python进行文本分类

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

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

接下来首先给出将文本转换为数字向量的过程,然后介绍如何基于这些向量来计算条件概率,并在此基础上构建分类器,最后还要介绍一些利用Python实现朴素贝叶斯过程中需要考虑的问题。

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

我们将把文本看成单词向量或者词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。接下来我们正式开始。

#coding:utf-8
from numpy import *

'''
函数loadDataSet()创建了一些实验样本。该函数返回的第一个变量是进行词条切分后的文档集合,这些文档来自斑点犬爱好者留言板。这些留言文本被切分成一系列的词条集合,标点符号从文本中去掉。loadDataSet( )函数返回的第二个变量是一个类别标签的集合。这里有两类,侮辱性和非侮辱性。这些文本的类别由人工标注,这些标注信息用于训练程序以便自动检测侮辱性留言。
'''
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 is abusive, 0 not
    return postingList,classVec
'''
函数createVocabList()会创建一个包含在所有文档中出现的不重复词的列表,为此使用了Python的set数据类型。将词条列表输给set构造函数,set就会返回一个不重复词表。首先,创建一个空集合1,然后将每篇文档返回的新词集合添加到该集合中2。操作符|用于求两个集合的并集,这也是一个按位或(OR)操作符。在数学符号表示上,按位或操作与集合求并操作使用相同记号。
'''
def createVocabList(dataSet):
    vocabSet = set([])  #create empty set
    for document in dataSet:
        vocabSet = vocabSet | set(document) #union of the two sets
    return list(vocabSet)
'''
获得词汇表后,便可以使用函数setOfWords2Vec(),该函数的输入参数为词汇表及某个文档,输出的是文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现。函数首先创建一个和词汇表等长的向量,并将其元素都设置为0。接着,遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1。一切都顺利的话,就不需要检查某个词是否还在vocabList中,后边可能会用到这一操作。
'''
'''
	其实就是input中的元素,如果在vocabList中存在,那么vocabList对应的位置的returnVec的值为1,反之为0
'''
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: %s is not in my Vocabulary!" % word)
    return returnVec
# 看一下函数setOfWords2Vec()的运行效果:
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
print(myVocabList)
print(listOPosts[0])
t = setOfWords2Vec(myVocabList,listOPosts[0])
print(t)
['love', 'my', 'how', 'quit', 'is', 'flea', 'problems', 'stop', 'posting', 'help', 'not', 'so', 'cute', 'mr', 'I', 'dog', 'food', 'maybe', 'him', 'take', 'buying', 'to', 'garbage', 'stupid', 'worthless', 'licks', 'steak', 'dalmation', 'park', 'please', 'ate', 'has']
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
[0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1]

未经允许不得转载!基于概率论的分类方法:朴素贝叶斯及其Python实现