在上一篇文章《使用朴素贝叶斯算法对文档分类详解》中,我们实现了用朴素贝叶斯算法对简单文档的分类,今天我们将利用此分类器来过滤垃圾邮件。
之前算法中输入的文档格式为单词向量,例如['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],而实际情况中通常要处理的是文本(例如邮件),那么就要先将文本转换为词向量,在bayes.py中加入代码:
# 文件解析为向量
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
上面3行代码包含了很多内容,它的输入参数是字符串,返回字符串列表。
现在有50封电子邮件,垃圾邮件和正常邮件各25个,随机选取10个作为测试集,剩余的40个作为训练集。测试过程可以分解为3部分:
# 垃圾邮件测试函数
def spamTest():
docList = []
classList = []
fileList = []
for i in range(1, 26):
wordList = textParse(
open(r'email\spam\%d.txt' % i).read()) # 将垃圾邮件内容转换为词向量
docList.append(wordList) # 添加到邮件列表
classList.append(1) # 添加到类别列表
fileList.append(r'email\spam\%d.txt' % i)
wordList = textParse(
open(r'email\ham\%d.txt' % i).read()) # 将正常邮件内容转换为词向量
docList.append(wordList)
classList.append(0)
fileList.append(r'email\ham\%d.txt' % i)
vocabList = createVocabList(docList) # 生成词条列表
trainingSet = list(range(50)) # 创建一个长度为50的训练集索引列表
testSet = []
for i in range(10): # 随机选择10个作为测试样本,并将其从训练集中删除
randomIndex = int(random.uniform(0, len(trainingSet)))
testSet.append(trainingSet[randomIndex])
del (trainingSet[randomIndex])
trainMat = []
trainClasses = []
for docIndex in trainingSet: # 生成训练样本的矩阵
trainMat.append(bagOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0v, p1v, pSpam = trainNB0(array(trainMat), array(trainClasses)) # 构建分类器
errorCount = 0
for docIndex in testSet: # 对测试集分类
wordVector = bagOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(array(wordVector), p0v, p1v, pSpam) != classList[
docIndex]: # 如果分类错误,打印错误信息
errorCount += 1
print('classification error', fileList[docIndex])
print('the error rate is: ', float(errorCount) / len(testSet)) # 输出错误率
下面测试一下分类的效果,因为测试集是随机选取的,每次测试的结果可能不同,可以多次运行测试函数求平均错误率,以此来评估算法。我运行了10次,平均错误率是4%。
上面处理的邮件是全英文的,可是中文邮件怎么办呢?如果按上述方法按标点符号来切分的话,中文的一整句话会被当做一个词条,这显然不行,好在Python有个强大的中文处理模块 jieba(结巴),它不仅能对中文文本切词,如果碰到英文单词,也会以英文的默认形式切分。
# 文件解析函数,可处理中文和英文
def textParse1(bigString):
import re
import jieba
listOfTokens = jieba.lcut(bigString) # 使用jieba切分文本
newList = [re.sub(r'\W*', '', s) for s in listOfTokens] # 去掉标点符号
return [tok.lower() for tok in newList if len(tok) > 0] # 删除长度为0的空值
这里需要注意的是,如果邮件内容包含中文,是否删除短字符串就要谨慎了,因为中文的一两个字对于分类也是有意义的。我们来试一下效果:
不错,现在对中文邮件也可以轻松过滤了。
说明:文中的测试样本和部分代码来自《机器学习实战》,但书中的代码适合Python 2.7版本, 我将代码做了调整使其能够在Python 3.6下运行,并添加了详细的中文注释。
如果需要测试样本文件,可到我的资源下载:https://download.csdn.net/download/leaf_zizi/10716544。
jieba的下载地址和安装方法:https://pypi.org/project/jieba/