700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 朴素贝叶斯算法:实现邮件分类

朴素贝叶斯算法:实现邮件分类

时间:2022-03-02 17:27:19

相关推荐

朴素贝叶斯算法:实现邮件分类

朴素贝叶斯算法:实现邮件分类

注:代码和数据已上传:/download/j__max/10705454

一、实验准备

1、实验内容和目的

使用5000封邮件作为训练集,训练朴素贝叶斯分类器,然后使用该分类器对1000封邮件进行分类,给出准确率结果

其中训练集为文件spam_train.txt,测试集为文件spam_test.txt。它们的存储形式为:每封邮件以一个标签(0或1)开头,然后以一个换行符来标示一封邮件的结尾

这里先给出我最后测试的准确率:98.3% ,接下来对我的实现过程进行详细说明

2、实验原理

朴素贝叶斯算法是有监督的学习算法,可以用来解决分类问题。朴素贝叶斯基于 贝叶斯定理 和 特征条件独立假设,它假设每个特征条件之间是独立的, 比如一个单词出现的概率和其它相邻单词没有关系,因此我们称之为“天真”的贝叶斯算法
2.1 基于贝叶斯决策理论的分类方法

朴素贝叶斯是贝叶斯决策理论的一部分,要完全掌握朴素贝叶斯的话有必要了解一下贝叶斯决策理论

假设有一个数据集,由两类数据组成(体现为圆点和三角形),分布情况如下图所示:

我们现在用p0(x,y)表示数据点(x,y)属于类别0(图中用圆点表示的类别)的概率,用p1(x,y)表示数据点(x,y)属于类别1(图中用三角形表示的类别)的概率,那么对于一个新数据点(x,y),可以使用下面的规则来判断它的类别:

如果p0(x,y) > p1(x,y),那么数据点(x,y)的类别为0

如果p1(x,y) > p0(x,y),那么数据点(x,y)的类别为1

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

2.2 条件概率

在2.1中提到了概率p0和p1,那么如何计算p0和p1?这就需要理解条件概率的计算。相信只要上过概率论,肯定对符号p(x,y | c)很熟悉,下面通过一个例子来复习一下条件概率

假设现在有一个装了7块石头的罐子,其中3块灰色的,4块黑色的(如下图所示)。如果从罐中随机取出一块石头,那么是灰色石头的可能性是多少?显然是3/7。我们使用P(gray)来表示取到灰色石头的概率,其概率值可以通过灰色石头数量除以总的石头数量来得到

那如果这7块石头如下图所示放在两个桶中,那么上述概率应该怎么计算?

要计算P(gray)或者P(black),事先得知道石头所在桶的信息会不会改变结果?你有可能已经想到计算从B桶中取到灰色石头概率的办法,这就是所谓的条件概率。假定计算的是从B桶中取到灰色石头的概率,这个概率可以记作P(gray | bucketB),我们称之为“在已知石头出自B桶的条件下,取出灰色石头的概率”。不难算出,P(gray | bucketA)=1/2,P(gray | bucketB)=1/3。条件概率的计算公式如下所示:

P(gray | bucketB) = P(gray and bucketB) / P(bucketB)

2.3 使用条件概率来分类

2.1节提到贝叶斯决策理论要求计算两个概率p0(x,y)和p1(x,y):

如果p0(x,y) > p1(x,y),那么数据点(x,y)的类别为0

如果p1(x,y) > p0(x,y),那么数据点(x,y)的类别为1

但这两个准则并不是贝叶斯决策理论的所有内容。使用p0()和p1()只是为了尽可能简化描述,而真正需要计算和比较的是p(c0 | x,y)和p(c1 | x,y)。这些符号所代表的具体意义是:给定某个由x、y表示的数据点,那么该数据点来自类别c0的概率是多少?数据点来自类别c1的概率又是多少?使用这些定义,可以定义贝叶斯准则为:

如果P(c0 | x,y) > P(c1 | x,y),那么属于类别0

如果P(c1 | x,y) > P(c0 | x,y),那么属于类别1

基于贝叶斯准则,可以训练出一个功能强大的贝叶斯分类器,接下来对我构造的分类器进行详细说明

二、进行实验

1、算法思路

其实在实验原理部分,已经是详细的算法思路了,因为朴素贝叶斯的核心思想,就是实验原理中说明的三个部分。简单来说,整体的思路过程就是:对于给定的训练集,首先基于特征条件独立假设学习输入/输出的联合概率分布。然后基于此模型,对于给定的输入x,利用贝叶斯定理求出后验概率最大的输出y

2、算法步骤

(1) 对训练数据进行处理,提出每一个训练数据和其对应的标签

(2) 根据训练数据,生成一个词汇表

(3) 向量化每个训练样本,然后结合生成的词汇表,训练朴素贝叶斯分类器

(4) 对测试数据进行处理,提出每一个测试数据和其对应的标签

(5) 向量化每个测试数据,使用朴素贝叶斯分类器对其进行分类

(6) 即通过比较概率p0和p1的大小,判断其属于哪个类别

3、代码实现

注:代码中的所有函数功能已注释在函数头部,具体的代码作用也在代码后面进行了注释说明

(1) 处理数据文件。这里因为训练数据和测试数据的存放格式一样的,因此能够使用同一个功能函数loadFile进行处理。对邮件内容进行切分,取出每封邮件对应的类别标签,生成每封邮件对应的词条向量,返回这两部分

def loadFile(filename):"""函数说明:加载数据文件:param filename:文件名:return:contentList - 切分邮件内容得到的词条classVec - 类别标签向量"""file = open(filename)contentList = []classVec = []contents = file.readlines()for line in contents:content = line.strip('\n').split(' ') #以空格为分割符,切分邮件的内容,得到该邮件对应的词条classVec.append(int(content[0])) #取出邮件的类别标签del(content[0])#删掉词条中的类别标签contentList.append(content)return contentList, classVec

(2) 根据处理训练数据得到的词条,汇总生成一个词汇表。其中使用set取并集的特性,去除重复的词汇

def createVocabList(dataSet):"""函数说明:根据训练数据,生成一个词汇表:param dataSet:切分所有邮件得到的词条:return:list(vocabSet) - 使用训练数据生成的不重复的词汇表"""vocabList = set([]) #创建一个空集合for content in dataSet:vocabList = vocabList | set(content) #通过取并集的方式去重,扩充词汇表return list(vocabList) #以list的形式返回词汇表

(3) 根据vocabList词汇表,将每个wordsSet词条向量化,向量的每个值为1或0,分别表示该词有或者没有在词汇表中出现

def Words_to_Vec(vocabList, wordsSet):"""函数说明:根据vocabList词汇表,将每个wordsSet词条向量化,向量的每个值为1或0,分别表示该词有或者没有在词汇表中出现:param vocabList:词汇表:param inputSet:切分每封邮件得到的词条:return:词条向量"""returnVec = [0] * len(vocabList)for word in wordsSet: #判断每个词是否在词汇表中出现if word in vocabList:returnVec[vocabList.index(word)] = 1 #在词汇表中出现的话则该词对应的位置标记为1else:print("The word %s is not in the VocabList!" % word)return returnVec

(4) 向量化每个训练样本,然后结合生成的词汇表,训练朴素贝叶斯分类器。其中使用了拉普拉斯平滑的方法

def trainNB(trainMat, trainLabel):"""函数说明:朴素贝叶斯分类训练函数:param trainMat:训练文档,即Words_to_Vec函数返回的词向量构成的矩阵:param trainLabel:训练数据的类别标签,即loadFile函数返回的classVec:return:p0Vec - 侮辱类的条件概率数组p1Vec - 非侮辱类的条件概率数组pNotAbusive - 文档属于侮辱类的概率"""numTraindocs = len(trainMat) #训练集的数量numWords = len(trainMat[0])#每个词条向量的长度pNotAbusive = sum(trainLabel) / float(numTraindocs) #文档属于非侮辱类的概率p0Num = np.ones(numWords) #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑方法p1Num = np.ones(numWords)p0Denom = 2.0##分母初始化为2,拉普拉斯平滑方法p1Denom = 2.0for i in range(numTraindocs):if trainLabel[i] == 1:p1Num += trainMat[i] #统计属于非侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Denom += sum(trainMat[i])else:p0Num += trainMat[i] #统计属于侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Denom += sum(trainMat[i])p1Vec = np.log(p1Num / p1Denom) #取对数p0Vec = np.log(p0Num / p0Denom)return p0Vec, p1Vec, pNotAbusive

(5) 向量化每个测试数据,使用朴素贝叶斯分类器对其进行分类,通过比较概率p0和p1的大小,判断其属于哪个类别

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass0):"""函数说明:朴素贝叶斯分类函数:param vec2Classify:待分类的词条向量:param p0Vec:侮辱类的条件概率数组:param p1Vec:非侮辱类的条件概率数组:param pClass0:文档属于侮辱类的概率:return:0 - 文档属于侮辱类1 - 文档属于分侮辱类"""p1 = sum(vec2Classify * p1Vec) + np.log(pClass0) p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass0)if p1 > p0:return 1else:return 0

(6) 主函数,综合调用上述的功能函数,加载处理数据,训练分类器,然后对测试集邮件进行分类,最后计算分类的准确率

def main():trainList, trainLabel = loadFile('spam_train.txt') #处理训练数据VocabList = createVocabList(trainList) #生成词汇表'''#由于数据集较大,生成的词汇表内容较多,因此我把生成的词汇表写出到txt文件,后续可以直接调用file = open('VocabList.txt', 'w')file.write(str(VocabList))file.close()'''trainMat = []cnt = 0#用来标记处理到第几组数据,在处理完每组数据后就会加1输出for train in trainList:#生成训练集矩阵trainMat.append(Words_to_Vec(VocabList, train))cnt += 1print("当前处理到第%s组训练数据." % cnt)'''#由于数据集较大,生成训练集矩阵较慢,内容也较多,因此我把生成的矩阵写出到txt文件,后续可以直接调用file = open('trainMat.txt', 'w')file.write(str(trainMat))file.close()'''p0V, p1V, pAb = trainNB(np.array(trainMat), np.array(trainLabel)) #使用训练集矩阵训练分类器testList, testLabel = loadFile('spam_test.txt')#处理测试数据resultLabel = []cnt = 0for test in testList: #分类测试数据,将分类的标签放入resultLabeldoc = np.array(Words_to_Vec(VocabList, test))if classifyNB(doc, p0V, p1V, pAb):resultLabel.append(1)else:resultLabel.append(0)cnt += 1print("当前处理到第%s组测试数据." % cnt)cc = 0 #分类正确的个数for i in range(len(testLabel)):#对比分类标签和真实标签if testLabel[i] == resultLabel[i]:cc += 1print('预测准确率:' + str(100 * cc / float(len(testLabel))) + '%') #计算准确率

4、实验总结

测试最后得到的准确率为98.3%,自认为效果不错。有一处不足就是生成训练集矩阵trainMat要花较多时间,有考虑结合多线程或者多进程来加速这一过程

完成这次作业,个人总结了一点朴素贝叶斯算法的优缺点:

优点:

有稳定的分类效率

对小规模的数据表现很好,能个处理多分类任务,适合增量式训练

对缺失数据不太敏感,算法也比较简单,常用于文本分类

缺点:

理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好

需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳

由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率

三、完整代码

#!/usr/bin/python# -*- coding utf-8 -*-# Project: NB# Author: jiangnan # Mail: jiangnanmax@# Date: /9/26import numpy as npdef loadFile(filename):"""函数说明:加载数据文件:param filename:文件名:return:contentList - 切分邮件内容得到的词条classVec - 类别标签向量"""file = open(filename)contentList = []classVec = []contents = file.readlines()for line in contents:content = line.strip('\n').split(' ') #以空格为分割符,切分邮件的内容,得到该邮件对应的词条classVec.append(int(content[0])) #取出邮件的类别标签del(content[0])#删掉词条中的类别标签contentList.append(content)return contentList, classVecxdef createVocabList(dataSet):"""函数说明:根据训练数据,生成一个词汇表:param dataSet:切分所有邮件得到的词条:return:list(vocabSet) - 使用训练数据生成的不重复的词汇表"""vocabList = set([]) #创建一个空集合for content in dataSet:vocabList = vocabList | set(content) #通过取并集的方式去重,扩充词汇表return list(vocabList) #以list的形式返回词汇表def Words_to_Vec(vocabList, wordsSet):"""函数说明:根据vocabList词汇表,将每个wordsSet词条向量化,向量的每个值为1或0,分别表示该词有或者没有在词汇表中出现:param vocabList:词汇表:param inputSet:切分每封邮件得到的词条:return:词条向量"""returnVec = [0] * len(vocabList)for word in wordsSet: #判断每个词是否在词汇表中出现if word in vocabList:returnVec[vocabList.index(word)] = 1 #在词汇表中出现的话则该词对应的位置标记为1else:print("The word %s is not in the VocabList!" % word)return returnVecdef trainNB(trainMat, trainLabel):"""函数说明:朴素贝叶斯分类训练函数:param trainMat:训练文档,即Words_to_Vec函数返回的词向量构成的矩阵:param trainLabel:训练数据的类别标签,即loadFile函数返回的classVec:return:p0Vec - 侮辱类的条件概率数组p1Vec - 非侮辱类的条件概率数组pNotAbusive - 文档属于侮辱类的概率"""numTraindocs = len(trainMat) #训练集的数量numWords = len(trainMat[0])#每个词条向量的长度pNotAbusive = sum(trainLabel) / float(numTraindocs) #文档属于非侮辱类的概率p0Num = np.ones(numWords) #创建numpy.ones数组,词条出现数初始化为1,拉普拉斯平滑方法p1Num = np.ones(numWords)p0Denom = 2.0##分母初始化为2,拉普拉斯平滑方法p1Denom = 2.0for i in range(numTraindocs):if trainLabel[i] == 1:p1Num += trainMat[i] #统计属于非侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···p1Denom += sum(trainMat[i])else:p0Num += trainMat[i] #统计属于侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···p0Denom += sum(trainMat[i])p1Vec = np.log(p1Num / p1Denom) #取对数p0Vec = np.log(p0Num / p0Denom)return p0Vec, p1Vec, pNotAbusivedef classifyNB(vec2Classify, p0Vec, p1Vec, pClass0):"""函数说明:朴素贝叶斯分类函数:param vec2Classify:待分类的词条向量:param p0Vec:侮辱类的条件概率数组:param p1Vec:非侮辱类的条件概率数组:param pClass0:文档属于侮辱类的概率:return:0 - 文档属于侮辱类1 - 文档属于分侮辱类"""p1 = sum(vec2Classify * p1Vec) + np.log(pClass0) # 对应元素相乘。logA * B = logA + logB,所以这里加上log(pClass1)p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass0)if p1 > p0:return 1else:return 0def main():trainList, trainLabel = loadFile('spam_train.txt') #处理训练数据VocabList = createVocabList(trainList) #生成词汇表'''#由于数据集较大,生成的词汇表内容较多,因此我把生成的词汇表写出到txt文件,后续可以直接调用file = open('VocabList.txt', 'w')file.write(str(VocabList))file.close()'''trainMat = []cnt = 0#用来标记处理到第几组数据,在处理完每组数据后就会加1输出for train in trainList:#生成训练集矩阵trainMat.append(Words_to_Vec(VocabList, train))cnt += 1print("当前处理到第%s组训练数据." % cnt)'''#由于数据集较大,生成训练集矩阵较慢,内容也较多,因此我把生成的矩阵写出到txt文件,后续可以直接调用file = open('trainMat.txt', 'w')file.write(str(trainMat))file.close()'''p0V, p1V, pAb = trainNB(np.array(trainMat), np.array(trainLabel)) #使用训练集矩阵训练分类器testList, testLabel = loadFile('spam_test.txt')#处理测试数据resultLabel = []cnt = 0for test in testList: #分类测试数据,将分类的标签放入resultLabeldoc = np.array(Words_to_Vec(VocabList, test))if classifyNB(doc, p0V, p1V, pAb):resultLabel.append(1)else:resultLabel.append(0)cnt += 1print("当前处理到第%s组测试数据." % cnt)cc = 0 #分类正确的个数for i in range(len(testLabel)):#对比分类标签和真实标签if testLabel[i] == resultLabel[i]:cc += 1print('预测准确率:' + str(100 * cc / float(len(testLabel))) + '%') #计算准确率if __name__ == '__main__':main()

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。