• 第四章 基于概率论的分类方法朴素贝叶斯

    • 4-1 基于贝叶斯决策理论的分类方法
    • 4-2 条件概率
    • 4-3 使用条件概率来分类
    • 4-4 使用朴素贝叶斯进行文档分类
    • 4-5 使用Python进行文本分类
      • 4-5-1 准备数据从文本中构建词向量
      • 4-5-2 训练算法从词向量计算概率
      • 4-5-3 测试算法根据现实情况修改分类器
      • 4-5-4 准备数据文档词袋模型
    • 4-6 示例使用朴素贝叶斯过滤垃圾邮件
      • 4-6-1 准备数据切分文本
      • 4-6-2 测试算法使用朴素贝叶斯进行交叉验证
    • 4-7 示例使用朴素贝叶斯分类器从个人广告中获取区域倾向
      • 4-7-1 收集数据导入RSS源
      • 4-7-2 分析数据显示地域相关的用词
    • 4-8 本章小结
    • 4-9 参考文献

第四章 基于概率论的分类方法:朴素贝叶斯

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

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

假设现在我们有一个数据集,它由两类数据组成,数据分布如下图所示:

假设有位读者找到了描述途中两类数据的统计参数。我们现在用p1(x,y)表示数据点(x,y)属于类别1(途中用圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(途中用三角形表示的类别)的概率,那么对于一个新数据点,可以用下面的规则来判断他的类别:

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

也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。

4-2 条件概率

假设现在有一个装了7块石头的罐子,其中3块是灰色的,4块是黑色的。如下图所示:

由于取石头有7种可能,漆黑中3种为灰色,所以取出灰色石头的概率为3/7。显然,取到黑色石头的概率是4/7。使用P(gray)来表示渠道灰色石头的概率,其概率值可以通过灰色石头数目除以总的石头数目来得到。

如果7块石头如下图放在两个桶中,上诉概率应如何计算?

要计算P(gray)或者P(black),事先得知道石头所在桶的信息会不会改变结果?你有可能已经想到计算从B桶中取到灰色石头的概率的方法,这就是所谓的条件概率(conditional probability)。假定计算的是从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),那么可以使用下面的计算方法:

4-3 使用条件概率来分类

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

  • -如果p1(x,y) > p2(x,y),那么属于类别1;
  • 如果p2(x,y) > p1(x,y),那么属于类别2。

但是这两个准则并不是贝叶斯决策理论的所有内容。使用p()和p()只是为了尽可能简化描述,而真正需要计算和比较的是p(c1|x,y)和p(c2|x,y)。

这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别c1的概率是多少?数据点来自类别c2的概率又是多少?这些概率与刚才给出的概率p(x,y|c2)并不一样,不过可以使用贝叶斯准测来交换概率中条件与结果。具体地,应用贝叶斯准则得到:

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

  • -如果p1(c1|x,y) > p2(c2|x,y),那么属于类别1;
  • 如果p1(c1|x,y) < p2(c2|x,y),那么属于类别2。

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

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

朴素贝叶斯的一般过程:

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

由统计学知,如果每个特征需要N个样本,那么对于10个特征将需要 N10 N^{10} 个样本,对于包含1000个特征的词汇表将需要N1000N^{1000}个样本。可以看出,所需要的样本数会随着特征数目增大而迅速增长。

如果特征之间相互独立,那么样本数就可以从N1000N^{1000}减少到1000∗N1000*N。所谓独立(independence)指的是统计意义上的独立,即一个特征或者单词出现的可能性与它和其他单词相邻没有关系。举个例子说:假设单词bacon出现在unhealthy后面与出现在delicious后面的概率相同。当然,我们知道这种假设并不正确,bacon常常出现在delicious附近,而很少出现在unhealthy附近,这个假设正式朴素贝叶斯分类器中朴素(native)依次的含义。

朴素贝叶斯分类器中的另一个假设是,每个特征同等重要。其实这个假设也有问题。尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效率却很好。

4-5 使用Python进行文本分类

要从文本中获取特征,需要先拆分文本。

这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把此条想象为单词,也可以使用非单词词条,如URL、IP地址或者任意其它字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现的文档中,0表示词条未出现。

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

4-5-1 准备数据:从文本中构建词向量

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

打开文本编辑器,创建bayes.py的新文件,并输入下列程序。

"""Function:创建一些实验样本Parameters:NoneReturn:postingList——进行词条切分后的文档集合classVec——类别标签的集合Modify:2017-12-25
"""
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:创建一个包含所有文档中出现的不重复的列表Parameters:dataSet—— 输入数据Return:list(vocabSet)——包含所有文档中出现的不重复的列表Modify:2017-12-25
"""
def createVocabList(dataSet):vocabSet = set([])  #创建一个空集for document in dataSet:vocabSet = vocabSet | set(document) #创建两个集合的并集return list(vocabSet)"""Function:输出词汇表中单词在输入文档中是否出现,向量的每个元素为1(出现)或0(未出现)Parameters:vocabList—— 词汇表inputSet—— 文档向量Return:returnVec——文档向量Modify:2017-12-25
"""
def setOfWords2Vec(vocabList, inputSet):returnVec = [0]*len(vocabList) #创建一个其中所有元素都为0的向量for word in inputSet:if word in vocabList:returnVec[vocabList.index(word)] = 1else: print "the word: %s is not in my Vocabulary!" % wordreturn returnVec

第一个函数loadDataSet()中返回的第二个变量是一个类别标签的集合,这里分为侮辱性和非侮辱性两类。这些类别标签由人工标注,这些标注信息用于训练程序以便自动检测侮辱性留言。

现在在Python提示符下输入下列代码,看一下这些函数的执行效果:

>>> import sys
>>> sys.path.append('E:\Python_Files\CodeofMe\Chapter4')
>>> import bayes
>>> listOposts,listClasses = bayes.loadDataSet()>>> myVocabList = bayes.createVocabList(listOposts)
>>> myVocabList
['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']

检查上述词表,发现这里不会出现重复的单词。

>>> bayes.setOfWords2Vec(myVocabList,listOposts[0])
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1]
>>> bayes.setOfWords2Vec(myVocabList,listOposts[3])
[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
>>> 

上述代码是setOfWords2Vec()的运行效果。

>>> listOposts[0]
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']
>>> listOposts[3]
['stop', 'posting', 'stupid', 'worthless', 'garbage']`

listOposts[0]与listOposts[3]
与词表进行对比,会发现setOfWords2Vec()输出结果无误。

4-5-2 训练算法:从词向量计算概率

p(c1|w)=p(c1|w)∗p(ci)p(w)

p(c1|w) = \dfrac{p(c1|w)*p(ci)}{p(w)}

我们将用到上诉公式,对每个类计算该值,然后比较这两个概率值的大小。

如何计算呢?首先可以通过类别i(侮辱性留言或非侮辱性留言)中文档数除以总的文档数来计算概率p(c1)。接下来计算p(w|c1),这里就要用到朴素贝叶斯假设。如果将w展开为一个个独立特征,那么就可以将上诉概率写作p(w0,w1,w2…wn|ci)。这里假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用p(w0|c1)p(w1|c1)p(w2|c2)…p(wn|ci)来计算上诉概率,这就极大地简化了计算的过程。

该函数的伪代码如下:

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

利用下面代码实现上诉伪代码:

"""Function:朴素贝叶斯分类器训练函数        Parameters:trainMatrix—— 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵trainCategory—— 训练类别标签向量,即loadDataSet返回的classVecReturn:p0Vect——侮辱类的条件概率数组p1Vect——非侮辱类的条件概率数组pAbusive——文档属于侮辱类的概率Modify:2017-12-28
"""
def trainNB0(trainMatrix,trainCategory):numTrainDocs = len(trainMatrix)#计算训练的文档数目    numWords = len(trainMatrix[0])#计算每篇文档的词条数    pAbusive = sum(trainCategory)/float(numTrainDocs)#文档属于侮辱类的概率    p0Num = zeros(numWords); p1Num = zeros(numWords)      #change to ones()#创建numpy.zeros数组,词条出现数初始化为0 p0Denom = 0.0; p1Denom = 0.0                        #change to 2.0#分母初始化为0.0for 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/p1Denomp0Vect = p0Num/p0Denom #p1Vect = log(p1Num/p1Denom)          #change to log()#p0Vect = log(p0Num/p0Denom)          #change to log()return p0Vect,p1Vect,pAbusive#返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率

该函数使用了Numpy的一些函数,故应确保将

from numpy import *

语句添加到bayes.py文件的最前面。

最后函数返回两个向量和一个概率。

在Python提示符下输入下列代码看一下效果:

>>> import sys
>>> sys.path.append('E:\Python_Files\CodeofMe\Chapter4')
>>> import bayes
>>> from numpy import *
>>> list0Posts,listClasses = bayes.loadDataSet()

该语句从预先加载值中调入数据。

>>> myVocabList = bayes.createVocabList(list0Posts)

至此我们构建了一个包含所有次的列表myVocabList :

>>> myVocabList
['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']

for循环使用词向量来填充trainMat列表:

>>> trainMat = []
>>> for postinDoc in list0Posts:trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))

下面给出属于侮辱性性文档的概率以及两个类别的概率向量:

>>> p0V,p1V,pAb = bayes.trainNB0(trainMat,listClasses)

这些变量的内部值:

>>> p0V
array([ 0.04166667,  0.04166667,  0.04166667,  0.        ,  0.        ,0.04166667,  0.04166667,  0.04166667,  0.        ,  0.04166667,0.04166667,  0.04166667,  0.04166667,  0.        ,  0.        ,0.08333333,  0.        ,  0.        ,  0.04166667,  0.        ,0.04166667,  0.04166667,  0.        ,  0.04166667,  0.04166667,0.04166667,  0.        ,  0.04166667,  0.        ,  0.04166667,0.04166667,  0.125     ])
>>> p1V
array([ 0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,0.        ,  0.        ,  0.        ,  0.05263158,  0.05263158,0.05263158,  0.05263158,  0.05263158,  0.        ,  0.10526316,0.        ,  0.05263158,  0.05263158,  0.        ,  0.10526316,0.        ,  0.15789474,  0.        ,  0.05263158,  0.        ,0.        ,  0.        ])
>>> pAb
0.5

属于侮辱类的概率pAb为0.5,从我们自己建立的实验样本来看,6个文档,3个是侮辱性的,所以该值是正确的。

 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代表正常言论 

接下来,看一看在给定文档类别条件下词汇表中单词的出现概率,看看是否正确。词汇表中的第一词是cute:

>>> myVocabList
['cute', 'love', 'help', 'garbage', 'quit', 'I', 'problems', 'is', 'park', 'stop', 'flea', 'dalmation', 'licks', 'food', 'not', 'him', 'buying', 'posting', 'has', 'worthless', 'ate', 'to', 'maybe', 'please', 'dog', 'how', 'stupid', 'so', 'take', 'mr', 'steak', 'my']

其在类别0中出现1次,而在类别1中从未出现。对应的条件概率分别为0.04166667与0.0。该计算值是正确的。我们找找所有概率中的最大值,该值出现在P(1)数组里,大小为0.15789474。在myVocabList的第26个下标位置上可以查到该单词是stupid。这意味着stupid是最能表征类别1(侮辱性文档类)的单词。

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

利用贝叶斯分类器对文档进行分类时,要计算多个概率乘积以获得文档属于某个类别的概率。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。

在bayes.py文件的trainNB0()函数中的:

    p0Num = zeros(numWords); p1Num = zeros(numWords)      p0Denom = 0.0; p1Denom = 0.0 

改为:

p0Num = one(numWords); p1Num = one(numWords)
p0Denom = 2.0; p1Denom = 2.0 

另一个问题是下溢出,这是由于太多很小的数相乘造成的。

解决方法:对乘积取自然对数。

在代数中有

ln(a∗b)=ln(a)+ln(b)

ln(a*b) = ln(a)+ln(b),于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。

如下图,是f(x)与ln(f(x))的曲线:

检查两条曲线,发现他们在相同区域内同时增加或者减少,并且在相同点上取到极值。虽然取值不同,但不影响最终结果。

通过修改return前的两行代码,将上诉做法用到分类器中:

    p1Vect = log(p1Num/p1Denom)          #change to log()p0Vect = log(p0Num/p0Denom)          #change to log()

下面利用朴素贝叶斯分类器进行分类测试,在bayes.py添加下列代码:

"""Function:朴素贝叶斯分类函数      Parameters:vec2Classify—— 待分类的向量p0Vec——非侮辱类的条件概率数组p1Vec——侮辱类的条件概率数组pClass1——文档属于侮辱类的概率Return:1或0——侮辱性或者非侮辱性类别Modify:2017-12-29
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):p1 = sum(vec2Classify * p1Vec) + log(pClass1)  #元素相乘 这里的 vec2Classify * p1Vec相乘是指矩阵里对应的元素相乘#element-wise multp0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)#元素相乘 这里的 vec2Classify * p0Vec相乘是指矩阵里对应的元素相乘 if p1 > p0:return 1#返回文档属于侮辱性类别else: return 0#返回文档属于非侮辱性类别
"""Function:朴素贝叶斯分类器测试函数        Parameters:NoneReturn:NoeeModify:2017-12-29
"""
def testingNB():listOPosts,listClasses = loadDataSet()#获取文档集合和文档类别标签的集合myVocabList = createVocabList(listOPosts)#得到所有文档中出现的词且不重复的列表trainMat=[]#建立一个空集用来存储元素为0(未出现)或1(出现)的文档向量(该文档向量每一个文档含有所有训练集的词汇)for postinDoc in listOPosts:#根据得到的训练集词汇列表,对每个文档的词汇进行检测,并将输出与词汇列表大小一样的标称型文档向量trainMat.append(setOfWords2Vec(myVocabList, postinDoc))#得到训练集文档的标称型文档向量p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))#得到训练集的非侮辱类的条件概率数组、侮辱类的条件概率数组、文档属于侮辱类的概率testEntry = ['love', 'my', 'dalmation']#待测试的文档集合thisDoc = array(setOfWords2Vec(myVocabList, testEntry))#将待测试的文档集合转换成标称型文档向量并转换成矩阵形式方便后面计算print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)#对测试文档进行分类测试并输出测试结果testEntry = ['stupid', 'garbage']thisDoc = array(setOfWords2Vec(myVocabList, testEntry))print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb)

代码中的第二个函数是一个便利函数(convenience function),该函数封装所有测试操作。

下面在Python提示符下输入下列代码,看看实际测试结果:

>>> reload(bayes)
<module 'bayes' from 'E:\Python_Files\CodeofMe\Chapter4\bayes.py'>
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1

4-5-4 准备数据:文档词袋模型

目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of_words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数setOfWords2Vec()稍加修改,修改后的函数称为bagOfWords2VecMN()。

"""Function:词袋模型:每个单词可以出现多次Parameters:vocabList—— 词汇表inputSet—— 文档向量Return:returnVec——包含词汇出现次数信息的文档向量Modify:2017-12-29
"""
def bagOfWords2VecMN(vocabList, inputSet):returnVec = [0]*len(vocabList)for word in inputSet:if word in vocabList:returnVec[vocabList.index(word)] += 1return returnVec

该函数与setOfWords2Vec()几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。

4-6 示例:使用朴素贝叶斯过滤垃圾邮件

下面这个例子,是朴素贝叶斯的一个最著名的应用:电子邮箱垃圾过滤

首先看一下如何使用通用框架来解决该问题:

示例:使用朴素贝叶斯对电子邮件进行分类

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

4-6-1 准备数据:切分文本

前面我们通过自己创建词向量,并基于这些词向量进行朴素贝叶斯分类的过程。前面的词向量使我们预先给订的,现在看看如何从文本文档中构建自己的词列表。

在Python提示符下输入下列代码:

>>> mySent = 'This book is the best book on Python or M.L. I have ever laid eyes upon.'
>>> mySent.split()
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M.L.', 'I', 'have', 'ever', 'laid', 'eyes', 'upon.']

上诉代码是使用了Python的String.split()方法将其切分,其切分的结果不错,但是标点符号也被当成了词的一部分。

可以使用正则表示式来切分句子,其中分隔符是除单词、数字外的任意字符串。

>>> import re
>>> regEx = re.compile('\\W*')
>>> listOfTokens = regEx.split(mySent)
>>> listOfTokens
['This', 'book', 'is', 'the', 'best', 'book', 'on', 'Python', 'or', 'M', 'L', 'I', 'have', 'ever', 'laid', 'eyes', 'upon', '']

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

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

现在来看数据集中一封完整的电子邮件的实际处理结果。

>>> emailText = open(r'E:\Python_Files\CodeofMe\Chapter4\email\ham\6.txt').read()
>>> emailText
'Hello,\n\nSince you are an owner of at least one Google Groups group that uses the customized welcome message, pages or files, we are writing to inform you that we will no longer be supporting these features starting February 2011. We made this decision so that we can focus on improving the core functionalities of Google Groups -- mailing lists and forum discussions.  Instead of these features, we encourage you to use products that are designed specifically for file storage and page creation, such as Google Docs and Google Sites.\n\nFor example, you can easily create your pages on Google Sites and share the site (http://www.google.com/support/sites/bin/answer.py?hl=en&answer=174623) with the members of your group. You can also store your files on the site by attaching files to pages (http://www.google.com/support/sites/bin/answer.py?hl=en&answer=90563) on the site. If you\x92re just looking for a place to upload your files so that your group members can download them, we suggest you try Google Docs. You can upload files (http://docs.google.com/support/bin/answer.py?hl=en&answer=50092) and share access with either a group (http://docs.google.com/support/bin/answer.py?hl=en&answer=66343) or an individual (http://docs.google.com/support/bin/answer.py?hl=en&answer=86152), assigning either edit or download only access to the files.\n\nyou have received this mandatory email service announcement to update you about important changes to Google Groups.'
>>> listOfTokens = regEx.split(emailText)
>>> listOfTokens
['Hello', 'Since', 'you', 'are', 'an', 'owner', 'of', 'at', 'least', 'one', 'Google', 'Groups', 'group', 'that', 'uses', 'the', 'customized', 'welcome', 'message', 'pages', 'or', 'files', 'we', 'are', 'writing', 'to', 'inform', 'you', 'that', 'we', 'will', 'no', 'longer', 'be', 'supporting', 'these', 'features', 'starting', 'February', '2011', 'We', 'made', 'this', 'decision', 'so', 'that', 'we', 'can', 'focus', 'on', 'improving', 'the', 'core', 'functionalities', 'of', 'Google', 'Groups', 'mailing', 'lists', 'and', 'forum', 'discussions', 'Instead', 'of', 'these', 'features', 'we', 'encourage', 'you', 'to', 'use', 'products', 'that', 'are', 'designed', 'specifically', 'for', 'file', 'storage', 'and', 'page', 'creation', 'such', 'as', 'Google', 'Docs', 'and', 'Google', 'Sites', 'For', 'example', 'you', 'can', 'easily', 'create', 'your', 'pages', 'on', 'Google', 'Sites', 'and', 'share', 'the', 'site', 'http', 'www', 'google', 'com', 'support', 'sites', 'bin', 'answer', 'py', 'hl', 'en', 'answer', '174623', 'with', 'the', 'members', 'of', 'your', 'group', 'You', 'can', 'also', 'store', 'your', 'files', 'on', 'the', 'site', 'by', 'attaching', 'files', 'to', 'pages', 'http', 'www', 'google', 'com', 'support', 'sites', 'bin', 'answer', 'py', 'hl', 'en', 'answer', '90563', 'on', 'the', 'site', 'If', 'you', 're', 'just', 'looking', 'for', 'a', 'place', 'to', 'upload', 'your', 'files', 'so', 'that', 'your', 'group', 'members', 'can', 'download', 'them', 'we', 'suggest', 'you', 'try', 'Google', 'Docs', 'You', 'can', 'upload', 'files', 'http', 'docs', 'google', 'com', 'support', 'bin', 'answer', 'py', 'hl', 'en', 'answer', '50092', 'and', 'share', 'access', 'with', 'either', 'a', 'group', 'http', 'docs', 'google', 'com', 'support', 'bin', 'answer', 'py', 'hl', 'en', 'answer', '66343', 'or', 'an', 'individual', 'http', 'docs', 'google', 'com', 'support', 'bin', 'answer', 'py', 'hl', 'en', 'answer', '86152', 'assigning', 'either', 'edit', 'or', 'download', 'only', 'access', 'to', 'the', 'files', 'you', 'have', 'received', 'this', 'mandatory', 'email', 'service', 'announcement', 'to', 'update', 'you', 'about', 'important', 'changes', 'to', 'Google', 'Groups', '']
>>> 

该6.txt文件非常长,这是某公司告知他们不再进行某些支持的一些邮件。

4-6-2 测试算法:使用朴素贝叶斯进行交叉验证

将文本解析器集成到一个完成分类器中。在bayes.py文件中添加下列代码。

"""Function:文件解析以达到切分文本生成词列表Parameters:bigString——文本中的字符串Return:[tok.lower() for tok in listOfTokens if len(tok) > 2]—— 词汇列表Modify:2017-12-29
"""
def textParse(bigString):
#input is big string,
#output is word listimport relistOfTokens = re.split(r'\W*', bigString)#切分文本字符串生成词汇列表return [tok.lower() for tok in listOfTokens if len(tok) > 2] #返回字符串长度大于2的词汇"""Function:垃圾邮箱测试函数Parameters:NoneReturn:NoneModify:2017-12-29
"""
def spamTest():docList=[]; classList = []; fullText =[]#docList每封邮件的词汇作为元素的列表、classList人为设定的邮件的类别(垃圾邮件或非垃圾邮件)、fullText所有邮件的词汇列表,一个词作为一个元素for i in range(1,26):#对25封垃圾邮件和非垃圾邮件进行导入并解析文本文件wordList = textParse(open(r'E:\Python_Files\CodeofMe\Chapter4\email\spam\%d.txt' % i).read())#读取垃圾邮件并将其生成词列表docList.append(wordList)#垃圾邮件词列表作为一个元素存放在docList文档列表中fullText.extend(wordList)#将垃圾邮件里的词汇全部存放在fullText列表中,一个词汇作为一个元素classList.append(1)#用1标记垃圾邮件存放在classList列表中wordList = textParse(open(r'E:\Python_Files\CodeofMe\Chapter4\email\ham\%d.txt' % i).read())#读取非垃圾邮件并将其生成词列表docList.append(wordList)#非垃圾邮件词列表作为一个元素存放在docList文档列表中        fullText.extend(wordList)#将非垃圾邮件里的词汇全部存放在fullText列表中,一个词汇作为一个元素classList.append(0)#用0标记垃圾邮件存放在classList列表中#这儿采用交替标记垃圾和非垃圾邮件,即classList=[1,0,1,0...]vocabList = createVocabList(docList)#create vocabulary 建立词典(该词典包括了25封垃圾和25封非垃圾邮件的所有长度大于2的词汇) 词典里的词不重复trainingSet = range(50)#设置训练长度(共有50封邮件)testSet=[]           #create test set 创建测试集for i in range(10):#随机构建训练集randIndex = int(random.uniform(0,len(trainingSet)))#生成0到50之间的随机数,并将其转换成整型作为索引号testSet.append(trainingSet[randIndex])#将生成的随机整数存放在testSet列表中del(trainingSet[randIndex])#删除 trainingSet列表中对应随机生成的索引号的值从而得到训练集索引号trainMat=[]; trainClasses = []#建立trainMat空集用来存储文档向量,建立trainClasses空集用来存储邮件分类向量for docIndex in trainingSet:#train the classifier (get probs) trainNB0 训练分类器trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))#得到针对词汇出现次数训练集计数的文档向量trainClasses.append(classList[docIndex])#得到训练集文档对应的类别向量标签p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))#根据训练集得到非垃圾类的条件概率数组、垃圾类的条件概率数组、文档属于垃圾类的概率errorCount = 0#错误计数初始化for docIndex in testSet:        #classify the remaining items 分类器测试并测试错误率wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])#得到关于测试组词汇出现次数的文档向量if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:#检测分类器分类的结果是否与测试集实际的类别一样errorCount += 1#不一样,错误计数加1print "classification error",docList[docIndex]#输出分类错误的文档print 'the error rate is: ',float(errorCount)/len(testSet)#输出分类器的错误率

第一个函数textParse()接受一个大字符串并将起解析为字符串列表。该函数去掉少于两个字符的字符串,并将所有字符串转换为小写。

第二个函数spamTest()对贝叶斯垃圾邮件分类系进行自动化处理。

导入文件夹spam和ham下的文本文件,并将他们解析为词列表。接下来构建一个测试集和一个训练集,两个集合中的邮件都是随机选出的。分类器所需要的概率计算只利用训练集中的文档来完成。Python变量trainingSet是一个整数列表,其中的值从0到49。接下来,随机选择其中10个文件。选择出的数字所对应的文档被添加到测试集,同时也将其从训练集中剔除。这种随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validation)

接下来的for循环遍历训练集的所有文档,对每封邮件基于词汇表并使用bagOfWords2VecMN()函数来构建词向量。这些词在trainNB0()函数中用于计算分类所需的概率。然后遍历测试集,对其中每封电子邮件进行分类。如果邮件分类错误,则错误数加1,最后给出总的错误百分比。

在Python提示符下输入下列代码,测试效果:

>>> import sys
>>> sys.path.append('E:\Python_Files\CodeofMe\Chapter4')
>>> import bayes
>>> bayes.spamTest()
classification error ['experience', 'with', 'biggerpenis', 'today', 'grow', 'inches', 'more', 'the', 'safest', 'most', 'effective', 'methods', 'of_penisen1argement', 'save', 'your', 'time', 'and', 'money', 'bettererections', 'with', 'effective', 'ma1eenhancement', 'products', 'ma1eenhancement', 'supplement', 'trusted', 'millions', 'buy', 'today']
the error rate is:  0.1

函数spamTest()会输出在10封随机选择的电子邮件上的分类错误率。

4-7 示例:使用朴素贝叶斯分类器从个人广告中获取区域倾向

前面介绍了朴素贝叶斯的两个实际应用的例子,第一个例子是过滤网站的恶意留言,第二个是过滤垃圾邮箱。

广告商往往想知道关于一个人的一些特定人口统计信息,以便能够更好地定向推销广告。从哪里可以获得这些训练数据呢?事实上,互联网上拥有大量的训练数据。

最后一个例子,我们将分别从美国的脸各个城市中选取一些人,通过分析这些人发布的征婚广告信息,来比较这两个城市的人们在广告用词上是否不同。如果结论的确不同, 那么他们各自常用的词是哪些?从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?

示例:使用朴素贝叶斯来发现地域相关的用词

1. 收集数据:从RSS源收集内容,这里需要对RSS源构建一个借口。
2. 准备数据:将文本文件解析成词条向量。
3. 分析数据:检查词条确保解析的正确性。
4. 训练算法:使用我们之前建立的trainNB0()函数。
5. 测试算法:观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果。
6. 使用算法:构建一个完成的程序,封装所有内容。给定两个RSS源,该程序会显示最常用的公共词。

4-7-1 收集数据:导入RSS源

接下来要做的第一件事是使用Python下载文本。利用RSS,这些文本很容易得到。Universal Feed Parser 是Python中最常用的RSS程序库。

feedparser地址

feedparser的安装教程

下面使用Craigslist上的个人广告,打开Craigslist上的RSS源,在Python提示符下输入:

>>> import feedparser
>>> ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')

要访问所有条目的列表,输入:

>>> ny['entries']
>>> len(ny['entries'])
25

下面构建类似于spamTest()的函数来测试过程自动化。在bayes.py文件中增加下列代码:

"""Function:获取文档的高频词Parameters:vocabList——不重复词向量列表fullText——文档对应的所有词汇Return:sortedFreq[:30]——频率高的前30个词汇Modify:2017-12-30
"""
def calcMostFreq(vocabList,fullText):import operator#operator——运算符模块freqDict = {}#创建空集词典for token in vocabList:freqDict[token]=fullText.count(token)#计算词汇列表中的词在所有文档中出现的次数sortedFreq = sorted(freqDict.iteritems(), key=operator.itemgetter(1), reverse=True) #将词汇按照健值从大到小排序return sortedFreq[:30]  #返回排序在前30的词汇"""Function:RSS源分类器Description:该部分的程序spamTest()类似,仅添加了剔除高频词函数Parameters:feed1——RSS源1feed0——RSS源2Return:vocabList——词汇表p0V——NY类的条件概率数组p1V——SF类的条件概率数组Modify:2017-12-30
"""
def localWords(feed1,feed0):import feedparserdocList=[]; 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) #NY is class 1wordList = textParse(feed0['entries'][i]['summary'])docList.append(wordList)fullText.extend(wordList)classList.append(0)vocabList = createVocabList(docList)#create vocabularytop30Words = calcMostFreq(vocabList,fullText)   #remove top 30 wordsfor pairW in top30Words:if pairW[0] in vocabList: vocabList.remove(pairW[0])trainingSet = range(2*minLen); testSet=[]           #create test setfor i in range(20):randIndex = int(random.uniform(0,len(trainingSet)))testSet.append(trainingSet[randIndex])del(trainingSet[randIndex])  trainMat=[]; trainClasses = []for docIndex in trainingSet:#train the classifier (get probs) trainNB0trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))trainClasses.append(classList[docIndex])p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))errorCount = 0for docIndex in testSet:        #classify the remaining itemswordVector = bagOfWords2VecMN(vocabList, docList[docIndex])if classifyNB(array(wordVector),p0V,p1V,pSpam) != classList[docIndex]:errorCount += 1print 'the error rate is: ',float(errorCount)/len(testSet)return vocabList,p0V,p1V

上述localWords()函数与spamTest()函数类似,只是添加了一个辅助函数calcMostFreq()。该函数遍历词汇表中的每个词并统计他在文本中出现的次数,然后根据出现次数从高到低对字典进行排序,最后返回排序最高的30个单词。

采用calcMostFreq()函数,是为了去除高频词汇,因为词汇表中的小部分单词占据了所有文本用词的大部分。产生这种现象的原因是因为语言中大部分都是冗余和结构辅助性内容。另一个常用的方法是不仅一出高频词,同时从某个预定词表中移除结构上的辅助词。该词表称为停用词表(stop word list)

在Python命令提示符下输入下列代码,测试结果:

>>> import sys
>>> sys.path.append('E:\Python_Files\CodeofMe\Chapter4')
>>> import bayes
>>> import feedparser
>>> ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
>>> sf = feedparser.parse('http://sfbay.craigslist.org/stp/index.rss')
>>> vocabList,pSF,pNY = bayes.localWords(ny,sf)
the error rate is:  0.5
>>> vocabList,pSF,pNY = bayes.localWords(ny,sf)
the error rate is:  0.45

4-7-2 分析数据:显示地域相关的用词

可以先对向量pSF与pNY进行排序,然后按照顺序将此打印出来。在bayes.py文件中添加下列代码:

"""Function:最具表征性的词汇显示函数Parameters:ny——RSS源1sf——RSS源2Return:NoneModify:2017-12-30
"""
def getTopWords(ny,sf):import operator#operator——运算符模块vocabList,p0V,p1V=localWords(ny,sf)#得到词汇表、0类条件概率数组、1类条件概率数组topNY=[]; topSF=[]#构建列表存储最具表征性词汇元组for i in range(len(p0V)):#条件概率大于-6.0的为最具表征词汇元组if p0V[i] > -6.0 : topSF.append((vocabList[i],p0V[i]))if p1V[i] > -6.0 : 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**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**NY**NY**"for item in sortedNY:print item[0]

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

在Python提示符下输入下列代码:

>>> reload(bayes)
<module 'bayes' from 'E:\Python_Files\CodeofMe\Chapter4\bayes.py'>
>>> bayes.getTopWords(ny,sf)
the error rate is:  0.2
SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**SF**
min
grandfather
because
get
things
friends
says
body
massage
more
...
NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**NY**
friend
let
don
get
them
barefoot
send
did
ever
full
...

4-8 本章小结

对于分类而言,使用概率有时要比使用硬规则更有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。

可以通过特征之间的条件独立性假设,降低对数据量的需求。尽管条件独立性假设并不正确,但是朴素贝叶斯任然是一种有效的分类器。

实现朴素贝叶斯时考虑很多实际因素:下溢出可以通过对概率取对数来解决;词袋模型在解决文档分类问题上比词集模型有所提高;等其他方面的改进。

4-9 参考文献

《机器学习实战》

Python《机器学习实战》读书笔记(四)——朴素贝叶斯相关推荐

  1. 机器学习实战读书笔记(3)朴素贝叶斯

    贝叶斯定理 要理解贝叶斯推断,必须先理解贝叶斯定理.后者实际上就是计算"条件概率"的公式. 所谓"条件概率"(Conditional probability), ...

  2. 机器学习实战 - 读书笔记(04) - 朴素贝叶斯

    核心公式 - 贝叶斯准则 \[p(c|x) = \frac{p(x|c)p(c)}{p(x)}\] p(c|x) 是在x发生的情况下,c发生的概率. p(x|c) 是在c发生的情况下,x发生的概率. ...

  3. 机器学习实战(三)朴素贝叶斯NB(Naive Bayes)

    目录 0. 前言 1. 条件概率 2. 朴素贝叶斯(Naive Bayes) 3. 朴素贝叶斯应用于文本分类 4. 实战案例 4.1. 垃圾邮件分类案例 学习完机器学习实战的朴素贝叶斯,简单的做个笔记 ...

  4. 机器学习实战(三)朴素贝叶斯 (Peter Harrington著)

    知识储备: 一.概率论和数理统计 第一章 概率论的基本概念 1.必须要掌握的名词 (1) 样本空间 一般可以认为是整个样本 (2) 样本点 其中的一个样本,其中每个样本一般可以理解为特征向量 (3) ...

  5. 机器学习实战---读书笔记: 第11章 使用Apriori算法进行关联分析---2---从频繁项集中挖掘关联规则

    #!/usr/bin/env python # encoding: utf-8''' <<机器学习实战>> 读书笔记 第11章 使用Apriori算法进行关联分析---从频繁项 ...

  6. 机器学习算法(8)——朴素贝叶斯、最小风险贝叶斯决策

    最后以巨佬--"贝叶斯大爷"作为基本机器学习算法学习的压轴算法>>>>>>>>>>>>>膜拜!!!!! ...

  7. 数据分享|Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户...

    原文链接:http://tecdat.cn/?p=23518 项目背景:银行的主要盈利业务靠的是贷款,这些客户中的大多数是存款大小不等的责任客户(存款人).银行拥有不断增长的客户(点击文末" ...

  8. 机器学习实战读书笔记--朴素贝叶斯

    1.朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法, 最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model, ...

  9. 机器学习笔记 - 学习朴素贝叶斯概念及应用

    一.思想概述         贝叶斯分类算法是一大类分类算法的总称:贝叶斯分类算法以样本可能属于某类的概率来作为分类依据:朴素贝叶斯分类算法是贝叶斯分类算法中最简单的一种. 朴素贝叶斯分类器是一种用于 ...

  10. 机器学习笔记:朴素贝叶斯方法(Naive Bayes)原理和实现

    本文主要描述了朴素贝叶斯分类方法,包括模型导出和学习描述.实例部分总结了<machine learning in action>一书中展示的一个该方法用于句子感情色彩分类的程序.1 方法概 ...

最新文章

  1. 【Memcache】下载与安装
  2. Windows内核执行体对象管理器的操作过程与分析
  3. 每日一皮:循环没写好,导致后面数据覆盖了前面的数据...
  4. 30分钟全面解析-SQL事务+隔离级别+阻塞+死锁
  5. 什么是真正的APM?
  6. istio-0.8 服务超时配置
  7. @dalao help!!!
  8. java reader_Java Reader reset()方法与示例
  9. Versant 对象型数据库
  10. node ajax validator,使用validator.js对字符串数据进行验证
  11. Spark机器学习数据流水线
  12. 电脑开机不能进入系统--死机
  13. 配置MyBatis Plus 的乐观锁功能
  14. eclipse Build Automatically在哪里
  15. linux脚本量产,可玩性很高的量产键盘、套件 HEAVY SHELL KIRA 96简单上手
  16. 苹果xr配置_iPhone11和XR到底谁性价比高?一文看懂
  17. dwz交互式弹窗处理
  18. #pragma pack详解
  19. 吃货食堂-吃货们的天堂
  20. java使用poi操作excel,写入excel数据并下载

热门文章

  1. 配置阿里云 yum 源和 EPEL 源
  2. java-字符串,抽象类与抽象
  3. 文件压缩支付加密方式
  4. 使用selenium 刷票
  5. 第二章 Redis高级
  6. BitMap数据结构梳理总结及代码实现
  7. 农民丰收节交易会亮点-农业大健康·万祥军:谋定功能性农业
  8. The road to learning English-Words
  9. WPF窗体自适应电脑分辨率的问题
  10. 1003. 我要通过!