首页 » 机器学习实战 » 机器学习实战全文在线阅读

《机器学习实战》4.6 示例:使用朴素贝叶斯过滤垃圾邮件

关灯直达底部

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

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

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

下面首先给出将文本解析为词条的代码。然后将该代码和前面的分类代码集成为一个函数,该函数在测试分类器的同时会给出错误率。

4.6.1 准备数据:切分文本

前一节介绍了如何创建词向量,并基于这些词向量进行朴素贝叶斯分类的过程。前一节中的词向量是预先给定的,下面介绍如何从文本文档中构建自己的词列表。

对于一个文本字符串,可以使用Python的string.split方法将其切分。下面看看实际的运行效果。在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./']       

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

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

最后,我们发现句子中的第一个单词是大写的。如果目的是句子查找,那么这个特点会很有用。但这里的文本只看成词袋,所以我们希望所有词的形式都是统一的,不论它们出现在句子中间、结尾还是开头。

Python中有一些内嵌的方法可以将字符串全部转换成小写(.lower)或者大写.upper),借助这些方法可以达到目的。于是,可以进行如下处理:

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

现在来看数据集中一封完整的电子邮件的实际处理结果。该数据集放在email文件夹中,该文件夹又包含两个子文件夹,分别是spam与ham。

>>> emailText = open(/'email/ham/6.txt/').read>>> listOfTokens=regEx.split(emailText)    

文件夹ham下的6.txt文件非常长,这是某公司告知我他们不再进行某些支持的一封邮件。需要注意的是,由于是URL:answer.py?hl=en&answer=174623的一部分,因而会出现en和py这样的单词。当对URL进行切分时,会得到很多的词。我们是想去掉这些单词,因此在实现时会过滤掉长度小于3的字符串。本例使用一个通用的文本解析规则来实现这一点。在实际的解析程序中,要用更高级的过滤器来对诸如HTML和URI的对象进行处理。目前,一个URI最终会解析成词汇表中的单词,比如www.whitehouse.gov会被解析为三个单词。文本解析可能是一个相当复杂的过程。接下来将构建一个极其简单的函数,你可以根据情况自行修改。

4.6.2 测试算法:使用朴素贝叶斯进行交叉验证

下面将文本解析器集成到一个完整分类器中。打开文本编辑器,将下面程序清单中的代码添加到bayes.py文件中。

程序清单4-5 文件解析及完整的垃圾邮件测试函数

def textParse(bigString):    import re    listOfTokens = re.split(r/'W*/', bigString)    return [tok.lower for tok in listOfTokens if len(tok) > 2]def spamTest:    docList=; classList = ; fullText =    for i in range(1,26):        #❶ (以下七行)导入并解析文本文件        wordList = textParse(open(/'email/spam/%d.txt/' % i).read)        docList.append(wordList)        fullText.extend(wordList)        classList.append(1)        wordList = textParse(open(/'email/ham/%d.txt/' % i).read)        docList.append(wordList)        fullText.extend(wordList)        classList.append(0)    vocabList = createVocabList(docList)    trainingSet = range(50); testSet=    #❷(以下四行)随机构建训练集    for i in range(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(array(trainMat),array(trainClasses))    errorCount = 0    #❸(以下四行)对测试集分类    for docIndex in testSet:        wordVector = setOfWords2Vec(vocabList, docList[docIndex])        if classifyNB(array(wordVector),p0V,p1V,pSpam) !=    classList[docIndex]:           errorCount += 1    print /'the error rate is: /',float(errorCount)/len(testSet)    

第一个函数textParse接受一个大字符串并将其解析为字符串列表。该函数去掉少于两个字符的字符串,并将所有字符串转换为小写。你可以在函数中添加更多的解析操作,但是目前的实现对于我们的应用足够了。

第二个函数spamTest对贝叶斯垃圾邮件分类器进行自动化处理。导入文件夹spamham下的文本文件,并将它们解析为词列表❶。接下来构建一个测试集与一个训练集,两个集合中的邮件都是随机选出的。本例中共有50封电子邮件,并不是很多,其中的10封电子邮件被随机选择为测试集。分类器所需要的概率计算只利用训练集中的文档来完成。Python变量trainingSet是一个整数列表,其中的值从0到49。接下来,随机选择其中10个文件❷。选择出的数字所对应的文档被添加到测试集,同时也将其从训练集中剔除。这种随机选择数据的一部分作为训练集,而剩余部分作为测试集的过程称为留存交叉验证(hold-out cross validation)。假定现在只完成了一次迭代,那么为了更精确地估计分类器的错误率,就应该进行多次迭代后求出平均错误率。

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

下面对上述过程进行尝试。输入程序清单4-5的代码之后,在Python提示符下输入:

>>> bayes.spamTestthe error rate is: 0.0>>> bayes.spamTestclassification error [/'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/']the error rate is: 0.1   

函数spamTest会输出在10封随机选择的电子邮件上的分类错误率。既然这些电子邮件是随机选择的,所以每次的输出结果可能有些差别。如果发现错误的话,函数会输出错分文档的词表,这样就可以了解到底是哪篇文档发生了错误。如果想要更好地估计错误率,那么就应该将上述过程重复多次,比如说10次,然后求平均值。我这么做了一下,获得的平均错误率为6%。

这里一直出现的错误是将垃圾邮件误判为正常邮件。相比之下,将垃圾邮件误判为正常邮件要比将正常邮件归到垃圾邮件好。为避免错误,有多种方式可以用来修正分类器,这些将在第7章中进行讨论。

目前我们已经使用朴素贝叶斯来对文档进行分类,接下来将介绍它的另一个应用。下一个例子还会给出如何解释朴素贝叶斯分类器训练所得到的知识。