技术标签: 机器学习 朴素贝叶斯算法 TF-IDF 垃圾邮件分类 分类
本项目基于朴素贝叶斯算法来解决垃圾邮件分类问题,并使用混淆矩阵进行了验证,得到了非常好的准确率和召回率(96%和97%)。此外还开发了一个可视化的垃圾邮件分类系统界面,使用PyQT进行界面设计。
本博客对基于朴素贝叶斯的垃圾邮件分类系统的开发过程进行了详细的总结,从原理到实现每一步都有记录,看完本篇博客保证各位读者对这方面知识有着更深入的了解。另外,只要跟着我一步一步做,各位读者也可以实现基于朴素贝叶斯的垃圾邮件分类系统!
使用朴素贝叶斯分类器对垃圾邮件训练和测试的结果:
贝叶斯分类器混淆矩阵:
对非垃圾邮件的分类:
对垃圾邮件的分类:
本项目所采用的数据集来源为国际文本检索会议提供的一个公开的垃圾邮件语料库,可以进入官网下载此数据集,此数据集是一个广泛使用的电子邮件数据集,用于测试垃圾邮件过滤算法的效果。该数据集包含了大约10000个标记的开发集和50000个未标记的测试集,共60000余封测试邮件。其中约40%的电子邮件消息被标记为垃圾邮件,其余的则被标记为非垃圾邮件。而我们本项目所采用的数据集为其中的trec06c中文邮件数据集,如果想进行英文的垃圾邮件分类,可以使用其中的trec06p数据集。
此数据集被广泛用于各种研究项目中,例如垃圾邮件过滤、邮件分类和特征提取等。此外,该数据集还被用于比较不同算法的性能,如支持向量机、朴素贝叶斯、决策树等。但是我们在使用的时候需要对电子邮件进行一些预处理操作,例如去除HTML标记、停用词过滤和词干提取。评估指标包括精确度、召回率、F1分数等。根据之前的研究结果,该数据集的分类性能通常在90%左右。
可以直接使用上面提供的下载链接下载数据集,下文我会详细介绍如何对此数据集进行预处理和具体的使用。下面就是本数据集中的中文邮件的一个示例:
条件概率是指在某种条件下发生某一事件的概率,通常用符号 P ( A ∣ B ) P(A|B) P(A∣B)表示,在事件 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(A∣B)=P(B)P(A∩B)
其中, P ( A ∩ B ) P(A \cap B) P(A∩B)表示事件 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。
全概率公式是概率论中一个重要的公式,用于计算某个事件的概率。它可以用于处理复杂的概率问题,特别是当我们无法直接计算事件的概率时,可以通过全概率公式来计算。全概率公式可以表示为:
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=1∑nP(A∣Bi)∗P(Bi)
其中, P ( A ) P(A) P(A)表示事件 A A A发生的概率, P ( A ∣ B i ) P(A|B_i) P(A∣Bi)表示在事件 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(喜欢计算机)=21∗31+31∗31+41∗31=3613
因此,随机选取一个学生,他或她喜欢计算机的概率是 13 36 \frac{13}{36} 3613。
现在我们已经掌握了条件概率公式和全概率公式,下面让我们再进一步,学习一下朴素贝叶斯公式。朴素贝叶斯公式是一种基于贝叶斯定理的简单概率分类器。朴素贝叶斯假设每个特征在给定类别下都是条件独立的。尽管这个假设在现实世界中往往不成立,但朴素贝叶斯在许多实际问题中表现出惊人的性能。朴素贝叶斯公式如下:
P ( A ∣ B ) = P ( B ∣ A ) × P ( A ) P ( B ) P(A|B) = \frac{P(B|A) \times P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)×P(A)
其中, P ( A ∣ B ) P(A|B) P(A∣B)是后验概率,表示在给定 B B B的情况下, A A A发生的概率; P ( B ∣ A ) P(B|A) P(B∣A)是条件概率,表示在给定 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 ( 戴眼镜 ) 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%。
当我们掌握以上知识点后,就可以使用朴素贝叶斯公式来解决垃圾邮件分类的问题。在正式解决这个问题之前我们先思考,作为人来说,我们如何对一个事物进行分类呢?比如要从各种植物中识别出草、花、树等等,可以从颜色、形状等等特征分析;从各种动物中识别出猫、狗、鸡等等,可以从叫声、长相等等特征分析;当然,将这个思想推广到邮件分类问题上,人也可以根据邮件中的具体内容对邮件进行分类判断,例如,如果某封邮件中充斥着:“今天一律五折、期待您的回访、欢迎购买我公司产品”等等句子,很明显这是一封垃圾邮件,那么我们如何让计算机也可以像人一样识别什么样的邮件是垃圾邮件呢?
其实思想很简单,我们人对一封邮件进行分类的依据就是邮件中的关键词,如果某封邮件中多次出现:“天气、心情、生活”等等有关日常问候的词语,我们就可以认为这是一封简单的日常交流的邮件,这并不是一封垃圾邮件,而如果某封邮件中多次出现:“打折、房价、发票”等等无用的词语,我们就可以认为这是一封垃圾邮件。所以我们考虑,是否可以让计算机也可以根据邮件中的关键词对垃圾邮件进行分类呢?答案是可以的,现在我们做如下定义:
如果现在有一封邮件,我们要对其进行分类判断,因为我们现在还不知道这封邮件究竟属于 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=spam∣X=x)=P(X=x∣Y=spam)×P(Y=spam)+P(X=x∣Y=ham)×P(Y=ham)P(X=x∣Y=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=ham∣X=x)=P(X=x∣Y=spam)×P(Y=spam)+P(X=x∣Y=ham)×P(Y=ham)P(X=x∣Y=ham)×P(Y=ham)
最后如果 P ( Y = s p a m ∣ X = x ) P(Y=spam|X=x) P(Y=spam∣X=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=x∣Y=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=argmaxi∏P(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是垃圾邮件还是非垃圾邮件了。
首先导入项目运行所需要的库:
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
然后根据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
然后根据上一步得到的邮件文件路径列表获取每个邮件的文本内容,并对其进行简单的处理,如去除特殊字段、换行符等,最终得到每个邮件的正文内容列表。另外,需要注意的是,只有第一次运行的时候需要调用此函数,因为邮件正文只是为了分词使用,而后面我们已经将分词结果保存到本地了,所以使用的时候直接调用即可,不用每次都加载,这样可以节省时间:
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")
然后使用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
然后计算邮件文本中词语的词频(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()
函数中的参数值简单介绍一下:
然后计算邮件文本中词语的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_{某个词语} TF−IDF某个词语=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
为了可视化展示我们的分类测试结果,我们可以计算一下混淆矩阵(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()
然后使用贝叶斯分类器对垃圾邮件进行分类,在分类的时候需要使用刚刚计算的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()
绘制混淆矩阵当我们完成各个子模块的功能后,就可以将这些子模块进行整合,来完成垃圾邮件分类任务了,主函数中各个子模块的调用顺序和上面各个子模块介绍的顺序一致,我只是额外增加了各个子步骤的运行时间计算。另外,需要注意的是,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)
最后我们可以运行主函数来看一下模型的训练结果:
可以看到整个项目运行时间还是蛮快的,如果加上刚才的三个函数时间就会慢很多。当然,时间并不是重点,我们可以观察到,我们的模型的准确率为96%,召回率为97%,这也就意味着我们的模型训练结果还是不错的,这次实验很成功:
我们再来看生成的混淆矩阵,可以看到所有的邮件几乎都分类成功了,这也从可视化的角度证明我们训练的模型的分类准确率非常高:
首先导入项目运行所需要的库:
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")
然后对测试文本进行预处理并计算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
然后直接调用训练好的模型进行测试,我已经将训练好的模型保存到项目的主目录了。当然,读者也可以使用自己训练的模型测试,具体方法自行搜索,比较简单,因为这部分内容不是本篇博客重点,所以不在此赘述:
def predict_res(mail):
"""
使用训练好的模型对传入的测试邮件进行分类预测
:param mail: 待测试邮件
:return: 分类预测结果
"""
classifier = joblib.load("model/classifier.pkl")
res = classifier.predict(mail)
return res
然后绘制测试窗口,这部分内容也不是本篇博客的重点内容,所以也不在此赘述,唯一需要介绍的就是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)
然后直接使用主函数调用上面介绍的绘制窗口的模块即可(预测功能模块已经嵌入到绘制窗口模块中了):
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
最后我们可以运行一下主函数,来看一下运行效果:
当我们输入一段非垃圾邮件的时候,窗口输出“这不是垃圾邮件”,表明预测成功:
当我们输入一段垃圾邮件的时候,窗口输出“这是垃圾邮件”,表明预测成功:
目前看来我们的模型训练效果还不错,但是还是存在问题,当我们在进行可视化的邮件分类的时候,需要输入一封测试邮件,我们需要计算这封测试邮件分词后的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))
这样虽然可以成功输出预测分类值,但是测试分类的结果总是很差,但是我目前也没有想到更好的办法去解决这个问题,如果各位读者有好的办法,欢迎随时和我交流!
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:IronmanJay
# email:[email protected]
# 导入程序运行必需的库
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)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:IronmanJay
# email:[email protected]
# 导入程序运行必需的库
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_())
以上就是基于朴素贝叶斯的垃圾邮件分类系统项目的全部开发教程,如果各位读者有什么疑问,欢迎随时和我私信或者留言,或者对我的问题有什么好的思路,也欢迎随时联系我。这篇博客就告一段落了,下篇博客见!
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线