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

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

  • 注:代码和数据已上传:https://download.csdn.net/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    #在词汇表中出现的话则该词对应的位置标记为1

        else:
            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.0

    for 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 1
    else:
        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 += 1
        print("当前处理到第%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 = 0

    for test in testList:   #分类测试数据,将分类的标签放入resultLabel
        doc = np.array(Words_to_Vec(VocabList, test))

        if classifyNB(doc, p0V, p1V, pAb):
            resultLabel.append(1)
        else:
            resultLabel.append(0)

        cnt += 1
        print("当前处理到第%s组测试数据." % cnt)

    cc = 0  #分类正确的个数
    for i in range(len(testLabel)):     #对比分类标签和真实标签
        if testLabel[i] == resultLabel[i]:
            cc += 1

    print('预测准确率:' + str(100 * cc / float(len(testLabel))) + '%')   #计算准确率

4、实验总结

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

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

    • 优点:

      • 有稳定的分类效率

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

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

    • 缺点:

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

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

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

三、完整代码

#!/usr/bin/python
# -*- coding utf-8 -*-
# Project: NB
# Author: jiangnan 
# Mail: jiangnanmax@gmail.com
# Date: 2018/9/26

import numpy as np

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, classVecx


def 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    #在词汇表中出现的话则该词对应的位置标记为1

        else:
            print("The word %s is not in the VocabList!" % word)

    return returnVec


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.0

    for 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


def 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 1
    else:
        return 0

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 += 1
        print("当前处理到第%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 = 0

    for test in testList:   #分类测试数据,将分类的标签放入resultLabel
        doc = np.array(Words_to_Vec(VocabList, test))

        if classifyNB(doc, p0V, p1V, pAb):
            resultLabel.append(1)
        else:
            resultLabel.append(0)

        cnt += 1
        print("当前处理到第%s组测试数据." % cnt)

    cc = 0  #分类正确的个数
    for i in range(len(testLabel)):     #对比分类标签和真实标签
        if testLabel[i] == resultLabel[i]:
            cc += 1

    print('预测准确率:' + str(100 * cc / float(len(testLabel))) + '%')   #计算准确率


if __name__ == '__main__':
    main()
  • 21
    点赞
  • 136
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
中文邮件分类朴素叶斯算法实现步骤如下: 1. 准备数据集:将邮件分为训练集和测试集。训练集用于训练模型,测试集用于测试模型准确率。 2. 分词:对邮件内容进行分词处理,将一封邮件转化为一个词汇列表。 3. 特征提取:将每个邮件转化为一个向量,向量的每个元素对应一个特征。 4. 训练模型:使用训练集训练朴素叶斯模型,计算每个类别的先验概率和条件概率。 5. 测试模型:使用测试集测试模型准确率。 6. 应用模型:使用训练好的模型对新邮件进行分类。 代码实现如下: ```python import jieba import os import random import numpy as np # 分词函数 def cut_words(file_path): with open(file_path, 'r', encoding='utf-8') as f: text = f.read() words = jieba.cut(text) return list(words) # 获取停用词列表 def get_stopwords(): with open('stopwords.txt', 'r', encoding='utf-8') as f: stopwords = f.readlines() stopwords = [word.strip() for word in stopwords] return stopwords # 获取所有文件路径 def get_file_path(root_path): file_path_list = [] for root, dirs, files in os.walk(root_path): for file in files: file_path_list.append(os.path.join(root, file)) return file_path_list # 计算先验概率和条件概率 def train_NB(train_data, train_label): # 先验概率 prior_prob = {} total_num = len(train_label) label_set = set(train_label) for label in label_set: prior_prob[label] = train_label.count(label) / total_num # 条件概率 word_prob = {} words_set = set([word for text in train_data for word in text]) for label in label_set: word_prob[label] = {} label_index = [i for i in range(len(train_label)) if train_label[i] == label] label_text = [train_data[i] for i in label_index] total_words_num = sum([len(text) for text in label_text]) for word in words_set: word_prob[label][word] = (sum([text.count(word) for text in label_text]) + 1) / (total_words_num + len(words_set)) return prior_prob, word_prob # 预测函数 def predict_NB(test_data, prior_prob, word_prob): predict_label = [] label_set = list(prior_prob.keys()) for text in test_data: max_prob = -np.inf for label in label_set: prob = np.log(prior_prob[label]) for word in text: if word in word_prob[label]: prob += np.log(word_prob[label][word]) if prob > max_prob: max_prob = prob predict = label predict_label.append(predict) return predict_label # 交叉验证 def cross_validation(data, label, k): data_num = len(label) index = list(range(data_num)) random.shuffle(index) fold_size = data_num // k accuracy_list = [] for i in range(k): start = i * fold_size end = min((i + 1) * fold_size, data_num) test_index = index[start:end] train_index = list(set(index) - set(test_index)) train_data = [data[i] for i in train_index] train_label = [label[i] for i in train_index] test_data = [data[i] for i in test_index] test_label = [label[i] for i in test_index] prior_prob, word_prob = train_NB(train_data, train_label) predict_label = predict_NB(test_data, prior_prob, word_prob) accuracy = sum([1 for i in range(len(test_label)) if test_label[i] == predict_label[i]]) / len(test_label) accuracy_list.append(accuracy) return sum(accuracy_list) / k if __name__ == '__main__': # 获取停用词列表 stopwords = get_stopwords() # 获取文件路径 file_path_list = get_file_path('data') # 获取所有文本和对应标签 text_list = [] label_list = [] for file_path in file_path_list: label = file_path.split('\\')[-2] label_list.append(label) words = cut_words(file_path) words = [word for word in words if word not in stopwords] text_list.append(words) # 执行交叉验证 accuracy = cross_validation(text_list, label_list, 5) print('模型准确率:%.2f%%' % (accuracy * 100)) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JiangNanMax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值