余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

文本分析初学者教程:用Python实现垃圾邮件过滤器

xiyangw 2022-11-24 16:23 376 浏览 301 评论

机器之心报道


本文介绍了如何通过 Python 和 scikit-learn 实现垃圾邮件过滤的一种方法。对比和分析了两个分类器的结果:多项式朴素贝叶斯和支持向量机。

文本挖掘(text mining,从文本中导出信息)是一个广泛的领域,因为不断产生的巨量文本数据而已经得到了普及。情绪分析、文档分类、主题分类、文本概括、机器翻译等许多任务的自动化都已经通过机器学习得到了实现。

垃圾邮件过滤(spam filtering)是文档分类任务的入门级示例,其涉及了将电子邮件分为垃圾邮件或非垃圾邮件(也称为 ham)。你的 Gmail 账户的垃圾邮箱就是最好的例子。那么让我们在公开的邮件语料库上构建垃圾邮件过滤器吧。我已经从 Ling-spam 语料库(http://www.aueb.gr/users/ion/data/lingspam_public.tar.gz )上提取了同等数量的垃圾邮件和非垃圾邮件。我们从这里下载将要对其操作的提取出来的子集:https://www.dropbox.com/s/yjiplngoa430rid/

我们将通过以下步骤构建此应用程序:

  1. 准备文本数据

  2. 创建词典

  3. 特征提取过程

  4. 训练分类器

此外,我们将在该子集中的测试集上测试我们的结果。


1、 准备文本数据

这里使用的数据集被分为训练集和测试集,分别包含了 702 封邮件和 260 封邮件,其中垃圾邮件和 ham 邮件的数量相等。垃圾邮件的文件名中包含了 *spmsg*,所以很容易识别。

在任何一个文本挖掘问题中,文本清理(text cleaning)是我们从文档中删除那些可能对我们想要提取的信息无用的文字的第一步。电子邮件可能包含了大量对垃圾邮件检测无用的字符,如标点符号、停止词、数字等。 Ling-spam 语料库中的邮件已经通过以下方式进行了预处理。

a) 移除停止词 — 像 “and”、“the”、“of” 之类的停止词在所有的英语句子当中都非常常见,在判定是否为垃圾邮件时没有多少作用,所以这些词已经从电子邮件中删除。

b) 词形还原(lemmatization ) — 这是将一个单词的不同变化形式分组在一起的过程,以便其可被视为单个项进行分析。 例如,“include”、“includes”和“included” 将全部用 “include” 表示。在词形还原中,句子的语境也会得到保留,而词干提取(stemming)则不会。(词干提取是文本挖掘中的另一个术语,其不会考虑句意)。

我们还需要从邮件文档中删除非文字信息,比如标点符号或者特殊字符。有几种方法可以做到这一点。这里,我们将在创建词典后删除这样的词,这非常方便,因为当你有了一个词典时你只需要删除每个这样的单词一次。欢呼吧!!到现在为止,你不需要做任何事情。


2、创建词典

数据集中的示例邮件如下所示:

Subject: postinghi , ' m work phonetics project modern irish ' m hard source . anyone recommend book article english ? ' , specifically interest palatal ( slender ) consonant , work helpful too . thank ! laurel sutton ( sutton @ garnet . berkeley . edu

可以看出,邮件第一行是主题(subject),第三行包含了邮件的正文。我们只会对其内容执行文本分析以检测垃圾邮件。作为第一步,我们需要创建一个词及其频率的词典。对于此任务,我们使用了 700 封邮件作为训练集。这个 Python 函数可为你创建这个词典。

def make_Dictionary(train_dir): emails = [os.path.join(train_dir,f) for f in os.listdir(train_dir)] all_words = [] for mail in emails: with open(mail) as m: for i,line in enumerate(m): if i == 2: #Body of email is only 3rd line of text file words = line.split() all_words += words dictionary = Counter(all_words) # Paste code for non-word removal here(code snippet is given below) return dictionary

.

一旦词典被创建,我们可以添加下面几行代码到上面的函数以删除移除非词(如第 1 步所示)。我也删除了词典中不合理的单个字符,这些字符在这里是不相关的。别忘了在函数def make_Dictionary(train_dir)中插入以下代码。

list_to_remove = dictionary.keys()for item in list_to_remove: if item.isalpha() == False: del dictionary[item] elif len(item) == 1: del dictionary[item]dictionary = dictionary.most_common(3000)

可以通过命令print dictionary显示词典。你也许会发现一些不合理的单词数很多,但是别担心,这只是一个词典并且稍后你可以改进它。如果你是按照这篇文章说的那样操作的并且使用了我提供的数据集,那么请确保你的词典中包含以下最常用的单词的条目。这里,我已经选择了 3000 个词典中最常用的词。

[('order', 1414), ('address', 1293), ('report', 1216), ('mail', 1127), ('send', 1079), ('language', 1072), ('email', 1051), ('program', 1001), ('our', 987), ('list', 935), ('one', 917), ('name', 878), ('receive', 826), ('money', 788), ('free', 762)

3、特征提取过程

一旦词典准备就绪,我们可以为训练集的每封邮件提取 3000 维的词计数向量(word count vector,这里是我们的特征)。每个词计数向量包含了训练文件中的 3000 个单词的频率。当然,你现在可能已经猜到了它们大部分是 0。让我们举个例子。假设我们的词典中有 500 个词。每个词计数向量包含训练文件中 500 个字典词的频率。 假设训练文件中的文本是“Get the work done, work done”,那么它将被编码为[0,0,0,0,0,... .0,0,2,0,0,0,......, 0,0,1,0,0,... 0,0,1,0,0,... 2,0,0,0,0,0]。 在这个 500 长度的词计数向量中,只有的第 296、359、415、495 位有词的计数值,其余位置都是零。

下面的 Python 代码将生成一个特征向量矩阵,其中行表示训练集的 700 个文件,列表示词典的 3000 个词。索引“ij”处的值将是第 i 个文件中词典的第 j 个词的出现次数。

def extract_features(mail_dir): files = [os.path.join(mail_dir,fi) for fi in os.listdir(mail_dir)] features_matrix = np.zeros((len(files),3000)) docID = 0; for fil in files: with open(fil) as fi: for i,line in enumerate(fi): if i == 2: words = line.split() for word in words: wordID = 0 for i,d in enumerate(dictionary): if d[0] == word: wordID = i features_matrix[docID,wordID] = words.count(word) docID = docID + 1 return features_matrix

4、训练分类器

这里,我将使用 scikit-learn 机器学习库(http://scikit-learn.org/stable/ )训练分类器。这是一个开源 Python 机器学习库,其被捆绑在第三方分布 anaconda(https://www.continuum.io/downloads )中,也可以按这个教程单独安装使用:http://scikit-learn.org/stable/install.html 。一旦安装,我们只需要将其导入到我们的程序中即可。

我已经训练了两个模型,即朴素贝叶斯分类器(Naive Bayes classifier)和支持向量机(SVM)。对于文档分类问题,朴素贝叶斯分类器是一种常规的并且非常流行的方法。它是一个基于贝叶斯定理的监督概率分类器,其假设每对特征之间是独立的。支持向量机是监督式的二元分类器,在你拥有更多的特征时它非常有效。支持向量机(SVM)的目标是将训练数据中的一些子集从被称为支持向量(support vector,分离超平面的边界)的剩余部分分离。预测测试数据类型的支持向量机模型的决策函数基于支持向量并且利用了核技巧( kernel trick)。

一旦分类器训练完毕,我们可以在测试集上检查模型的表现。我们提取了测试集中的每一封邮件的词计数向量,并使用训练后的朴素贝叶斯(NB)分类器和支持向量机模型预测其类别(ham 邮件或垃圾邮件)。以下是垃圾邮件过滤应用程序的完全代码。你必须包含我们在之前的步骤 2 和步骤 3 定义的两个函数。

import osimport numpy as npfrom collections import Counterfrom sklearn.naive_bayes import MultinomialNB, GaussianNB, BernoulliNBfrom sklearn.svm import SVC, NuSVC, LinearSVC# Create a dictionary of words with its frequencytrain_dir = 'train-mails'dictionary = make_Dictionary(train_dir)# Prepare feature vectors per training mail and its labelstrain_labels = np.zeros(702)train_labels[351:701] = 1train_matrix = extract_features(train_dir)# Training SVM and Naive bayes classifiermodel1 = MultinomialNB()model2 = LinearSVC()model1.fit(train_matrix,train_labels)model2.fit(train_matrix,train_labels)# Test the unseen mails for Spamtest_dir = 'test-mails'test_matrix = extract_features(test_dir)test_labels = np.zeros(260)test_labels[130:260] = 1result1 = model1.predict(test_matrix)result2 = model2.predict(test_matrix)print confusion_matrix(test_labels,result1)print confusion_matrix(test_labels,result2)

测试表现水平

测试集包含 130 封垃圾邮件 和 130 封非垃圾邮件。如果你已经走到了这一步,你将会发现以下结果。我已经展示出了对这两个模型的训练集的混淆矩阵(confusion matrix )。对角元素表示正确识别(也叫真识别)的邮件,其中非对角元素表示邮件的错误分类(假识别)。

多项式朴素贝叶斯HamSpam
Ham1291
Spam9121
支持向量机(线性)HamSpam
Ham1264
Spam6124


除了 SVM 具有稍微平衡的假识别之外,这两个模型在测试集上具有相似的表现。我必须提醒你,测试数据既没有在创建词典使用,也没有用在训练集中。


你的任务

下载 Euron-spam 语料库(http://www.aueb.gr/users/ion/data/enron-spam/ )的预处理表格。该语料库在 6 个目录中包含了 33716 封邮件,其中包含 “ham” 和“spam” 文件夹。非垃圾邮件和垃圾邮件的总数分别为 16545 和 17171。

遵循本文章中描述的相同步骤,并检查它如何执行支持向量机和多项式朴素贝叶斯模型。由于该语料库的目录结构不同于博客文章中使用的 ling-spam 子集的目录结构,因此你可能需要重新对其组织或在def make_Dictionary(dir)def extract_features(dir)函数中进行修改。

我将 Euron-spam 语料库以 60:40 的比例分成训练集和测试集。执行本博客的相同步骤后,我在 13487 封测试集邮件中得到以下结果。我们可以看到,在正确检测垃圾电子邮件方面的表现,支持向量机(SVM)略优于朴素贝叶斯分类器。

多项式朴素贝叶斯HamSpam
Ham6445225
Spam1376680
支持向量机(线性)HamSpam
Ham6490180
Spam1096708

最后的感想

我试图保持教程的简洁性。希望对文本分析感兴趣的初学者可以从这个应用程序开始。

你可能会思考朴素贝叶斯和支持向量机(SVM)背后的数学技术。 支持向量机(SVM)在数学上是较为复杂的模型,但朴素贝叶斯相对容易理解。我鼓励你从在线资源中学习这些模型。除此之外,你可以进行很多实验以便发现各种参数的效果,比如

  • 训练数据的数量

  • 词典的大小

  • 不同的机器学习技术,比如 GaussianNB、BernoulliNB、SVC)

  • 对支持向量机模型参数进行调优

  • 通过消除不重要的词(可以手动)以改进词典

  • 一些其它的特征(可搜索 td-idf 了解)

你可以从 GitHub 获得这两种语料库的完整 Python 实现:https://github.com/abhijeet3922/Mail-Spam-Filtering

相关推荐

spring利用spring.handlers解析自定义配置(spring validation 自定义)

一、问题我们在spring的xml配置文件里经常定义各种各样的配置(tx、bean、mvc、bean等等)。以及集成第三方框架时,也会看到一些spring之外的配置,例如dubbo的配置、securi...

「Spring源码分析」AOP源码解析(上篇)(spring源码深度解析(第2版))

前言前面写了六篇文章详细地分析了SpringBean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析。为了探究AOP实现原理,首先定义几个类,一个Dao接口:1&nbs...

Spring 解析注册BeanDefinition这一篇就Over
Spring 解析注册BeanDefinition这一篇就Over

一、简介:学习过Spring框架的人一定都会听过Spring的IoC(控制反转)、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC、...

2023-03-20 14:53 xiyangw

域、模块、空间、闭包,你真的懂了吗?(模块控制域与作用域的关系)

Javascript有一个特性叫做域。尽管对于初学者来说理解域是有难度的,但我会尽力用最简单的方式让你理解域。理解域能让你的代码更优秀,减少错误,及有助于你做出更强大的模式设计。什么是域域是在运行时,...

这一次搞懂Spring自定义标签以及注解解析原理
这一次搞懂Spring自定义标签以及注解解析原理

前言在上一篇文章中分析了Spring是如何解析默认标签的,并封装为BeanDefinition注册到缓存中,这一篇就来看看对于像context这种自定义标签是如...

2023-03-20 14:53 xiyangw

前端基础进阶(七)-前端工程师最容易出错的问题-this关键字
前端基础进阶(七)-前端工程师最容易出错的问题-this关键字

我们在学习JavaScript的时候,因为对一些概念不是很清楚,但是又会通过一些简洁的方式把它给记下来,那么这样自己记下来的概念和真正的概念产生了很强的偏差.当...

2023-03-20 14:52 xiyangw

深入K8s:守护进程DaemonSet及其源码分析(k8s 进程)
深入K8s:守护进程DaemonSet及其源码分析(k8s 进程)

建议学习:膜拜!阿里内部都在强推的K8S(kubernetes)学习指南,不能再详细了最近也一直在加班,处理项目中的事情,发现问题越多越是感觉自己的能力不足,...

2023-03-20 14:52 xiyangw

Spring 是如何解析 bean 标签的?(spring beans标签)
Spring 是如何解析 bean 标签的?(spring beans标签)

前情回顾上回「SpringIoC容器初始化(2)」说到了Spring如何解析我们定义的<bean>标签,代码跟进了一层又一层,跋山涉水,...

2023-03-20 14:52 xiyangw

快速了解JavaScript文本框操作(javascript文本框代码)
快速了解JavaScript文本框操作(javascript文本框代码)

HTML中使用<input>元素表示单行输入框和<textarea>元素表示多行文本框。HTML中使用的<input&...

2023-03-20 14:51 xiyangw

荐读|30道JavaOOP面试题,可以和面试官扯皮了
荐读|30道JavaOOP面试题,可以和面试官扯皮了

面试是我们每个人都要经历的事情,大部分人且不止一次,今天给大家准备了30道JavaOOP面试题,希望能够帮助到对Java感兴趣的同学,让大家在找工作的时候能够...

2023-03-20 14:51 xiyangw

源码系列——mybatis源码刨析总结,下(mybatis源码分析)
源码系列——mybatis源码刨析总结,下(mybatis源码分析)

接上文简答题一.1.Mybatis动态sql是做什么的?1.动态sql就是根据条件标签动态的拼接sql,包括判空,循环,拼接等2.哪些动态sql?动态sql大...

2023-03-20 14:50 xiyangw

Java面试题(第二弹)(java面试题及答案整理)
Java面试题(第二弹)(java面试题及答案整理)

1.抽象类和接口的区别?接口可以被多重implements,抽象类只能被单一extends接口只有定义,抽象类可以有定义和实现接口的字段定义默认为:public...

2023-03-20 14:50 xiyangw

mybatis3 源码深度解析-动态 sql 实现原理(sql数据库基础知识)
mybatis3 源码深度解析-动态 sql 实现原理(sql数据库基础知识)

大纲动态sql使用示例SqlSource和BoundSql以及实现类LanguageDriver以及实现类SqlNode以及实现类动态sql解...

2023-03-20 14:50 xiyangw

第43节 Text、Comment及CDATASection(第43节 Text、Comment及CDATASection)
第43节 Text、Comment及CDATASection(第43节 Text、Comment及CDATASection)

本内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。文本节点用Text类型表示,包含的是可以按字面解释...

2023-03-20 14:49 xiyangw

Qt读写三种文件(qt读取文件数据并赋值给变量)

第一种INI配置文件.ini文件是InitializationFile的缩写,即初始化文件。除了windows现在很多其他操作系统下面的应用软件也有.ini文件,用来配置应用软件以实现不同用户的要...

已有301位网友发表了看法:

取消回复欢迎 发表评论: