机器学习实战——4.基于概率论的分类方法:朴素贝叶斯

k-近邻算法和决策树要求分类器做出艰难决策,给出“该数据实例属于哪一类”这类问题的明确答案。不过,分类器有时会产生错误结果,这是可以要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。
朴素贝叶斯分类器是使用概率论进行分类的分类器,之所以称为“朴素”,是因为整个形式化过程只做最原始、最简单的假设。
4.1 基于贝叶斯决策理论的分类方法
朴素贝叶斯:
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据。
朴素贝叶斯是贝叶斯决策理论的一部分,首先快速了解一下贝叶斯决策理论。
假设现在有一个数据集,它由两类数据组成,分局分布如下(两个参数已知的概率分布,参数决定了分布的形状):
在这里插入图片描述
假设已经找到了描述图中两类数据的统计参数。现在用p1(x,y)表示数据点(x,y)属于类别1(图中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以用下面的规则来判断它的类别:
如果p1(x,y)>p2(x,y),那么判别为1。
如果p1(x,y)<p2(x,y),那么判别为2。
也就是说,会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。上图中的整个数据使用6个浮点数来表示,并且计算类别概率的python代码只有两行,下面的方法都可以用来对该数据点进行分类:
(1)使用kNN,进行1000次距离计算;
(2)使用决策树,分别沿x轴、y轴划分数据;
(3)计算数据点属于每个类别的概率,并进行比较。
使用决策树不会非常成功;而和简单的概率计算相比,kNN的计算量太大。因此,对于上述问题,最佳选择是使用概率比较方法。
(贝叶斯?贝叶斯概率以18世纪的一位神学家托马斯·贝叶斯(thomas Bayes)的名字命名。贝叶斯概率引入先验知识和逻辑推理来处理不确定命题。另一种概率解释称为频数概率(frequency probability),它只从数据本身获得结论,并不考虑逻辑推理及先验知识。)
4.2 条件概率
事件A发生的情况下,事件B发生的概率:
在这里插入图片描述
贝叶斯准则:告诉我们如何交换条件概率中的条件与结果,即如果已知p(x|c),求p(c|x),计算方法为:
在这里插入图片描述
4.3 使用条件概率来分类
4.1节提到贝叶斯决策理论要求计算两个概率p1(x,y)和p2(x,y):
如果p1(x,y)>p2(x,y),那么属于类别1;
如果p1(x,y)<p2(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。
使用贝叶斯准则,可以通过已知的三个概率值来计算未知的概率值。
4.4 使用朴素贝叶斯进行文档分类
机器学习的一个重要应用就是文档的自动分类。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但同样可以对新闻报道、用户留言、政府公文等其它任意类型的文本进行分类。可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。朴素贝叶斯是贝叶斯分类器的一个扩展,是用于文档分类的常用算法。
使用每个词作为特征并观察它们是否出现,这样得到的特征数目会有多少呢?针对的是哪一种人类语言呢?,当然不止一种语言。据估计,仅在英语中,单词的总数就有500000之多。为了能进行英文阅读,估计需要掌握数千单词。
朴素贝叶斯的一般过程
(1)收集数据:可以使用任何方式,本章使用RSS源。
(2)准备数据:需要数值型或者布尔型数据。
(3)分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
(4)训练算法:计算不同的独立特征的条件概率。
(5)测试算法:计算错误率。
(6)使用算法:一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。
假设词汇表中有1000个单词。要得到好的概率分布,就需要足够的数据样本,假定样本数为N。约会网站示例中有1000个实例,手写识别示例中每个数字有200个样本,而决策树示例中有24个样本。其中,24个样本有点少,200个样本好一些,而1000个样本就非常好了。约会网站例子中有三个特征。由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要N10个样本,对于包含1000个特征的词汇表将需要N1000个样本。可以看出,所需要的样本数会随着特征数目增大而迅速增长。
如果特征之间相互独立,那么样本数就可以从N1000减少到1000xN。所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子讲,假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,这种假设并不正确,bacon尝尝出现在delicious附近,而很少出现在unhealthy附近,这个假设正是朴素贝叶斯分类器中朴素(naive)一词的含义。朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。其实这个假设也有问题。如果要判断留言板的留言是否得当,那么可能不需要看完所有的1000个单词,而只需要看10~20个特征就足以做出判断了。尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。
4.5 使用python进行文本分类
要从文本中获得特征,需要先拆分文本,特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条想象为单词,也可以使用非单词词条,如URL、IP地址或者任意其它字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。
以在线社区的留言板为例。为了不影响社区的发展,要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或侮辱性的语言,那么就将该留言标识为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。
接下来首先给出将文本转换为数字向量的过程,然后介绍如何基于这些向量来计算条件概率,并在此基础上构建分类器,最后介绍一些利用python实现朴素贝叶斯过程中需要考虑的问题。
4.5.1 准备数据:从文本中构建词向量
将文本看成单词向量或词条向量,也就是说将句子转换为向量。考虑出现在所有文档中的所有单词,再决定将哪些词纳入词汇表或者说所要的词汇集合,然后必须要将每一篇文档转换为词汇表上的向量。创建bayes.py的新文件,写入下列程序:

def koadDataSet():
    postingList=[['my','dog','has','flea','problems','help','please'],
                 ['maybe','not','take','him','to','dog','park','stupid'],
                 ['my','dalmatian','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代表正常言论(对应6个文档)
    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)
    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

第一个函数loadDataSet()创建了一些实验样本。该函数返回的第一个变量是进行词条切分后的文档集合,这些文档来自斑点犬爱好者留言板。这些留言文本被切分成一系列的词条集合,标点符号从文本中去掉。loadDataSet()函数返回的第二个变量是一个类别标签的集合。这里有两类,侮辱性和非侮辱性。这些文本的类别由人工标注,这些标注信息用于训练程序以便自动检测侮辱性留言。
下一个函数createVocabList()会创建一个包含在所有文档中出现的不重复词的列表,为此使用了python的set数据类型。将词条列表输给set构造函数,set就会返回一个不重复词表。首先,创建一个空集合,然后将每篇文档返回的新词集合添加到该集合中。操作符|用于求两个集合的并集,这是一个按位或(or)操作符。在数学符号表示上,按位或操作与集合求并操作使用相同记号。
获得词汇后,便可以使用函数setOfWords2Vec(),该函数的输入参数为词汇表即某个文档,输出的是文档向量,向量的每一元素为1或0,分别表示词汇表中的单词在输入文档中是否出现。函数首先创建一个和词汇表等长的向量,并将其元素都设置为0。接着,遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1。
测试代码:

if __name__=='__main__':
    ListPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(ListPosts)
    print(myVocabList)

测试结果:

[‘quit’, ‘my’, ‘I’, ‘garbage’, ‘steak’, ‘worthless’, ‘ate’, ‘help’, ‘take’, ‘how’, ‘park’, ‘has’, ‘maybe’, ‘licks’, ‘buying’, ‘him’, ‘posting’, ‘stupid’, ‘cute’, ‘to’, ‘is’, ‘dog’, ‘flea’, ‘mr’, ‘not’, ‘problems’, ‘stop’, ‘so’, ‘food’, ‘please’, ‘love’, ‘dalmatian’]
上述词表没有重复的单词。

setOfWords2Vec()运行效果:

if __name__=='__main__':
    ListPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(ListPosts)
    res1=setOfWords2Vec(myVocabList,ListPosts[0])
    res2=setOfWords2Vec(myVocabList,ListPosts[3])
    print(res1)
    print(res2)

[0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

该函数使用词汇表或者想要检查的所有单词作为输入,然后为其中每一个单词构建一个特征。一旦给定一篇文档(斑点犬网站上的一条留言),该文档就会被转换为词向量。接下来检查一下函数的有效性。

if __name__=='__main__':
    ListPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(ListPosts)
    res1=setOfWords2Vec(myVocabList,ListPosts[0])
    res2=setOfWords2Vec(myVocabList,ListPosts[3])
    print(myVocabList)
    print(res1)
    print(res2

[‘help’, ‘cute’, ‘him’, ‘buying’, ‘please’, ‘to’, ‘is’, ‘how’, ‘I’, ‘has’, ‘my’, ‘so’, ‘licks’, ‘worthless’, ‘quit’, ‘ate’, ‘mr’, ‘flea’, ‘take’, ‘not’, ‘stupid’, ‘park’, ‘steak’, ‘maybe’, ‘posting’, ‘food’, ‘problems’, ‘dalmatian’, ‘garbage’, ‘love’, ‘dog’, ‘stop’]
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1]

4.5.2 训练算法:从词向量计算概率
重写贝叶斯准则,将之前的x、y替换为w。粗体w表示这是一个向量,即它由多个数组组成。在这个例子中,数值个数与词汇表中的词个数相同。
p(ci|w)=p(w|ci)p(ci)/p(w)
使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。首先通过类别i(侮辱性留言或非侮辱性留言)中文档数除于总的文档数来计算概率p(ci)。接下来计算p(w|ci),这里就要用到朴素贝叶斯假设。如果将w展开为一个个独立特征,那么就可以将上述概率写作p(w0|c1)p(w1|c1)p(w2|c2)…p(w!|c!)来计算上述概率,这就极大地简化了计算的过程。
该函数的伪代码如下:

计算每个类别中的文档数目
对每篇训练文档:
。。对每个类别:
。。。。如果词条出现文档中→增加该词条的计数值
。。。。增加所有词条的计数值
。。对每个类别:
。。。。对每个词条:
。。。。。。将该词条的数目除于总词条数目得到条件概率
。。返回每个类别的条件概率

利用下面的代码实现上述伪代码。将这些代码添加到bayes.py文件中。该函数使用了numpy的一些函数,故应确保将import numpy as np增加到bayes.py文件的最前面。

def trainNBO(trainMatrix,trainCategory):
    numTrainDocs=len(trainMatrix)   #trainMatrix中有多少文档
    numWords=len(trainMatrix[0])    #trainMatrix中第一个文档中有多少词(个数都一样,等于总的不重复词数)
    pABusive=sum(trainCategory)/float(numTrainDocs) #包含侮辱性文字的文档个数占总文档数的比例
    p0Num=np.zeros(numWords)
    p1Num=np.zeros(numWords)
    p0Denom=0.0
    p1Denom=0.0
    for i in range(numTrainDocs):
        if trainCategory[i]==1: #包含侮辱性文字的文档
            p1Num+=trainMatrix[i]  #两个矩阵相加
            p1Denom+=sum(trainMatrix[i])
        else:
            p0Num+=trainMatrix[i]  #不含侮辱性文字的文档
            p0Denom+=sum(trainMatrix[i])
    p1Vect=p1Num/p1Denom
    p0Vect=p0Num/p0Denom
    return  p0Vect,p1Vect,pABusive

代码函数中的输入参数为文档矩阵trainMatrix,以及由每篇文档类别标签所构成的向量trainCategory。首先,计算文档属于侮辱性文档(class=1)的概率,即p(1)。因为这是一个二类分类问题,所以可以通过1-p(1)得到p(0)。对于多分类问题,需对代码稍加修改。
计算p(w!|c1)和p(w!|c0),需初始化程序中的分子变量和分母变量。由于w中元素众多,因此可以使用numpy数组快速计算这些值。上述程序中的分母变量是一个元素个数等于词汇表大小的numpy数组。在for循环中,要遍历训练集trainMatrix中的所有文档。一旦某个词语(侮辱性或正常词语)在某一文档中出现,则该词对应的个数(p1Num或者p0Num)就加1,而且在所有的文档中,该文档的总词数也相应加1.对于两个类别都要进行同样的计算处理。
最后,对每个元素除以该类别中的总词数。利用numpy可以很好实现,用一个数组除以浮点数即可,若使用常规的python列表则很难完成这种任务。最后,函数会返回两个向量和一个概率。
测试代码:

if __name__=='__main__':
    ListPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(ListPosts)  #包含的所有词(无重复)
    trainMat=[]
    for postinDoc in ListPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0v,p1v,pAb=trainNBO(trainMat,listClasses)
    print(p0v)
    print(p1v)
    print(pAb)

测试结果:

myVocabList: ['park', 'I', 'stupid', 'so', 'him', 'stop', 'garbage', 'worthless', 'maybe', 'take', 'dog', 'please', 'flea', 'food', 'dalmatian', 'has', 'ate', 'steak', 'cute', 'mr', 'buying', 'problems', 'quit', 'how', 'to', 'posting', 'licks', 'is', 'my', 'not', 'love', 'help']
p0v: [0.         0.04166667 0.         0.04166667 0.08333333 0.04166667
 0.         0.         0.         0.         0.04166667 0.04166667
 0.04166667 0.         0.04166667 0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.         0.04166667 0.         0.04166667
 0.04166667 0.         0.04166667 0.04166667 0.125      0.
 0.04166667 0.04166667]
p1v: [0.05263158 0.         0.15789474 0.         0.05263158 0.05263158
 0.05263158 0.10526316 0.05263158 0.05263158 0.10526316 0.
 0.         0.05263158 0.         0.         0.         0.
 0.         0.         0.05263158 0.         0.05263158 0.
 0.05263158 0.05263158 0.         0.         0.         0.05263158
 0.         0.        ]
pAb: 0.5

首先,我们发现文档属于侮辱类的概率pAb为0.5,该值是正确的。接下来,看一看在给定文档类别条件下词汇表中单词的出现概率,看看是否正确。词汇表中的第一个词是park,其在类别0中从未出现,而在类别1中出现1次。对应的条件 概率分别为0和0.05263158,该计算是正确的。找到概率中的最大值,该值出现在p(1)数组第2(从0开始数)个下标位置,大小为0.15789474.在myVocabList的第2个下标位置上可以查到该单词是stupid。这意味着stupid是最能表征类别1(侮辱性文档类)的单词。
4.5.3 测试算法:根据现实情况修改分类器
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化1,并将分母初始化为2。
将trainNBO()的第4~7行修改为:

    p0Num=np.ones(numWords)
    p1Num=np.ones(numWords)
    p0Denom=2.0 
    p1Denom=2.0

另一个问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积p(w0|c!)p(w1|c!)p(w2|c!)…p(wn|c!)时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。一种解决办法是对乘积取自然对数。在代数中有ln(a*b)=ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。下图给出函数f(x)与ln(f(x))的曲线。检查这两条曲线,就会发现它们在相同区域内同时增加或减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。通过修改return前的两行代码,将上述做法用到分类器中:

    p1Vect=np.log(p1Num/p1Denom)
    p0Vect=np.log(p0Num/p0Denom)

在这里插入图片描述
在bayes.py中加入以下代码:

#贝叶斯分类函数
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
    p0=sum(vec2Classify*p0Vec)+np.log(1-pClass1)
    if p1>p0:
        return 1
    else:
        return 0
def NBtesting():
    listOPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(listOPosts)
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb=trainNBO(np.array(trainMat),np.array(listClasses))
    testEntry=['love','my','dalmation']
    thidDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
    print(testEntry,'classified as:',classifyNB(thidDoc,p0V,p1V,pAb))
    testEntry=['stupid','garbage']
    thisDoc=np.array(setOfWords2Vec(myVocabList,testEntry))
    print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))

分类代码有四个输入:要分类的向量vec2Classify以及使用函数trainNBO()计算得到的三个概率。使用numpy的数组来计算两个向量相乘的结果。这里的相乘是指对应元素相乘,即先将两个向量中的第一个元素相乘,然后将第2个元素相乘,一次类推。接下来将词汇表中所有词的对应值相加,然后将该值加到类别的对数概率上。最后,比较类别的概率返回大概率对应的类别标签
代码的第二个函数是一个便利函数(convenience function),该函数封装所有操作。
测试代码:

if __name__=='__main__':
    NBtesting()

测试结果:

the word:dalmation is not in my vocabulary!
[‘love’, ‘my’, ‘dalmation’] classified as: 0
[‘stupid’, ‘garbage’] classified as: 1

4.5.4 准备数据:文档词袋模型
目前为止,我们将每个词的出现与否作为一个特征,这可以描述为词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个单词只能出现一次。为适应词袋模型,需要对函数setOfWords2Vec()稍加修改,修改后的函数称为bagOfWords2Vec()。
下面的程序清单给出了基于词袋模型的朴素贝叶斯代码。它与函数setOfWords2Vec()几乎完全相同,唯一不同的是没当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。

#朴素贝叶斯词袋模型
def bagOfWords2Vec(vocabList,inputSet):
    returnVec=[0*len(vocabList)]
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)]+=1
    return returnVec

4.6 示例:使用朴素贝叶斯过滤垃圾邮件
前面的例子引入了字符串列表。使用朴素贝叶斯解决一些现实生活中的问题时,需要先从文本内容得到字符串列表,然后生成词向量。下面例子中,将了解朴素贝叶斯的一个最著名的应用:电子邮件垃圾过滤。首先看一下如何使用通用框架来解决该问题。
示例:使用朴素贝叶斯对电子邮件进行分类
(1)收集数据:提供文本文件。
(2)准备数据:将文本文件解析成词条向量。
(3)分析数据:检查词条确保解析的正确性。
(4)训练算法:使用之前建立的trainNBO()函数。
(5)测试算法:使用classifyNb(),并且构建一个新的测试函数来计算文档集的错误率。
(6)使用算法:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。
4.6.1 准备数据:切分文本
对于一个文本字符串,可以使用python的string.split()方法将其切分:

mySent='This book is the best book on python or M.L. I have ever laid eyes upon.'
print(mySent.split())

[‘This’, ‘book’, ‘is’, ‘the’, ‘best’, ‘book’, ‘on’, ‘python’, ‘or’, ‘M.L.’, ‘I’, ‘have’, ‘ever’, ‘laid’, ‘eyes’, ‘upon.’]

可以看到,切分的效果不错,但是标点符号也被当成了词的一部分。可以使用正则表达式来切分句子,其中分隔符是除单词、数字外的任意字符串。

import re
mySent='This book is the best book on python or M.L. I have ever laid eyes upon.'
regEX=re.compile('\\W+')
listOfTokens=regEX.split(mySent)
print(listOfTokens)

[‘This’, ‘book’, ‘is’, ‘the’, ‘best’, ‘book’, ‘on’, ‘python’, ‘or’, ‘M’, ‘L’, ‘I’, ‘have’, ‘ever’, ‘laid’, ‘eyes’, ‘upon’, ‘’]

现在得到了一系列词组成的词表,但是里面的空字符需要去掉。可以计算每个字符串的长度,只返回长度大于0的字符串。

import re
mySent='This book is the best book on python or M.L. I have ever laid eyes upon.'
regEX=re.compile('\\W+')
listOfTokens=regEX.split(mySent)
print([tok for tok in listOfTokens if len(tok)>0])

这里是引用[‘This’, ‘book’, ‘is’, ‘the’, ‘best’, ‘book’, ‘on’, ‘python’, ‘or’, ‘M’, ‘L’, ‘I’, ‘have’, ‘ever’, ‘laid’, ‘eyes’, ‘upon’]

最后,我们发现句子中的第一个单词是大写的。如果目的是句子查找,那么这个特点会很有用。但这里的文本只看成词袋,所以我们希望所有词的形式都是统一的,不论它们出现在句子中间、结尾还是开头。
python中有一些内嵌的方法,可以将字符串全部转换成小写(.lower())或者大写(.upper()),借助这些方法可以达到目的。于是,可以进行如下处理:

print([tok.lower() for tok in listOfTokens if len(tok)>0])

[‘this’, ‘book’, ‘is’, ‘the’, ‘best’, ‘book’, ‘on’, ‘python’, ‘or’, ‘m’, ‘l’, ‘i’, ‘have’, ‘ever’, ‘laid’, ‘eyes’, ‘upon’]

4.6.2 测试算法:使用朴素贝叶斯进行交叉验证
(缺少数据集,先搁置起来)

4.7 示例:使用朴素贝叶斯分类器从个人广告中获取区域倾向
分类有大量的应用,比如有证据表示,人的年龄越大,他所用的词越好。那么,可以基于一个人的用词来推断他的年龄吗?除了年龄之外,还能否推测其他方面?广告商往往想知道关于一个人的一些特定人口统计信息,以便能够更好地定向推销广告。从哪里可以获得这些训练数据呢?事实上,互联网上拥有大量的训练数据。几乎任一个能想到的利基市场都有专业社区,很多人会认为自己属于该社区。
本节的例子,将分别从美国的两个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同。如果结论确实是不同,那么他们各自常用的词是哪些?从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?
示例:使用朴素贝叶斯来发现地域相关的用词
(1)收集数据:从RSS源收集内容,这里需要对RSS源构建一个接口。
(2)准备数据:将文本文件解析成词条向量。
(3)分析数据:检查词条确保解析的正确性。
(4)训练算法:使用之前建立的trainNBO()函数。
(5)测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果的准确性。
(6)使用算法:构建一个完整的程序,封装所有内容。给定两个RSS源,该程序会显示最常用的公共词。
下面将使用来自不同城市的广告训练一个分类器,然后观察分类器的效果。我们的目的并不是使用分类器进行分类,而是通过观察单词和条件概率值来发现与特定城市相关的内容。
4.7.1 收集数据:导入RSS源
首先使用python下载文本。利用RSS,这些文本很容易得到。现在所需要的是一个RSS阅读器。Universal Feed Parser是python中最常用的RSS程序库。
可以在[http://code.google.com/p/feedparser/]下浏览相关文档,进行下载安装。也可以在电脑终端使用 pip install feedparser进行安装。
下面使用Craigslist上的个人广告,当然希望是在服务条款允许的条件下。打开Craigslist上的RSS源,输入以下代码:
('http://newyork.craigslist.org/stp/index.rss '打不开,换成为:NASA Image of the Day:‘http://www.nasa.gov/rss/dyn/image_of_the_day.rss’)

import feedparser
ny=feedparser.parse('http://www.nasa.gov/rss/dyn/image_of_the_day.rss')
print(len(ny['entries']))

60

可以构建一个函数来对测试过程自动化:

#RSS源分类器及高频词去除函数
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict={}
    for token in vocabList:
        freqDict[token]=fullText.count(token)
    sortedFreq=sorted(freqDict.items(),key=operator.intemgetter(1),reverse=True)
    return sortedFreq[:30]

def localWords(feed1,feed0):
    import feedparser
    docList=[]
    classList=[]
    fullText=[]
    minLen=min(len(feed1['entries']),len(feed0['entries']))
    for i in range(minLen):
        wordList=textParse(feed1['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList=textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList=createVocabList(docList)
    top30Words=calcMostFreq(vocabList,fullText)
    for pairw in top30Words:
        if pairw[0] in vocabList:
            vocabList.remove(pairw[0])
    trainingSet=list(range(2*minLen))
    testSet=[]
    for i in range(20):
        randIndex=int(np.random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        #del(trainingSet[randIndex])   #不注释掉这行代码会报错:IndexError: list index out of range(未找到原因)
    trainMat=[]
    trainClasses=[]
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2Vec(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam=trainNBO(np.array(trainMat),np.array(trainClasses))
    errorCount=0
    for docIndex in testSet:
        wordVector=bagOfWords2Vec(vocabList,docList[docIndex])
        if classifyNB(np.array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
            errorCount+=1
    print('the error rate is:',float(errorCount)/len(testSet))
    return vocabList,p0V,p1V

上述代码的辅助函数calcMostFreq()遍历词汇表中的每个词并统计它在文本中出现的次数,然后根据出现次数从高到低对词典进行排序,最后返回排序最高的100个单词。
函数localWords()使用两个RSS源作为参数。RSS源要在函数外导入,这样做的原因是RSS源会随时间而改变。如果想通过改变代码来比较程序执行的差异,就应该使用相同的输入。重新加载RSS源就会得到新的数据,但很难确定是代码原因还是输入原因导致输出结果的改变。函数localWords()访问的是RSS源而不是文件。然后调用函数calcMostFreq()来获得排序最高的100个单词并随后将它们移除。
可以注释掉用于移除高频词的代码(top30words),然后比较注释前后的分类性能。有无这段代码,错误率可能发生很大变化,这是因为词汇表中的一小部分单词会占据所有文本用词的一大部分。产生这种现象的原因是因为语言中大部分都是冗余和结构辅助性内容。另一个常用的方法是不仅移除高频词,同时从某个预定词表中移除结构上的辅助词,该词表称为停用词表,目前可以找到许多停用词列表。
测试程序:
(将‘http://sfbay.craigslist.org/stp/index.rss’替换为:Yahoo Sports - NBA - Houston Rockets News:‘http://sports.yahoo.com/nba/teams/hou/rss.xml’)

if __name__=='__main__':
    ny = feedparser.parse('http://www.nasa.gov/rss/dyn/image_of_the_day.rss')
    sf=feedparser.parse('http://sports.yahoo.com/nba/teams/hou/rss.xml')
    vocabList,pSF,pNY=localWords(ny,sf)

the error rate is: 0.65

为了得到错误率的精确估计,应该多次进行上述实验,然后取平均值。这里的错误率较高,由于这里关注的是单词概率而不是实际分类,因此这个问题倒不严重。可以通过函数calcMostFreq()改变要移除的单词数目,然后观察错误率的变化情况。
4.7.2 分析数据:显示地域相关的用词
可以先对向量pSF与pNY进行排序,然后按照顺序将词打印出来。下面的最后一段代码会完成这部分工作:

#最具表征性的词汇显示函数
def getTopWords(ny,sf):
    import operator
    vocabList,p0V,p1V=localWords(ny,sf)
    topNY=[]
    topSF=[]
    for i in range(len(p0V)):
        if p0V[i]>-6:
            topSF.append((vocabList[i],p0V[i]))
        if p1V[i]>-6:
            topNY.append((vocabList[i],p1V[i]))
    sortedSF=sorted(topSF,key=lambda pair:pair[1],reverse=True)
    print('SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF')
    for item in sortedSF:
        print(item[0])
    sortedNY=sorted(topNY,key=lambda pair:pair[1],reverse=True)
    print('NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY')
    for item in sortedNY:
        print(item[0])

getTopWords()使用两个RSS源作为输入,然后训练并测试朴素贝叶斯分类器,返回使用的概率值。然后创建两个列表用于元组的存储。与之前返回排名最高的X个单词不同,这里可以返回大于某个阈值的所有词。这些元组会按照它们的条件概率进行排序。
测试代码:

if __name__=='__main__':
    ny = feedparser.parse('http://www.nasa.gov/rss/dyn/image_of_the_day.rss')
    sf=feedparser.parse('http://sports.yahoo.com/nba/teams/hou/rss.xml')
    getTopWords(ny,sf)

the error rate is: 0.45
SFSFSFSFSFSFSFSFSFSFSFSFSFSF
state
when
houston
titles
will
basketball
billion
his
forward
buy
… #(太多了,后面省略不粘上来了)
NY
NYNYNYNYNYNYNYNYNYNYNYNYNY
nasa
south
image
throughout
bob
2019
wind
pacific
instrument
continue

如果移除固定的停用词,分类错误率也会降低。

4.8 本章小结
对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值估计未知概率的有效方法。
可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然这个假设过于简单,这就是之所以成为朴素贝叶斯的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。
利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出就是其中一个问题,它可以通过对概率取对数来解决。词袋模型在解决文档分类问题上比词集模型有所提高。还有其它一些方面的改进,比如说移除停用词,当然也可以花大量时间对切分器进行优化。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整全套资源下载地址:https://download.csdn.net/download/qq_27595745/66030967 【完整课程列表】 完整版 南京邮电大学 机器学习课程教程PPT课件 1-1.机器学习简介-上课版part1(共31页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 1-2 机器学习简介-上课版part2(共55页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 2. 概念学习 分类(共27页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 3 决策树(共44页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 4-1 贝叶斯学习(共18页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 5. 神经网络(共42页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 6 支持向量机(共29页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 7. 基于实例的学习-k近邻(共17页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 8-1 模式选择和评估(共30页).pdf 完整版 南京邮电大学 机器学习课程教程PPT课件 8-2 模式选择和评估(共14页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 9. 计算学习理论(共26页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 10 聚类分析(共74页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 12 特征选择(共36页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 13 Sparse-SDM10(共133页).pdf 完整版 南京邮电大学 机器学习课程教程PPT课件 14 机器学习总结(共25页).ppt 完整版 南京邮电大学 机器学习课程教程PPT课件 15 Overview of ensemble(共31页).ppt

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值