基于朴素贝叶斯的垃圾邮件分类系统项目开发教程

news2024/9/21 21:31:21

项目资源下载

  1. 基于朴素贝叶斯的垃圾邮件分类系统源码

项目简介

  本项目基于朴素贝叶斯算法来解决垃圾邮件分类问题,并使用混淆矩阵进行了验证,得到了非常好的准确率和召回率(96%和97%)。此外还开发了一个可视化的垃圾邮件分类系统界面,使用PyQT进行界面设计。


项目结构

  • data:数据集
    • trec06c:中文邮件数据集
      • data:中文邮件数据集
      • delay:邮件文本索引和标签
      • full:邮件文本索引和标签
  • model:训练好的模型
  • cut_word_lists.npy:分词结果
  • main.py:垃圾邮件统计分类全部代码
  • stopwords.txt:停用词
  • VisualizationInterface.py:垃圾邮件可视化分类全部代码

项目开发软件环境

  • Windows 11
  • Python 3.7
  • PyCharm 2022.1

项目开发硬件环境

  • CPU:Intel® Core™ i7-8750H CPU @ 2.20GHz 2.20 GHz
  • RAM:24GB
  • GPU:NVIDIA GeForce GTX 1060

文章目录

  • 项目资源下载
  • 项目简介
  • 项目结构
  • 项目开发软件环境
  • 项目开发硬件环境
  • 前言
  • 零、项目演示
  • 一、邮件数据集
  • 二、算法原理
    • 2.1 条件概率公式
    • 2.2 全概率公式
    • 2.3 朴素贝叶斯公式
      • 2.3.1 什么是朴素贝叶斯公式
      • 2.3.2 使用朴素贝叶斯公式进行垃圾邮件分类
  • 三、代码实现
    • 3.1 垃圾邮件统计分类代码实现
    • 3.2 垃圾邮件可视化分类代码实现
  • 四、亟待解决的问题
  • 五、全部代码
    • 5.1 垃圾邮件统计分类全部代码
    • 5.2 垃圾邮件可视化分类全部代码
  • 总结


前言

  本博客对基于朴素贝叶斯的垃圾邮件分类系统的开发过程进行了详细的总结,从原理到实现每一步都有记录,看完本篇博客保证各位读者对这方面知识有着更深入的了解。另外,只要跟着我一步一步做,各位读者也可以实现基于朴素贝叶斯的垃圾邮件分类系统!


零、项目演示

  1. 使用朴素贝叶斯分类器对垃圾邮件训练和测试的结果:
    请添加图片描述

  2. 贝叶斯分类器混淆矩阵:
    请添加图片描述

  3. 对非垃圾邮件的分类:
    请添加图片描述

  4. 对垃圾邮件的分类:
    请添加图片描述

一、邮件数据集

  本项目所采用的数据集来源为国际文本检索会议提供的一个公开的垃圾邮件语料库,可以进入官网下载此数据集,此数据集是一个广泛使用的电子邮件数据集,用于测试垃圾邮件过滤算法的效果。该数据集包含了大约10000个标记的开发集和50000个未标记的测试集,共60000余封测试邮件。其中约40%的电子邮件消息被标记为垃圾邮件,其余的则被标记为非垃圾邮件。而我们本项目所采用的数据集为其中的trec06c中文邮件数据集,如果想进行英文的垃圾邮件分类,可以使用其中的trec06p数据集。

  此数据集被广泛用于各种研究项目中,例如垃圾邮件过滤、邮件分类和特征提取等。此外,该数据集还被用于比较不同算法的性能,如支持向量机、朴素贝叶斯、决策树等。但是我们在使用的时候需要对电子邮件进行一些预处理操作,例如去除HTML标记、停用词过滤和词干提取。评估指标包括精确度、召回率、F1分数等。根据之前的研究结果,该数据集的分类性能通常在90%左右。

  可以直接使用上面提供的下载链接下载数据集,下文我会详细介绍如何对此数据集进行预处理和具体的使用。下面就是本数据集中的中文邮件的一个示例:
请添加图片描述

二、算法原理

2.1 条件概率公式

  条件概率是指在某种条件下发生某一事件的概率,通常用符号 P ( A ∣ B ) P(A|B) P(AB)表示,在事件 B B B发生的前提下事件 A A A发生的概率。条件概率公式是用来计算条件概率的数学公式,它可以表示为:
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B) = \frac{P(A \cap B)}{P(B)} P(AB)=P(B)P(AB)
  其中, P ( A ∩ B ) P(A \cap B) P(AB)表示事件 A A A和事件 B B B同时发生的概率, P ( B ) P(B) P(B)表示事件 B B B发生的概率。因此,条件概率公式可以解释为:在事件 B B B发生的前提下,事件 A A A B B B同时发生的概率,除以事件 B B B发生的概率,就是在事件 B B B发生的前提下事件 A A A发生的概率。

  例如,假设某个班级有 30 30 30名学生,其中 20 20 20名男生和 10 10 10名女生。如果从这个班级中随机选出一个学生,问选出的学生是男生的概率是多少?根据条件概率公式,我们可以得到:

P ( 男生 ∣ 选中的学生 ) = P ( 男生 ∩ 选中的学生 ) P ( 选中的学生 ) P(男生|选中的学生) = \frac{P(男生 \cap 选中的学生)}{P(选中的学生)} P(男生选中的学生)=P(选中的学生)P(男生选中的学生)
  其中, P ( 男生 ∩ 选中的学生 ) P(男生 \cap 选中的学生) P(男生选中的学生)表示选中的学生是男生的概率, P ( 选中的学生 ) P(选中的学生) P(选中的学生)表示任意一个学生被选中的概率。因为男生占总人数的 2 3 \frac{2}{3} 32,所以选中男生的概率为 20 30 \frac{20}{30} 3020,即 2 3 \frac{2}{3} 32;任意一个学生被选中的概率为 1 1 1,因为我们一定会选中一个学生。因此,我们可以得到:

P ( 男生 ∣ 选中的学生 ) = 20 30 1 = 2 3 P(男生|选中的学生) = \frac{\frac{20}{30}}{1} = \frac{2}{3} P(男生选中的学生)=13020=32
  因此,从这个班级中随机选中一个学生,他是男生的概率是 2 3 \frac{2}{3} 32

2.2 全概率公式

  全概率公式是概率论中一个重要的公式,用于计算某个事件的概率。它可以用于处理复杂的概率问题,特别是当我们无法直接计算事件的概率时,可以通过全概率公式来计算。全概率公式可以表示为:

P ( A ) = ∑ i = 1 n P ( A ∣ B i ) ∗ P ( B i ) P(A)=\sum_{i=1}^{n} P(A|B_i)*P(B_i) P(A)=i=1nP(ABi)P(Bi)
  其中, P ( A ) P(A) P(A)表示事件 A A A发生的概率, P ( A ∣ B i ) P(A|B_i) P(ABi)表示在事件 B i B_i Bi发生的条件下,事件 A A A发生的概率, P ( B i ) P(B_i) P(Bi)表示事件 B i B_i Bi发生的概率。 ∑ i = 1 n \sum_{i=1}^{n} i=1n表示对所有可能发生的事件 B i B_i Bi求和,即包括事件 B 1 , B 2 , . . . , B n B_1, B_2, ..., B_n B1,B2,...,Bn

  该公式的基本思想是,将事件 A A A发生的概率分解为在不同条件下的概率之和。换句话说,事件 A A A可能会在不同的条件下发生,我们需要考虑所有可能的条件,然后计算每个条件下事件 A A A发生的概率,并加权求和。这些权重是每个条件下的概率,即 P ( B i ) P(B_i) P(Bi)

  例如,我们假设班级里有 1 3 \frac{1}{3} 31的学生喜欢数学, 1 3 \frac{1}{3} 31的学生喜欢语文, 1 3 \frac{1}{3} 31的学生喜欢英语。我们还知道,喜欢数学的学生中有 1 2 \frac{1}{2} 21的人喜欢计算机,喜欢语文的学生中有 1 3 \frac{1}{3} 31的人喜欢计算机,而喜欢英语的学生中有 1 4 \frac{1}{4} 41的人喜欢计算机。

  现在问题来了,如果随机选取一个学生,他或她喜欢计算机的概率是多少?我们可以使用全概率公式来解决这个问题,即:

P ( 喜欢计算机 ) = P ( 喜欢计算机 ∣ 喜欢数学 ) ∗ P ( 喜欢数学 ) + P ( 喜欢计算机 ∣ 喜欢语文 ) ∗ P ( 喜欢语文 ) + P ( 喜欢计算机 ∣ 喜欢英语 ) ∗ P ( 喜欢英语 ) P(喜欢计算机) = P(喜欢计算机|喜欢数学) * P(喜欢数学) + P(喜欢计算机|喜欢语文) * P(喜欢语文) + P(喜欢计算机|喜欢英语) * P(喜欢英语) P(喜欢计算机)=P(喜欢计算机喜欢数学)P(喜欢数学)+P(喜欢计算机喜欢语文)P(喜欢语文)+P(喜欢计算机喜欢英语)P(喜欢英语)
  其中, P ( 喜欢计算机 ∣ 喜欢数学 ) P(喜欢计算机|喜欢数学) P(喜欢计算机喜欢数学)表示在喜欢数学的学生中喜欢计算机的概率,即 1 2 \frac{1}{2} 21 P ( 喜欢数学 ) P(喜欢数学) P(喜欢数学)表示喜欢数学的学生占总人数的比例,即 1 3 \frac{1}{3} 31。同理, P ( 喜欢计算机 ∣ 喜欢语文 ) P(喜欢计算机|喜欢语文) P(喜欢计算机喜欢语文)表示在喜欢语文的学生中喜欢计算机的概率,即 1 3 \frac{1}{3} 31 P ( 喜欢语文 ) P(喜欢语文) P(喜欢语文)表示喜欢语文的学生占总人数的比例,即 1 3 \frac{1}{3} 31 P ( 喜欢计算机 ∣ 喜欢英语 ) P(喜欢计算机|喜欢英语) P(喜欢计算机喜欢英语)表示在喜欢英语的学生中喜欢计算机的概率,即 1 4 \frac{1}{4} 41 P ( 喜欢英语 ) P(喜欢英语) P(喜欢英语)表示喜欢英语的学生占总人数的比例,即 1 3 \frac{1}{3} 31

  因此,将这些值代入公式,我们可以得到:

P ( 喜欢计算机 ) = 1 2 ∗ 1 3 + 1 3 ∗ 1 3 + 1 4 ∗ 1 3 = 13 36 P(喜欢计算机) = \frac{1}{2} * \frac{1}{3} + \frac{1}{3} * \frac{1}{3} + \frac{1}{4} * \frac{1}{3} = \frac{13}{36} P(喜欢计算机)=2131+3131+4131=3613
  因此,随机选取一个学生,他或她喜欢计算机的概率是 13 36 \frac{13}{36} 3613

2.3 朴素贝叶斯公式

2.3.1 什么是朴素贝叶斯公式

  现在我们已经掌握了条件概率公式和全概率公式,下面让我们再进一步,学习一下朴素贝叶斯公式。朴素贝叶斯公式是一种基于贝叶斯定理的简单概率分类器。朴素贝叶斯假设每个特征在给定类别下都是条件独立的。尽管这个假设在现实世界中往往不成立,但朴素贝叶斯在许多实际问题中表现出惊人的性能。朴素贝叶斯公式如下:
P ( A ∣ B ) = P ( B ∣ A ) × P ( A ) P ( B ) P(A|B) = \frac{P(B|A) \times P(A)}{P(B)} P(AB)=P(B)P(BA)×P(A)
  其中, P ( A ∣ B ) P(A|B) P(AB)是后验概率,表示在给定 B B B的情况下, A A A发生的概率; P ( B ∣ A ) P(B|A) P(BA)是条件概率,表示在给定 A A A的情况下, B B B发生的概率; P ( A ) P(A) P(A) A A A的先验概率; P ( B ) P(B) P(B) B B B的先验概率。

  现在我们用一个班级学生的例子来解释朴素贝叶斯。假设一个班级有 100 100 100名学生,其中 60 60 60名男生, 40 40 40名女生。我们想要预测一个学生是否戴眼镜。我们已经观察到,男生中有 30 30 30名戴眼镜,女生中有 20 20 20名戴眼镜。问题:给定一个戴眼镜的学生,他(她)是男生的概率是多少?

  我们根据贝叶斯定理来求解:
P ( 男生 ∣ 戴眼镜 ) = P ( 戴眼镜 ∣ 男生 ) ∗ P ( 男生 ) P ( 戴眼镜 ) P(男生|戴眼镜) = \frac{P(戴眼镜|男生) * P(男生)}{P(戴眼镜)} P(男生戴眼镜)=P(戴眼镜)P(戴眼镜男生)P(男生)
  在这个例子中:

  • P ( 男生 ) = 60 100 = 0.6 P(男生) = \frac{60}{100} = 0.6 P(男生)=10060=0.6
  • P ( 女生 ) = 40 100 = 0.4 P(女生) = \frac{40}{100} = 0.4 P(女生)=10040=0.4
  • P ( 戴眼镜 ∣ 男生 ) = 30 60 = 0.5 P(戴眼镜|男生) = \frac{30}{60} = 0.5 P(戴眼镜男生)=6030=0.5
  • P ( 戴眼镜 ∣ 女生 ) = 20 40 = 0.5 P(戴眼镜|女生) = \frac{20}{40} = 0.5 P(戴眼镜女生)=4020=0.5

  我们首先计算 P ( 戴眼镜 ) P(戴眼镜) P(戴眼镜)
P ( 戴眼镜 ) = P ( 戴眼镜 ∣ 男生 ) × P ( 男生 ) + P ( 戴眼镜 ∣ 女生 ) × P ( 女生 ) = 0.5 × 0.6 + 0.5 × 0.4 = 0.5 P(戴眼镜) = P(戴眼镜|男生) \times P(男生) + P(戴眼镜|女生) \times P(女生) = 0.5 \times 0.6 + 0.5 \times 0.4 = 0.5 P(戴眼镜)=P(戴眼镜男生)×P(男生)+P(戴眼镜女生)×P(女生)=0.5×0.6+0.5×0.4=0.5
  然后,我们可以根据朴素贝叶斯公式计算后验概率:
P ( 男生 ∣ 戴眼镜 ) = P ( 戴眼镜 ∣ 男生 ) × P ( 男生 ) P ( 戴眼镜 ) = 0.5 × 0.6 0.5 = 0.6 P(男生|戴眼镜) = \frac{P(戴眼镜|男生) \times P(男生)}{P(戴眼镜)} = \frac{0.5 \times 0.6}{0.5} = 0.6 P(男生戴眼镜)=P(戴眼镜)P(戴眼镜男生)×P(男生)=0.50.5×0.6=0.6
  所以,给定一个戴眼镜的学生,他(她)是男生的概率是 60 % 60\% 60%

2.3.2 使用朴素贝叶斯公式进行垃圾邮件分类

  当我们掌握以上知识点后,就可以使用朴素贝叶斯公式来解决垃圾邮件分类的问题。在正式解决这个问题之前我们先思考,作为人来说,我们如何对一个事物进行分类呢?比如要从各种植物中识别出草、花、树等等,可以从颜色、形状等等特征分析;从各种动物中识别出猫、狗、鸡等等,可以从叫声、长相等等特征分析;当然,将这个思想推广到邮件分类问题上,人也可以根据邮件中的具体内容对邮件进行分类判断,例如,如果某封邮件中充斥着:“今天一律五折、期待您的回访、欢迎购买我公司产品”等等句子,很明显这是一封垃圾邮件,那么我们如何让计算机也可以像人一样识别什么样的邮件是垃圾邮件呢?

  其实思想很简单,我们人对一封邮件进行分类的依据就是邮件中的关键词,如果某封邮件中多次出现:“天气、心情、生活”等等有关日常问候的词语,我们就可以认为这是一封简单的日常交流的邮件,这并不是一封垃圾邮件,而如果某封邮件中多次出现:“打折、房价、发票”等等无用的词语,我们就可以认为这是一封垃圾邮件。所以我们考虑,是否可以让计算机也可以根据邮件中的关键词对垃圾邮件进行分类呢?答案是可以的,现在我们做如下定义:

  • s p a m spam spam表示垃圾邮件
  • h a m ham ham表示非垃圾邮件
  • x x x表示邮件文本内容
  • y y y表示分类结果

  如果现在有一封邮件,我们要对其进行分类判断,因为我们现在还不知道这封邮件究竟属于 s p a m spam spam,还是属于 h a m ham ham,所以我们需要计算其对于不同类别的后验概率:

  • 当输入邮件文本为 x x x时,这封邮件为 s p a m spam spam的概率为:
    P ( Y = s p a m ∣ X = x ) = P ( X = x ∣ Y = s p a m ) × P ( Y = s p a m ) P ( X = x ∣ Y = s p a m ) × P ( Y = s p a m ) + P ( X = x ∣ Y = h a m ) × P ( Y = h a m ) P(Y=spam|X=x)=\frac{P(X=x|Y=spam) \times P(Y=spam)}{P(X=x|Y=spam) \times P(Y=spam) + P(X=x|Y=ham) \times P(Y=ham)} P(Y=spamX=x)=P(X=xY=spam)×P(Y=spam)+P(X=xY=ham)×P(Y=ham)P(X=xY=spam)×P(Y=spam)

  • 当输入邮件文本为 x x x时,这封邮件为 h a m ham ham的概率为:
    P ( Y = h a m ∣ X = x ) = P ( X = x ∣ Y = h a m ) × P ( Y = h a m ) P ( X = x ∣ Y = s p a m ) × P ( Y = s p a m ) + P ( X = x ∣ Y = h a m ) × P ( Y = h a m ) P(Y=ham|X=x)=\frac{P(X=x|Y=ham) \times P(Y=ham)}{P(X=x|Y=spam) \times P(Y=spam) + P(X=x|Y=ham) \times P(Y=ham)} P(Y=hamX=x)=P(X=xY=spam)×P(Y=spam)+P(X=xY=ham)×P(Y=ham)P(X=xY=ham)×P(Y=ham)

  最后如果 P ( Y = s p a m ∣ X = x ) P(Y=spam|X=x) P(Y=spamX=x)的概率值大就表明 x x x是一封垃圾邮件,反之表明 x x x是一封非垃圾邮件,上面的式子看似计算起来很复杂,但实际他们的分母都是相等的,唯一的区别就是分子,所以可得:
y = a r g m a x P ( X = x ∣ Y = y ) × P ( Y = y ) , y ∈ { s p a m , h a m } y = argmaxP(X=x|Y=y) \times P(Y=y),y \in \{spam,ham\} y=argmaxP(X=xY=y)×P(Y=y),y{spam,ham}
  还需要注意的是, x x x虽然是输入的邮件文本,但是已经经过数据预处理、分词、TF-IDF值的计算等步骤的处理成为了邮件文本 x x x对应的特征向量,而我们使用的朴素贝叶斯公式默认每个特征都是条件独立的,也就是说特征向量中每个词语的TF-IDF值之间互不影响,所以说上式可以继续化简为:
y = a r g m a x ∏ i P ( X = x ( i ) ∣ Y = y ) × P ( Y = y ) , y ∈ { s p a m , h a m } y = argmax\prod_{i}P(X=x^{(i)}|Y=y) \times P(Y=y),y \in \{spam,ham\} y=argmaxiP(X=x(i)Y=y)×P(Y=y),y{spam,ham}
  之后就可以训练数据,使用最大似然估计方法计算得到每个关键词的条件概率值 P ( X = x ( i ) ∣ Y = y ) P(X=x^{(i)}|Y=y) P(X=x(i)Y=y),而 P ( Y = y ) P(Y=y) P(Y=y)作为先验概率很容易计算得到。当输入一封新的邮件文本 x x x后,就可以对其进行同样的数据处理,然后代入上式得到分类概率值,取最大的分类概率值作为最后的分类结果,就可以判断输入 x x x是垃圾邮件还是非垃圾邮件了。

三、代码实现

3.1 垃圾邮件统计分类代码实现

  1. 首先导入项目运行所需要的库:

    import numpy as np
    import matplotlib.pyplot as plt
    import re
    import jieba
    import itertools
    import time
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import confusion_matrix, recall_score
    from sklearn.naive_bayes import MultinomialNB
    from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
    
  2. 然后根据index文件获取数据文件的路径和标签,用0和1对标签值进行标记,0代表不是垃圾邮件,1代表是垃圾邮件,最终得到邮件文件路径列表和邮件对应的标签值列表:

    def get_path_label():
        """
        根据index文件获取数据文件的路径和标签
        :return 数据文件路径、数据文件标签
        """
        label_path_list = open("data/trec06c/full/index", "r", encoding="gb2312", errors="ignore")
        label_path = [data for data in label_path_list]
        label_path_split = [data.split() for data in label_path if len(data.split()) == 2]
        label_list = [1 if data[0] == "spam" else 0 for data in label_path_split]
        path_list = [data[1].replace("..", "trec06c") for data in label_path_split]
        return path_list, label_list
    
  3. 然后根据上一步得到的邮件文件路径列表获取每个邮件的文本内容,并对其进行简单的处理,如去除特殊字段、换行符等,最终得到每个邮件的正文内容列表。另外,需要注意的是,只有第一次运行的时候需要调用此函数,因为邮件正文只是为了分词使用,而后面我们已经将分词结果保存到本地了,所以使用的时候直接调用即可,不用每次都加载,这样可以节省时间:

    def get_data(path_list):
        """
        根据数据文件路径打开数据文件,提取每个邮件的正文
        :param path_list:
        :return 提取的邮件正文
        """
        mail = open(path_list, "r", encoding="gb2312", errors="ignore")
        mail_text = [data for data in mail]
        mail_head_index = [mail_text.index(i) for i in mail_text if re.match("[a-zA-z0-9]", i)]
        text = ''.join(mail_text[max(mail_head_index) + 1:])
        text = re.sub('\s+', '', re.sub("\u3000", "", re.sub("\n", "", text)))
        return text
    
  4. 然后加载停用词表,停用词就是一些不重要的词语,比如“啊、它们、没有”等,这些单词不仅对我们最终的判断结果没有帮助,还会消耗存储空间并降低搜索效率,所以我们应该忽略这些词语,加载停用词表的目的就是把刚刚获取到的每一封邮件正文中的停用词删除。另外,此停用词表就在我的项目中,下载项目后就可以直接使用。还有一点需要注意的是,只有第一次运行的时候需要加载停用词表,因为停用词表只在分词的时候使用过,而分词后的结果已经被我保存到本地了,需要使用的时候直接调用本地保存好的分词结果就行,所以不用每次使用都加载停用词表,这样可以节省时间:

    def upload_stopword():
        """
        加载停用词
        :return 返回加载的停用词表
        """
        with open("stopwords.txt", encoding="utf-8") as file:
            data = file.read()
            return data.split("\n")
    
  5. 然后使用jieba分词工具将邮件文本分词,因为我们要计算每封邮件中每个词语对这封邮件最终的分类影响,这里需要使用刚才加载的停用词表以去除不需要的词语。这里还需要注意的是,只有第一次使用的时候调用此函数,否则每次分词非常耗时,除了第一次使用,其余测试的时候直接调用保存的分词结果即可,保存好的分词结果已经被我放到项目的主目录了,使用的时候直接调用即可,可以节省时间:

    def participle(mail_list, stopword_list):
        """
        使用jieba对邮件文本分词
        :param mail_list: 邮件文本
        :param stopword_list: 停用词表
        :return 返回邮件文本分词结果
        """
        cur_word_list = []
        startTime = time.time()
        for mail in mail_list:
            cut_word = [data for data in jieba.lcut(mail) if data not in set(stopword_list)]
            cur_word_list.append(cut_word)
        print("jieba分词用时%0.2f秒" % (time.time() - startTime))
        return cur_word_list
    
  6. 然后计算邮件文本中词语的词频(TF),也可称为词语出现的频率(Term Frequency)。TF指的是一个词语的重要程度,TF值越高,表明一个词语在邮件中出现的次数越多,也就意味着该词语越重要,反之意味着越不重要。TF的计算公式如下:
    T F 某个词语 = 某个词语出现的次数 邮件中的总词汇数量 TF_{某个词语}=\frac{某个词语出现的次数}{邮件中的总词汇数量} TF某个词语=邮件中的总词汇数量某个词语出现的次数
    上面的计算量看起来很大,但是在Python中实现却很简单,我们只需要使用CountVectorizer()函数就能帮我们完成TF值的计算,最终返回计算好的TF值列表:

    def get_TF(cur_word_list):
        """
        计算TF
        :param cur_word_list: 分词结果列表
        :return TF列表
        """
        text = [' '.join(data) for data in cur_word_list]
        cv = CountVectorizer(max_features=5000, max_df=0.6, min_df=5)
        count_list = cv.fit_transform(text)
        return count_list
    

    对于CountVectorizer()函数中的参数值简单介绍一下:

    • max_features=5000:对TF值降序排序,取前5000个词语作为关键词集
    • max_df=0.6:去除在60%的邮件中都出现过的词语,因为出现次数太多,没有区分度
    • min_df=5:去除只在5个以下的邮件中出现的词语,因为出现次数太少,没有区分度
  7. 然后计算邮件文本中词语的IDF(Inverse Document Frequency),也可称为逆文本词频。IDF指的是一个词语在某一封邮件中的区分度,若某个词语只在某封邮件中出现过多次,而在其他邮件中几乎没有出现过,那么就可以认为该词语对这封邮件很重要,也就说明可以通过该词语将此邮件和其他邮件区分开,反之不能进行区分。IDF的计算公式如下:
    I D F 某个词语 = l o g ( 数据集的邮件总数 包含该词语的邮件数 + 1 ) IDF_{某个词语}=log(\frac{数据集的邮件总数}{包含该词语的邮件数+1}) IDF某个词语=log(包含该词语的邮件数+1数据集的邮件总数)
    刚刚我们已经计算得到了TF值,现在我们将TF值与IDF值相乘,就得到了某个词语的TF-IDF值:
    T F − I D F 某个词语 = T F 某个词语 × I D F 某个词语 TF-IDF_{某个词语}=TF_{某个词语} \times IDF_{某个词语} TFIDF某个词语=TF某个词语×IDF某个词语
    通过计算词语的TF-IDF值,就可以把TF-IDF值高的词语作为邮件的特征属性,因为TF-IDF值不仅仅包括词语重要性的权重,还包括词语区分度的权重,通过结合权重得到特征值,从而降低特征值低的词语的重要性,提高特征值高的词语的重要性,就可以利用这些词语对邮件进行分类。以上的计算步骤看起来可能比较复杂,但是在Python中也只需要两行(甚至一行)就可以完成TF-IDF的计算:

    def get_TFIDF(count_list):
        """
        计算TF-IDF
        :param count_list: 计算得到的TF列表
        :return TF-IDF列表
        """
        TF_IDF = TfidfTransformer()
        TF_IDF_matrix = TF_IDF.fit_transform(count_list)
        return TF_IDF_matrix
    
  8. 为了可视化展示我们的分类测试结果,我们可以计算一下混淆矩阵(Confusion Matrix),混淆矩阵的每一列代表真实值,每一行代表预测值(也可以自行设置行和列的表示值),通过混淆矩阵我们可以大致观察出训练模型的分类效果:

    def plt_confusion_matrix(confusion_matrix, classes, title, cmap=plt.cm.Blues):
        """
        绘制混淆矩阵
        :param confusion_matrix: 混淆矩阵值
        :param classes: 分类类别
        :param title: 绘制图形的标题
        :param cmap: 绘图风格
        """
        plt.rcParams['font.sans-serif'] = ['SimHei']
        plt.rcParams['axes.unicode_minus'] = False
        plt.imshow(confusion_matrix, interpolation="nearest", cmap=cmap)
        plt.title(title)
        plt.colorbar()
        axis_marks = np.arange(len(classes))
        plt.xticks(axis_marks, classes, rotation=0)
        plt.yticks(axis_marks, classes, rotation=0)
        axis_line = confusion_matrix.max() / 2.
        for i, j in itertools.product(range(confusion_matrix.shape[0]), range(confusion_matrix.shape[1])):
            plt.text(j, i, confusion_matrix[i, j], horizontalalignment="center",
                     color="white" if confusion_matrix[i, j] > axis_line else "black")
        plt.tight_layout()
        plt.xlabel("预测结果")
        plt.ylabel("真实结果")
        plt.show()
    
  9. 然后使用贝叶斯分类器对垃圾邮件进行分类,在分类的时候需要使用刚刚计算的TF-IDF矩阵值和标签列表进行训练和测试:

    def train_test_Bayes(TF_IDF_matrix_result, label_list):
        """
        朴素贝叶斯分类器对于垃圾邮件数据集的训练和测试
        :param TF_IDF_matrix_result: TF_IDF值矩阵
        :param label_list: 标签列表
        :return 无
        """
        print(">>>>>>>>>>>>>朴素贝叶斯分类器垃圾邮件分类<<<<<<<<<<<<<")
        train_x, test_x, train_y, test_y = train_test_split(TF_IDF_matrix_result, label_list, test_size=0.2, random_state=0)
        classifier = MultinomialNB()
        startTime = time.time()
        classifier.fit(train_x, train_y)
        print("贝叶斯分类器训练耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startTime)
        score = classifier.score(test_x, test_y)
        print("贝叶斯分类器的分类结果准确率>>>>>>>>>>>>>>>>>>>>>>>>>>>", score)
        predict_y = classifier.predict(test_x)
        print("贝叶斯分类器的召回率>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", recall_score(test_y, predict_y))
        plt_confusion_matrix(confusion_matrix(test_y, predict_y), [0, 1], title="贝叶斯分类器混淆矩阵")
    
    • 首先使用train_test_split()进行训练集和测试集的划分,本项目中训练集划分80%,测试集划分20%
    • 然后使用MultinomialNB()进行朴素贝叶斯模型训练,其实sklearn库中的 naive_bayes模块实现了五种朴素贝叶斯算法,我们这里选用MultinomialNB(),因为其适用于离散性数据,且符合多项式分布的特征变量,也就是词语的TF-IDF值,另外,MultinomialNB()的默认参数alpha=1,即使用拉普拉斯平滑处理,采用加1的方式,来统计没有出现过的词语的概率,避免因为训练集样本不充分而导致概率计算结果为0的情况,这样得到的概率值更接近真实概率值
    • 然后计算模型的准确率
    • 然后计算模型的召回率
    • 最后使用上面介绍的plt_confusion_matrix()绘制混淆矩阵
  10. 当我们完成各个子模块的功能后,就可以将这些子模块进行整合,来完成垃圾邮件分类任务了,主函数中各个子模块的调用顺序和上面各个子模块介绍的顺序一致,我只是额外增加了各个子步骤的运行时间计算。另外,需要注意的是,get_data()upload_stopword()participle()这三个函数只有第一次运行的时候需要使用,其余的时候注释掉即可,这样可以节省很多时间,因为咱们的训练和测试数据太多了,如果不这么做的话,消耗的时间太长了:

    if __name__ == '__main__':
        print(">>>>>>>>>>>>>>>>>>>>>>>>垃圾邮件分类系统开始运行<<<<<<<<<<<<<<<<<<<<<<<<")
        path_lists, label_lists = get_path_label()
        '''
            只有第一次运行的时候需要加载,因为后面都已经将分词结果保存到本地了,所以就不用每次都加载邮件文本了,而且停用词表也在分词的时候使用过了,所以也不用每次都加载
        '''
        # mail_texts = [get_data(path) for path in path_lists]
        # stopword_lists = upload_stopword()
        '''
            将筛选后的数据集分词,并将分词结果保存,只有第一次使用的时候需要将注释打开,否则每次分词非常耗时,除了第一次使用,其余测试的时候直接调用保存的分词结果即可
        '''
        # cut_word_lists = participle(mail_texts, stopword_lists)
        # cut_word_lists = np.array(cut_word_lists)
        # np.save("cut_word_lists.npy", cut_word_lists)
        startOne = time.time()
        cut_word_lists = np.load('cut_word_lists.npy')
        print("加载分词文件耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startOne)
        startTwo = time.time()
        cut_word_lists = cut_word_lists.tolist()
        print("转换分词列表耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startTwo)
        startThree = time.time()
        count_lists = get_TF(cut_word_lists)
        print("获取分词TF值耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startThree)
        startFour = time.time()
        TF_IDF_matrix_results = get_TFIDF(count_lists)
        print("获取分词TF-IDF值耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startFour)
        startFive = time.time()
        train_test_Bayes(TF_IDF_matrix_results, label_lists)
        print("贝叶斯分类器训练和预测耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startFive)
    
  11. 最后我们可以运行主函数来看一下模型的训练结果:

    • 可以看到整个项目运行时间还是蛮快的,如果加上刚才的三个函数时间就会慢很多。当然,时间并不是重点,我们可以观察到,我们的模型的准确率为96%,召回率为97%,这也就意味着我们的模型训练结果还是不错的,这次实验很成功:
      请添加图片描述

    • 我们再来看生成的混淆矩阵,可以看到所有的邮件几乎都分类成功了,这也从可视化的角度证明我们训练的模型的分类准确率非常高:
      请添加图片描述

3.2 垃圾邮件可视化分类代码实现

  1. 首先导入项目运行所需要的库:

    import sys
    import re
    import jieba
    from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
    from PyQt5 import QtCore, QtGui, QtWidgets
    from scipy.sparse import csr_matrix
    import joblib
    
  2. 然后加载停用词表,目的是去除测试文本中的停用词,和上面介绍的一样,不再赘述:

    def upload_stopword():
        """
        加载停用词
        :return 返回加载的停用词表
        """
        with open("stopwords.txt", encoding="utf-8") as file:
            data = file.read()
            return data.split("\n")
    
  3. 然后对测试文本进行预处理并计算TF-IDF值,这部分的处理和上面介绍的过程一样,不再赘述:

    def handle_mail(mail, stopword_list):
        """
        对邮件文本预处理,并计算其TF-IDF值,最后返回TF-IDF值矩阵
        :param mail: 待检测的邮件文本
        :param stopword_list: 停用词列表
        :return TF-IDF值矩阵
        """
        '''
            处理邮件文本的邮件头以及空格、换行等等
        '''
        mail_head_index = [mail.index(i) for i in mail if re.match("[a-zA-z0-9]", i)]
        mail_res = ''.join(mail[max(mail_head_index) + 1:])
        mail_res = re.sub('\s+', '', re.sub("\u3000", "", re.sub("\n", "", mail_res)))
        '''
            对邮件文本进行分词处理
        '''
        cut_word = [data for data in jieba.lcut(mail_res) if data not in set(stopword_list)]
        '''
            计算邮件本文的TF-IDF值
        '''
        text = [' '.join(cut_word)]
        cv = CountVectorizer()
        TF_IDF = TfidfTransformer()
        TF_IDF_matrix = TF_IDF.fit_transform(cv.fit_transform(text))
        TF_IDF_matrix_res = csr_matrix((TF_IDF_matrix.data, TF_IDF_matrix.indices, TF_IDF_matrix.indptr), shape=(1, 5000))
        return TF_IDF_matrix_res
    
  4. 然后直接调用训练好的模型进行测试,我已经将训练好的模型保存到项目的主目录了。当然,读者也可以使用自己训练的模型测试,具体方法自行搜索,比较简单,因为这部分内容不是本篇博客重点,所以不在此赘述:

    def predict_res(mail):
        """
        使用训练好的模型对传入的测试邮件进行分类预测
        :param mail: 待测试邮件
        :return: 分类预测结果
        """
        classifier = joblib.load("model/classifier.pkl")
        res = classifier.predict(mail)
        return res
    
  5. 然后绘制测试窗口,这部分内容也不是本篇博客的重点内容,所以也不在此赘述,唯一需要介绍的就是my_func()函数,my_func()函数的作用是调用上面介绍的各个子模块,返回得到预测结果,最终将预测结果显示在窗口上:

    class Ui_Dialog(object):
        """
        垃圾邮件分类的可视化展示
        """
    
        def __init__(self):
            """
            初始化参数
            """
            self.pushButton_2 = None
            self.pushButton = None
            self.horizontalLayout = None
            self.plainTextEdit_2 = None
            self.plainTextEdit = None
            self.horizontalLayout_2 = None
            self.verticalLayout = None
            self.verticalLayout_2 = None
    
        def setupUi(self, Dialog):
            """
            绘制垃圾邮件分类的UI界面
            :param Dialog: 当前对话窗口
            """
            Dialog.setObjectName("基于朴素贝叶斯的垃圾邮件过滤系统")
            Dialog.resize(525, 386)
            self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
            self.verticalLayout_2.setObjectName("verticalLayout_2")
            self.verticalLayout = QtWidgets.QVBoxLayout()
            self.verticalLayout.setObjectName("verticalLayout")
            self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
            self.horizontalLayout_2.setObjectName("horizontalLayout_2")
            self.plainTextEdit = QtWidgets.QPlainTextEdit(Dialog)
            font = QtGui.QFont()
            font.setFamily("SimHei")
            font.setPointSize(12)
            self.plainTextEdit.setFont(font)
            self.plainTextEdit.setObjectName("plainTextEdit")
            self.horizontalLayout_2.addWidget(self.plainTextEdit)
            self.plainTextEdit_2 = QtWidgets.QPlainTextEdit(Dialog)
            font = QtGui.QFont()
            font.setFamily("SimHei")
            font.setPointSize(12)
            self.plainTextEdit_2.setFont(font)
            self.plainTextEdit_2.setObjectName("plainTextEdit_2")
            self.horizontalLayout_2.addWidget(self.plainTextEdit_2)
            self.verticalLayout.addLayout(self.horizontalLayout_2)
            self.horizontalLayout = QtWidgets.QHBoxLayout()
            self.horizontalLayout.setObjectName("horizontalLayout")
            self.pushButton = QtWidgets.QPushButton(Dialog)
            font = QtGui.QFont()
            font.setFamily("SimHei")
            font.setPointSize(16)
            self.pushButton.setFont(font)
            self.pushButton.setObjectName("pushButton")
            self.horizontalLayout.addWidget(self.pushButton)
            self.pushButton_2 = QtWidgets.QPushButton(Dialog)
            font = QtGui.QFont()
            font.setFamily("SimHei")
            font.setPointSize(16)
            self.pushButton_2.setFont(font)
            self.pushButton_2.setObjectName("pushButton_2")
            self.horizontalLayout.addWidget(self.pushButton_2)
            self.verticalLayout.addLayout(self.horizontalLayout)
            self.verticalLayout_2.addLayout(self.verticalLayout)
            self.retranslateUi(Dialog)
            QtCore.QMetaObject.connectSlotsByName(Dialog)
            self.pushButton.clicked.connect(self.my_func)
    
        def retranslateUi(self, Dialog):
            """
            绘制当前窗口的按钮
            :param Dialog: 当前对话窗口
            """
            _translate = QtCore.QCoreApplication.translate
            Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
            self.pushButton.setText(_translate("Dialog", "确定"))
            self.pushButton_2.setText(_translate("Dialog", "清空"))
            self.pushButton_2.clicked.connect(self.plainTextEdit.clear)
            self.pushButton_2.clicked.connect(self.plainTextEdit_2.clear)
    
        def my_func(self):
            """
            调用各个功能模块
            """
            stopword_lists = upload_stopword()
            mail = self.plainTextEdit.toPlainText()
            mail_handle = handle_mail(mail, stopword_lists)
            res = predict_res(mail_handle)
            show_res = None
            if res[0] == 1:
                show_res = "这是垃圾邮件"
            else:
                show_res = "这不是垃圾邮件"
            self.plainTextEdit_2.setPlainText(show_res)
    
  6. 然后直接使用主函数调用上面介绍的绘制窗口的模块即可(预测功能模块已经嵌入到绘制窗口模块中了):

    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        Dialog = QtWidgets.QDialog()
        ui = Ui_Dialog()
        ui.setupUi(Dialog)
        Dialog.show()
        sys.exit(app.exec_())
    
  7. 最后我们可以运行一下主函数,来看一下运行效果:

    • 当我们输入一段非垃圾邮件的时候,窗口输出“这不是垃圾邮件”,表明预测成功:
      请添加图片描述

    • 当我们输入一段垃圾邮件的时候,窗口输出“这是垃圾邮件”,表明预测成功:
      请添加图片描述

四、亟待解决的问题

  目前看来我们的模型训练效果还不错,但是还是存在问题,当我们在进行可视化的邮件分类的时候,需要输入一封测试邮件,我们需要计算这封测试邮件分词后的TF-IDF值。但是之前我们在进行模型的训练和测试的时候,由于总数据量非常大,所以我们设定的维度为5000维,也就是取TF-IDF值降序后的前5000个单词作为关键词集,而我们输入的测试邮件肯定达不到5000维,也就导致无法进行预测。

  为了解决这个问题,我能想到的办法就是使用csr_matrix()函数和shape()函数对计算得到的测试邮件的TF-IDF值进行维度扩充,将其维度扩充到5000维:

TF_IDF_matrix_res = csr_matrix((TF_IDF_matrix.data, TF_IDF_matrix.indices, TF_IDF_matrix.indptr), shape=(1, 5000))

  这样虽然可以成功输出预测分类值,但是测试分类的结果总是很差,但是我目前也没有想到更好的办法去解决这个问题,如果各位读者有好的办法,欢迎随时和我交流!

五、全部代码

5.1 垃圾邮件统计分类全部代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:IronmanJay
# email:1975686676@qq.com

# 导入程序运行必需的库
import numpy as np
import matplotlib.pyplot as plt
import re
import jieba
import itertools
import time
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, recall_score
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer


def get_path_label():
    """
    根据index文件获取数据文件的路径和标签
    :return 数据文件路径、数据文件标签
    """
    label_path_list = open("data/trec06c/full/index", "r", encoding="gb2312", errors="ignore")
    label_path = [data for data in label_path_list]
    label_path_split = [data.split() for data in label_path if len(data.split()) == 2]
    label_list = [1 if data[0] == "spam" else 0 for data in label_path_split]
    path_list = [data[1].replace("..", "trec06c") for data in label_path_split]
    return path_list, label_list


def get_data(path_list):
    """
    根据数据文件路径打开数据文件,提取每个邮件的正文
    :param path_list:
    :return 提取的邮件正文
    """
    mail = open(path_list, "r", encoding="gb2312", errors="ignore")
    mail_text = [data for data in mail]
    mail_head_index = [mail_text.index(i) for i in mail_text if re.match("[a-zA-z0-9]", i)]
    text = ''.join(mail_text[max(mail_head_index) + 1:])
    text = re.sub('\s+', '', re.sub("\u3000", "", re.sub("\n", "", text)))
    return text


def upload_stopword():
    """
    加载停用词
    :return 返回加载的停用词表
    """
    with open("stopwords.txt", encoding="utf-8") as file:
        data = file.read()
        return data.split("\n")


def participle(mail_list, stopword_list):
    """
    使用jieba对邮件文本分词
    :param mail_list: 邮件文本
    :param stopword_list: 停用词表
    :return 返回邮件文本分词结果
    """
    cur_word_list = []
    startTime = time.time()
    for mail in mail_list:
        cut_word = [data for data in jieba.lcut(mail) if data not in set(stopword_list)]
        cur_word_list.append(cut_word)
    print("jieba分词用时%0.2f秒" % (time.time() - startTime))
    return cur_word_list


def get_TF(cur_word_list):
    """
    计算TF
    :param cur_word_list: 分词结果列表
    :return TF列表
    """
    text = [' '.join(data) for data in cur_word_list]
    cv = CountVectorizer(max_features=5000, max_df=0.6, min_df=5)
    count_list = cv.fit_transform(text)
    return count_list


def get_TFIDF(count_list):
    """
    计算TF-IDF
    :param count_list: 计算得到的TF列表
    :return TF-IDF列表
    """
    TF_IDF = TfidfTransformer()
    TF_IDF_matrix = TF_IDF.fit_transform(count_list)
    return TF_IDF_matrix


def plt_confusion_matrix(confusion_matrix, classes, title, cmap=plt.cm.Blues):
    """
    绘制混淆矩阵
    :param confusion_matrix: 混淆矩阵值
    :param classes: 分类类别
    :param title: 绘制图形的标题
    :param cmap: 绘图风格
    """
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.imshow(confusion_matrix, interpolation="nearest", cmap=cmap)
    plt.title(title)
    plt.colorbar()
    axis_marks = np.arange(len(classes))
    plt.xticks(axis_marks, classes, rotation=0)
    plt.yticks(axis_marks, classes, rotation=0)
    axis_line = confusion_matrix.max() / 2.
    for i, j in itertools.product(range(confusion_matrix.shape[0]), range(confusion_matrix.shape[1])):
        plt.text(j, i, confusion_matrix[i, j], horizontalalignment="center",
                 color="white" if confusion_matrix[i, j] > axis_line else "black")
    plt.tight_layout()
    plt.xlabel("预测结果")
    plt.ylabel("真实结果")
    plt.show()


def train_test_Bayes(TF_IDF_matrix_result, label_list):
    """
    朴素贝叶斯分类器对于垃圾邮件数据集的训练和测试
    :param TF_IDF_matrix_result: TF_IDF值矩阵
    :param label_list: 标签列表
    :return 无
    """
    print(">>>>>>>>>>>>>朴素贝叶斯分类器垃圾邮件分类<<<<<<<<<<<<<")
    train_x, test_x, train_y, test_y = train_test_split(TF_IDF_matrix_result, label_list, test_size=0.2, random_state=0)
    classifier = MultinomialNB()
    startTime = time.time()
    classifier.fit(train_x, train_y)
    print("贝叶斯分类器训练耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startTime)
    score = classifier.score(test_x, test_y)
    print("贝叶斯分类器的分类结果准确率>>>>>>>>>>>>>>>>>>>>>>>>>>>", score)
    predict_y = classifier.predict(test_x)
    print("贝叶斯分类器的召回率>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", recall_score(test_y, predict_y))
    plt_confusion_matrix(confusion_matrix(test_y, predict_y), [0, 1], title="贝叶斯分类器混淆矩阵")


if __name__ == '__main__':
    print(">>>>>>>>>>>>>>>>>>>>>>>>垃圾邮件分类系统开始运行<<<<<<<<<<<<<<<<<<<<<<<<")
    path_lists, label_lists = get_path_label()
    '''
        只有第一次运行的时候需要加载,因为后面都已经将分词结果保存到本地了,所以就不用每次都加载邮件文本了,而且停用词表也在分词的时候使用过了,所以也不用每次都加载
    '''
    # mail_texts = [get_data(path) for path in path_lists]
    # stopword_lists = upload_stopword()
    '''
        将筛选后的数据集分词,并将分词结果保存,只有第一次使用的时候需要将注释打开,否则每次分词非常耗时,除了第一次使用,其余测试的时候直接调用保存的分词结果即可
    '''
    # cut_word_lists = participle(mail_texts, stopword_lists)
    # cut_word_lists = np.array(cut_word_lists)
    # np.save("cut_word_lists.npy", cut_word_lists)
    startOne = time.time()
    cut_word_lists = np.load('cut_word_lists.npy')
    print("加载分词文件耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startOne)
    startTwo = time.time()
    cut_word_lists = cut_word_lists.tolist()
    print("转换分词列表耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startTwo)
    startThree = time.time()
    count_lists = get_TF(cut_word_lists)
    print("获取分词TF值耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startThree)
    startFour = time.time()
    TF_IDF_matrix_results = get_TFIDF(count_lists)
    print("获取分词TF-IDF值耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startFour)
    startFive = time.time()
    train_test_Bayes(TF_IDF_matrix_results, label_lists)
    print("贝叶斯分类器训练和预测耗时>>>>>>>>>>>>>>>>>>>>>>>>>>>>", time.time() - startFive)

5.2 垃圾邮件可视化分类全部代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:IronmanJay
# email:1975686676@qq.com

# 导入程序运行必需的库
import sys
import re
import jieba
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from PyQt5 import QtCore, QtGui, QtWidgets
from scipy.sparse import csr_matrix
import joblib


def upload_stopword():
    """
    加载停用词
    :return 返回加载的停用词表
    """
    with open("stopwords.txt", encoding="utf-8") as file:
        data = file.read()
        return data.split("\n")


def handle_mail(mail, stopword_list):
    """
    对邮件文本预处理,并计算其TF-IDF值,最后返回TF-IDF值矩阵
    :param mail: 待检测的邮件文本
    :param stopword_list: 停用词列表
    :return TF-IDF值矩阵
    """
    '''
        处理邮件文本的邮件头以及空格、换行等等
    '''
    mail_head_index = [mail.index(i) for i in mail if re.match("[a-zA-z0-9]", i)]
    mail_res = ''.join(mail[max(mail_head_index) + 1:])
    mail_res = re.sub('\s+', '', re.sub("\u3000", "", re.sub("\n", "", mail_res)))
    '''
        对邮件文本进行分词处理
    '''
    cut_word = [data for data in jieba.lcut(mail_res) if data not in set(stopword_list)]
    '''
        计算邮件本文的TF-IDF值
    '''
    text = [' '.join(cut_word)]
    cv = CountVectorizer()
    TF_IDF = TfidfTransformer()
    TF_IDF_matrix = TF_IDF.fit_transform(cv.fit_transform(text))
    TF_IDF_matrix_res = csr_matrix((TF_IDF_matrix.data, TF_IDF_matrix.indices, TF_IDF_matrix.indptr), shape=(1, 5000))
    return TF_IDF_matrix_res


def predict_res(mail):
    """
    使用训练好的模型对传入的测试邮件进行分类预测
    :param mail: 待测试邮件
    :return: 分类预测结果
    """
    classifier = joblib.load("model/classifier.pkl")
    res = classifier.predict(mail)
    return res


class Ui_Dialog(object):
    """
    垃圾邮件分类的可视化展示
    """

    def __init__(self):
        """
        初始化参数
        """
        self.pushButton_2 = None
        self.pushButton = None
        self.horizontalLayout = None
        self.plainTextEdit_2 = None
        self.plainTextEdit = None
        self.horizontalLayout_2 = None
        self.verticalLayout = None
        self.verticalLayout_2 = None

    def setupUi(self, Dialog):
        """
        绘制垃圾邮件分类的UI界面
        :param Dialog: 当前对话窗口
        """
        Dialog.setObjectName("基于朴素贝叶斯的垃圾邮件过滤系统")
        Dialog.resize(525, 386)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.plainTextEdit = QtWidgets.QPlainTextEdit(Dialog)
        font = QtGui.QFont()
        font.setFamily("SimHei")
        font.setPointSize(12)
        self.plainTextEdit.setFont(font)
        self.plainTextEdit.setObjectName("plainTextEdit")
        self.horizontalLayout_2.addWidget(self.plainTextEdit)
        self.plainTextEdit_2 = QtWidgets.QPlainTextEdit(Dialog)
        font = QtGui.QFont()
        font.setFamily("SimHei")
        font.setPointSize(12)
        self.plainTextEdit_2.setFont(font)
        self.plainTextEdit_2.setObjectName("plainTextEdit_2")
        self.horizontalLayout_2.addWidget(self.plainTextEdit_2)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(Dialog)
        font = QtGui.QFont()
        font.setFamily("SimHei")
        font.setPointSize(16)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtWidgets.QPushButton(Dialog)
        font = QtGui.QFont()
        font.setFamily("SimHei")
        font.setPointSize(16)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)
        self.pushButton.clicked.connect(self.my_func)

    def retranslateUi(self, Dialog):
        """
        绘制当前窗口的按钮
        :param Dialog: 当前对话窗口
        """
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.pushButton.setText(_translate("Dialog", "确定"))
        self.pushButton_2.setText(_translate("Dialog", "清空"))
        self.pushButton_2.clicked.connect(self.plainTextEdit.clear)
        self.pushButton_2.clicked.connect(self.plainTextEdit_2.clear)

    def my_func(self):
        """
        调用各个功能模块
        """
        stopword_lists = upload_stopword()
        mail = self.plainTextEdit.toPlainText()
        mail_handle = handle_mail(mail, stopword_lists)
        res = predict_res(mail_handle)
        show_res = None
        if res[0] == 1:
            show_res = "这是垃圾邮件"
        else:
            show_res = "这不是垃圾邮件"
        self.plainTextEdit_2.setPlainText(show_res)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

总结

  以上就是基于朴素贝叶斯的垃圾邮件分类系统项目的全部开发教程,如果各位读者有什么疑问,欢迎随时和我私信或者留言,或者对我的问题有什么好的思路,也欢迎随时联系我。这篇博客就告一段落了,下篇博客见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端通过ajax上传文件到七牛云

1. 从服务端获取七牛云上传的token,生成token参考官方文档https://developer.qiniu.com/kodo/1208/upload-token 2. 在七牛云文档查找上传的存储区域 https://developer.qiniu.com/kodo/1671/region-endpoint-fq 在七牛云控制台找到空间管理的cdn加速域名https://portal.qiniu…

【Thinkphp 6】框架基础知识

文章目录 环境搭建框架基础规则继承引入单应用模式多应用模式自定义路由调试器空控制器 视图模板引擎安装渲染模板facade代理变量传递view.php语法查看编译后的文件默认值数组按键取值md5加密 请求request信息参数接收生成URL 文件上传上传及验证 验证功能验证器表单令牌 中间件…

android不可不知调试技巧

目录 1、条件断点 2、评估表达式&#xff08;Evaluate Expression&#xff09; 3、日志断点 4、方法断点 5、异常断点 6、Field WatchPoint 1、条件断点 假设我们列表循环的某个元素时候才暂停&#xff0c;就用这种方式。具体方式在循环列表打断点&#xff0c;对着断点右…

Nginx简介和快速入门

前言: 在一个小型的个人博客网站中&#xff0c;因为没什么流量&#xff0c;并发量小&#xff0c;一般可以直接在一个服务器上的tomcat中直接运行jar包.由tomcat直接响应给客户。 到后面之后随着流量的增大&#xff0c;一台服务器的资源不够用了&#xff0c;此时就需要再多开一…

浏览器渲染页面的原理及流程

1、渲染引擎首先通过网络获得所请求文档的内容 2、解析HTML文件&#xff0c;构建 DOM Tree 3、解析CSS&#xff0c;构建 CSSOM Tree(CSS规则树) 4、将 DOM Tree 和 CSSOM Tree合并&#xff0c;构建Render tree(渲染树) 5、reflow(重排、回流)&#xff1a;根据Render tree进行节…

功率器件的仿真评估

功率器件的仿真评估 1.功率器件仿真评估概述2.IGBT温升模型整理3.Matlab仿真计算4.仿真评估报告 1.功率器件仿真评估概述 功率器件的仿真评估共五个步骤&#xff1a; a.根据IGBT数据手册整理中热阻参数、开关损耗参数温升模型&#xff1b; b.带入到Matlab中仿真堵转、中速运行…

一键免费部署你的私人 ChatGPT 网页应用

主要功能 在 1 分钟内使用 Vercel &#xff08;https://vercel.com/&#xff09;免费一键部署精心设计的 UI&#xff0c;响应式设计&#xff0c;支持深色模式极快的首屏加载速度&#xff08;~100kb&#xff09;海量的内置 prompt 列表&#xff0c;来自中文和英文自动压缩上下文…

CUDA下载,以及下载GPU版本的pytorch

一、下载anaconda 因为这步我之前就下好了&#xff0c;主要参考这个链接&#xff1a;史上最全最详细的Anaconda安装教程 二、下载CUDA 1.首先观察自己需要什么版本的CUDA&#xff0c;以及是否安装过CUDA 先cmd&#xff0c;输入命令 nvidia-smi结果如下&#xff0c;所以我们…

论文笔记:Hidden Markov Map MatchingThrough Noise and Sparseness

sigspatial 2009 1 方法介绍 1.0great circle和route距离 1.1 和ST-matching的比较 1.1.1 转移概率和观测概率 和同一年的ST-matching很类似&#xff0c;也是使用HMM来进行路网匹配论文笔记&#xff1a;Map-Matching for low-sampling-rate GPS trajectories&#xff08;ST…

【Redis7】Spring Boot集成Redis(重点:集成RedisTemplate)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 Spring Boot集成Redis&#xff0c;包括Jedis、lettuce、集成RedisTemplate、集群时一台master宕机&#xff0c;java报错的情况分析。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文…

linux-02-软件安装-centos7配置jdk、tomcat、mysql、lrzsz、项目部署(Git、Maven)、shell脚本自动从git仓库获取项目更新★

文章目录 Linux-Day02课程内容1. 软件安装1.1 软件安装方式1.2 安装JDKshell脚本里写 cd命令不生效 1.3 安装Tomcat1.3.1 Tomcat安装好多方便的自定义命令:1.3.2 Tomcat进程查看1.3.3 防火墙操作1.3.4 停止Tomcat 1.4 安装MySQL1.4.1 MySQL安装1.4.2 MySQL启动1.4.3 MySQL登录1…

几何算法——7.Blending(倒角)的调研、设计及算法

几何算法——7.Blending&#xff08;倒角&#xff09;的调研、设计及算法 1 Parasolid的Blending1.1 关于Parasolid的BlendSurface1.2 Edge Blending1.2.1 Rolling-ball blends1.2.2 Variable rolling-ball blends1.2.3 Chamfers1.2.3.1 face offset chamfers1.2.3.2 apex-rang…

自学黑客/网络渗透,一般人我劝你还是算了

写在开篇 笔者本人 17 年就读于一所普通的本科学校&#xff0c;20 年 6 月在三年经验的时候顺利通过校招实习面试进入大厂&#xff0c;现就职于某大厂安全实验室。 我为啥说自学黑客&#xff0c;一般人我还是劝你算了吧。因为我就是那个不一般的人。 首先我谈下对黑客&…

AMBA总线协议AXI——学习笔记

文章目录 前言一、AXI&#xff08;Advanced eXtensible Interface&#xff09;1、定义2、信号2.1 全局信号2.2 写数据通路2.3 写地址通道2.4 写回复通道2.5 读数据通道2.6 读地址通道2.7 低功耗接口信号 3、AXI-Lite协议特点4、读写时序图4.1 读burst4.2 读重叠burst4.3 写burs…

Baklib母公司探码科技荣获甲子光年:2023中国AI数据平台创新企业

4月25日&#xff0c;由中国科技产业智库「甲子光年」主办、上海市信息服务业行业协会支持的「共赴山海2023甲子引力X智能新世代」峰会在上海召开。峰会上为了表彰在AI领域中取得卓越成就的公司&#xff0c;甲子光年在峰会现场颁布了星辰20&#xff1a;创新企业&#xff0c;表彰…

鹏程·盘古

鹏程盘古模型基于 1.1 TB 高质量中文训练数据&#xff0c;采用全场景人工智能计算框架 MindSpore 自动并行技术实现了五维并行训练策略&#xff0c;从而可将训练任务高效扩展到 4 096 个处理器上。 对比实验表明&#xff0c;在少样本或零样本情况下&#xff0c;鹏程盘古模型在…

Mysql高级知识-------索引

mysql索引的创建&#xff0c;新增&#xff0c;删除 查询索引&#xff1a; 语法&#xff1a; show index from 表 主要参数&#xff1a; 新建表中添加索引 ① 普通索引 create table t_dept( no int not null primary key, name varchar(20) null, sex varchar(2) null, inf…

讯飞星火大模型申请及测试:诚意满满

“ 大家好&#xff0c;我是可夫小子&#xff0c;关注AIGC、读书和自媒体。解锁更多ChatGPT、AI绘画玩法。加&#xff1a;keeepdance&#xff0c;备注&#xff1a;chatgpt&#xff0c;拉你进群。 最近国产大模型跟下饺子似&#xff0c;隔几天就发布一个。厂家发布得起劲&#xf…

ArduPilot之posHoldRTL实测

ArduPilot之posHold&RTL实测 1. 源由2. 模式配置3. 测试步骤4. 飞行实测5. 总结6. 参考资料7. 附录-关于QGC 暂不支持MAVLink2 signing Protocol问题7.1 问题描述7.2 硬件配置7.3 逻辑分析7.4 配置Signature7.5 总结&#xff08;QGC目前尚不支持MAVLink2 Signature&#xf…

算法——分布式——一致性哈希、一致性hash图解动画

分布式算法——一致性哈希、一致性Hash 概述传统Hash算法算法步骤生成Hash环定位服务器定位数据和映射服务器 服务器变更Hash环倾斜虚拟节点总结 概述 一致性哈希算法在1997年由麻省理工学院提出&#xff0c;是一种特殊的哈希算法&#xff0c;目的是解决分布式缓存的问题。在移…