原理
假如邮箱中有n个单词,
如果returnVec[i]=0代表这个单词在这封邮件中不出现,
returnVec[i]=1代表这个单词在邮件中出现了。
设训练集中每个邮件都有标记为是垃圾邮件和不是垃圾邮件,是垃圾邮件的分类为1,不是垃圾邮件的分类为0。
算法原理:
提取邮件内单词,改写为小写单词输入字典,过滤长度不大于2的单词。
利用词汇表计算出单词属于正常词汇的概率p0V=(正常邮件中该单词出现次数)/(正常邮件数量)。
辱骂性词汇的概率p1V=(垃圾邮件中该单词出现次数)/(垃圾邮件数量)。
然后比较p0V和p1V的概率大小,如果p0V大于p1V就认为他属于正常邮件,如果p1V大于p0V就认为他属于垃圾邮件。
pb为(对应类别的邮件数)/(所有邮件总数)。
文章中的改进:设定初始一行的单词数计数为2,是为了防止下溢,避免因为某个特性下概率为0。计算概率使用了log函数简化乘除法。
对原文章中的代码进行些许改进,添加了中文注释,加入了主函数,能够直接运行出结果。因为能力有限,有部分可能解释不清,希望大牛批评指正!
代码如下:
# -*- coding: utf-8 -*-
from numpy import *
def loadDataSet():
#记录数据集,使用python的list,一共是6条,每条又包含了若干条
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']]
#分类向量,1 代表侮辱性文字,0代表正常言论。
classVec = [0,1,0,1,0,0]
#返回的第一个变量是进行词条切分后的文档集合,第二个变量是一个类别标签的集合
return postingList,classVec
#创建一个包含在所有文档中出现的不重复词的列表
def createVocabList(dataSet):
#创建一个空集
vocabSet = set([])
#循环实现如果这个词出现在数据集中,就创建两个集合的并集。重复的词只会被加入进去一次。也就实现了只存储不重复词的功能
for document in dataSet:
vocabSet = vocabSet | set(document)#创建两个集合的并集
#返回这个词汇列表
return list(vocabSet)
#这个函数实现的功能:将两个词汇表进行对比,如果输入的数据集在词汇表中,就让向量值为1,否则输出该词不在文档中。
#输入的参数为词汇表及某个文档,输出的是文档向量。
def wordstoVec(vocabList, inputSet):
#创建一个其中所含元素都为0的向量,向量长度与词汇表长度一致
returnVec = [0]*len(vocabList)
#对输入集中每一个单词执行,如果单词在词汇表中就使这个词的索引位置相应的的文档向量值为1,否则输出这个词不是词汇表中的
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else: print "这个单词: %s 不是我词汇表里的!" % word
#返回这个转化后的向量
return returnVec
#输入的参数为文档矩阵trainMatrix,以及由每篇文档类别标签所构成的向量trainCategory。
#首先计算文档属于侮辱性文档的概率,即P(1)。因为是二类分类问题,所以可以通过1-P(1)得到p(0)。
def trainNB0(trainMatrix,trainCategory):
#训练文本单词数为训练矩阵的长度。
TrainDocsNum = len(trainMatrix)
#单词数为训练矩阵第一行的长度,这只是为了初始化
Wordsnum = len(trainMatrix[0])
#辱骂性词汇出现的概率为所有训练类别相加除以训练文本单词总数,因为辱骂性词汇的类别都是1.
pbad = sum(trainCategory)/float(TrainDocsNum)
#计算p(wi|c1)和p(wi|c0),p(w|c)指在分类c下,词语w出现的样本数与分类c所有样本中所有词数
#让类别为0的单词出现的次数设为全为1的向量,向量的高度与单词数相同。
p0 = ones(Wordsnum)
#让类别为1的单词出现的次数设为全为1的向量,向量的高度与单词数相同。
p1 = ones(Wordsnum)
#设定初始一行的单词数计数为2,这也是一处改进,为了防止下溢,避免因为某个特性下概率为0 就导致整体概率为0。把计数从1改为2
p0Denom = 2.0; p1Denom = 2.0
#让所有的向量对应元素相加,一旦某个词在文档出现,对该词对应的个数就加1。而且总词数也加1。
#range代表从0到TrainDocsNum的整数列表
for i in range(TrainDocsNum):
#假如训练种类对应的值为1就使类别1单词出现的次数加1
if trainCategory[i] == 1:
p1 += trainMatrix[i]
#假如训练种类对应的值为0就使类别0单词出现的次数加1
else:
p0 += trainMatrix[i]
p1Denom = sum(trainCategory)
p0Denom = len(trainCategory) - p1Denom
#对每个元素出现的次数除以该类别中对应某行的总词数,概率进行乘法过后数值会比较小,取log方便比较
p1Vect = log(p1/p1Denom)
p0Vect = log(p0/p0Denom)
return p0Vect,p1Vect,pbad
#朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#元素相乘,对应元素相乘,然后将词汇表中所有词的对应值相加,然后将该值加到类别的对数概率上。
p1 = sum(vec2Classify*p1Vec) + log(pClass1)
p0 = sum(vec2Classify*p0Vec) + log(1.0 - pClass1)
#比较类别的概率返回大概率对应的类别标签。
if p1>p0:
return 1
else:
return 0
#便利函数,封装了所有操作。
def testingNB():
#加载数据集
listOPosts,listClasses = loadDataSet()
#创建词汇表
myVocabList = createVocabList(listOPosts)
#创建一个空集
trainMat = []
#循环所有数据集中词汇,转化为词汇向量
for postingDoc in listOPosts:
trainMat.append(wordstoVec(myVocabList,postingDoc))
#调用函数计算0类词出现概率和1类词出现的概率以及辱骂性词汇出现的概率
p0V,p1V,pb = trainNB0(array(trainMat),array(listClasses))
testEntry = ['I','love','you']
thisDoc = array(wordstoVec(myVocabList,testEntry))
print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'类\n','是正常词的概率为:\n',p0V,'\n是辱骂性词的概率为:\n',p1V,'\n辱骂性词占得比例:',pb
testEntry = ['stupid','garbage']
thisDoc = array(wordstoVec(myVocabList,testEntry))
print testEntry, '被分到: ', classifyNB(thisDoc,p0V,p1V,pb),'类\n','是正常词的概率为:\n',p0V,'\n是辱骂性词的概率为:\n',p1V,'\n辱骂性词占得比例:',pb
#应用部分:使用朴素贝叶斯过滤垃圾邮件,使用到交叉验证。
#数据准备会有垃圾邮件文件夹下面全部是标记的垃圾邮件,正常邮件文件夹下是正常邮件。
#textParse函数接受一个字符串并将其解析为字符串列表,把大写字符转化成小写,并且只存储大于两个字母的词。
#spamTest函数在50封邮件中选取10篇邮件随机选择为测试集交叉验证。
def textParse(bigString):
import re
listOfTokens = re.split(r'\W*',bigString)
return[tok.lower() for tok in listOfTokens if len(tok) > 2]
#对贝叶斯垃圾邮件分类器进行自动化处理。导入文件夹spam和ham下的文本文件,并将它们解析为词列表。
def spamTest():
docList = []; classList = []; fullText = []
#导入并解析文本
for i in range(1,26):
wordList = textParse(open('email/垃圾邮件/%d.txt' % i).read())
docList.append(wordList)
fullText.extend(wordList)
classList.append(1)
wordList = textParse(open('email/正常邮件/%d.txt' % i).read())
docList.append(wordList)
#extend()方法只接受一个列表作为参数,并将该参数的每个元素都添加到原有的列表中。
fullText.extend(wordList)
#append() 方法向列表的尾部添加一个新的元素。只接受一个参数。
classList.append(0)
vocabList = createVocabList(docList)
#随机构建训练集,进行留存交叉验证
trainingSet = range(50); testSet = []
for i in range(15):
#uniform() 方法将随机生成下一个实数,它在[x,y]范围内
randIndex = int(random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat = []; trainClasses = []
for docIndex in trainingSet:
trainMat.append(wordstoVec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V,p1V,pSpam = trainNB0(array(trainMat),array(trainClasses))
errorCount = 0
#对测试集分类
for docIndex in testSet:
wordVector = wordstoVec(vocabList, docList[docIndex])
if classifyNB(array(wordVector),p0V,p1V,pSpam)!= classList[docIndex]:
errorCount += 1
print "分类错误了!分类的错误词是: \n",docList[docIndex],'分类错误的数目有: ',errorCount
print '分类错误率为:',float(errorCount)/len(testSet)
if __name__ == "__main__":
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
print ' ————————————————————我的词汇列表:——————————————————\n',myVocabList
trainMat = []
for postinDoc in listOPosts:
trainMat.append(wordstoVec(myVocabList, postinDoc))
p0V,p1V,pAb = trainNB0(trainMat, listClasses)
print ' ——————————————————————测试案例———————————————————————————\n'
testingNB()
print ' ———————————————第1次垃圾邮件分类自动化处理结果——————————————————\n'
spamTest()
print ' ———————————————第2次垃圾邮件分类自动化处理结果——————————————————\n'
spamTest()
print ' ———————————————第3次垃圾邮件分类自动化处理结果——————————————————\n'
spamTest()
运行时,注意python的版本,要注意修改代码。要安装numpy才能正常运行,案例中邮箱,可以去机器学习实战官网下载,也可以去这个网址下载,http://www.robot-ai.org/forum.php?mod=viewthread&tid=986&extra=注意改名。以下是运行结果: