python 和 scikit-learn 实现垃圾邮件过滤
文本挖掘(Text Mining,从文字中获取信息)是一个比较宽泛的概念,这一技术在如今每天都有海量文本数据生成的时代越来越受到关注。目前,在机器学习模型的帮助下,包括情绪分析,文件分类,话题分类,文本总结,机器翻译等在内的诸多文本挖掘应用都已经实现了自动化。
在这些应用中,垃圾邮件过滤算是初学者实践文件分类的一个很不错的开始,例如 Gmail 账户里的“垃圾邮箱”就是一个垃圾邮件过滤的现实应用。下面我们将基于一份公开的邮件数据集 Ling-spam,编写一个垃圾邮件的过滤器。Ling-spam 数据集的下载地址如下:
http://t.cn/RKQBl9c
这里我们已经从 Ling-spam 中提取了相同数量的垃圾邮件和非垃圾邮件,具体下载地址如下:
http://t.cn/RKQBkRu
下面我们将通过以下几个步骤,编写一个现实可用的垃圾邮件过滤器。
1.准备文本数据;
2.创建词典(word dictionary);
3.特征提取;
4.训练分类器。
最后,我们会通过一个测试数据集对过滤器进行验证。
1. 准备文本数据
这里我们将数据集分成了训练集(702封邮件)和测试集(260封邮件)两部分,其中垃圾和非垃圾邮件各占 50%。这里因为每个垃圾邮件的数据集都以 spmsg 命名,因此很容易区分。
在大部分的文本挖掘问题中,文本清理都是第一步,即首先要清理掉那些与我们的目标信息无关的词句,本例中也一样。通常邮件里一般都会包含很多无用的字符,比如标点符号,停用词,数字等等,这些字符对检测垃圾邮件没什么帮助,因此我们需要将它们清理掉。这里 Ling-spam 数据集里的邮件已经经过了以下几个步骤的处理:
a) 清除停用词 --- 像 "and", "the", "of" 等这些停用词在英语语句中非常常见。然而,这些停用词对于判定邮件的真实身份并没有什么卵用,所以这些词已经从邮件中被移除。
b) 词形还原 --- 这是一种把同一个词的不同形式组合在一起,以便被当做一个单独项目来分析的过程。举个栗子,"include", "includes" 和 "included" 就可以全部用 "include" 来代表。与此同时,语句的上下文含义也会通过词形还原的方法保留下来,这一点不同于词干提取 (stemming) 的方法(注:词干提取是另一种文本挖掘的方法,此法不考虑语句的含义)。
此外,我们还需要移除一些非文字类的符号(non-words),比如标点符号或者特殊字符之类的。要实现这一步有很多方法,这里,我们将首先创建一个词典(creating a dictionary),之后再移除这些非文字类的符号。需要指出的是,这种做法其实非常方便,因为当你手上有了一个词典之后,对于每一种非文字类符号,只需要移除一次就 ok 了。
2. 创建词典(Creating word dictionary)
一个数据集里的样本邮件一般长这样:
Subject: posting
hi , ' 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
你会发现邮件的第一行是标题,从第三行开始才是正文。这里我们只在邮件正文内容的基础上做文本分析,来判定该邮件是否为垃圾邮件。第一步,我们需要创建一个文字的词典和文字出现的频率。为了创建这样一个“词典”,这里我们利用了训练集里的 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 belowfeifanshifan8.cn)
return dictionary
词典创建好之后,我们只要在上面函数的基础上再加几行代码,就可以移除之前提到的那些非文字类符号了。这里我还顺手删掉了一些与垃圾邮件的判定无关的单字符,具体参见如下的代码,注意这些代码要附在 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 www.yongshiyule178.com), ('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 的位置,其他位置显示为 0。
下面这个 python 函数会帮助我们生成一个特征向量矩阵,该矩阵有 700 行 3000 列。其中每一行代表训练集中 700 封邮件的的每一封邮件,每一列代表词典中的 3000 个关键词。在 “ij” 位置上的值代表了词典中第 j 个词在该邮件(第 i 封)中出现的次数。
def extract_features(mail_dir):
files = [os.path.join(mail_dir,fi) for fi in os.listdir(mail_dir)www.dajinnylee.cn]
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 www.6788878.cn)
docID = docID + 1
return features_matrix
4.训练分类器
在这里我们会使用 scikit-learn 机器学习库来训练分类器,scikit-learn 库的相关链接如下:
http://t.cn/SMzAoZ
这是一个绑定在第三方 python 发行版 Anaconda 的开源机器学习库,可以跟随 Anaconda 一同下载安装,或者也可以按照以下链接中的提示独立安装:
http://t.cn/8kkrVlQ
安装好了之后,我们只需要将其 import 到我们的程序中就可以使用了。
这里我们训练了两个模型,分别是朴素贝叶斯分类器和 SVM(支持向量机)。朴素贝叶斯分类器是一个传统的监督型概率分类器,在文本分类的场景中非常常用,它基于贝叶斯定理,假设每一对特征都是相互独立的。SVM 是监督型的二分类器,面对特征数量较多的场景时非常有效,其最终目标是从训练数据中分离出一组子集,称为支持向量(分离超平面的边界)。判定测试数据最终类别的 SVM 决策函数正是基于该支持向量和内核技巧(kernel trick)的。
分类器训练完成后,我们可以在测试集上测试模型的性能。这里我们为测试集中的每封邮件提取字数向量,然后用训练好的朴素贝叶斯分类器和 SVM 模型,预测它的类别(普通邮件或垃圾邮件)。下面是垃圾邮件分类器的完整 python 代码,另外还需要包含我们在步骤 2 和步骤 3 中定义的两个函数。
import os
import numpy as np
from collections import Counter
from sklearn.naive_bayes import MultinomialNB, G www.aomenyonli.cn aussianNB, BernoulliNB
from sklearn.svm import SVC, NuSVC, LinearSVC
from sklearn.metrics import confusion_matrix
# Create a dictionary of words with its frequency
train_dir = 'train-mails'
dictionary = make_Dictionary(train_dir)
# Prepare feature vectors per training mail and its labels
train_labels = np.zeros(702)
train_labels[351:701] = 1
train_matrix = extract_features(train_dir)
# Training SVM and Naive bayes classifier
model1 = MultinomialNB()
model2 = LinearSVC()
model1.fit(train_matrix,train_labels)
model2.fit(train_matrix,train_labels)
# Test the unseen mails for Spam
test_dir = 'test-mails'
test_matrix = extract_features(test_dir)
test_labels = np.zeros(260)
test_labels[130:260] = 1
result1 = model1.predict(test_matrix)
result2 = model2.predict(test_matrix)
print confusion_matrix(test_labels,result1)
print confusion_matrix(test_labels,result2)
性能测试
这里我们的测试集中包含 130 封垃圾邮件和 130 封非垃圾邮件,如果你已经顺利完成了之前的所有步骤,那么你将会得到如下的结果。这里显示的是两个模型在测试数据中的混淆矩阵,对角元素代表了正确识别的邮件数,非对角元素代表的则是错误的分类。
可以看到,两个模型在测试集上有着相近的性能,但 SVM 更倾向垃圾邮件的判定。需要注意的是,这里的测试数据集既没有用于创建字典,也没有用于模型训练。
拓展
感兴趣的朋友可以按照上文所述的步骤进行一些拓展,这里介绍拓展相关的数据库和结果。
拓展使用的是已经预处理好的 Euron-spam 数据库,其中包含了 6 个目录,33716 封邮件,每个目录中都包含非垃圾邮件和垃圾邮件子目录,非垃圾邮件和垃圾邮件的总数分别为 16545 封和 17171 封。Euron-spam 库的下载链接如下:
http://t.cn/RK84mv6
需要注意的是,由于 Euron-spam 数据库的组织形式有别于上文提到的 ling-spam 库,因此上文的一些函数也需要做少量的修改才能应用于 Euron-spam。
这里我们将 Euron-spam 数据库按照 3:2 的比例分为训练集和测试集,按照上文的步骤,我们在 13478 封测试邮件中得到了如下结果:
可以看到,SV www.senta77.com M 的表现略胜于朴素贝叶斯。
总结
在本文中我们尽量保持简单易懂的叙述,省略了许多技术性强的讲解和名词。我们希望这是一篇简单易懂的教程,希望这篇教程可以对文本分析感兴趣的初学者们有所裨益。
有些朋友可能会对朴素贝叶斯模型和 SVM 模型背后的数学原理感到好奇,这里需要指出的是,SVM 在数学上属于比较复杂的模型,而朴素贝叶斯则相对更容易理解一些。我们当然鼓励对数学原理感兴趣的朋友们深入探索,关于这些数学模型网上有非常详细的教程和实例。除此之外,采用不同的方式实现同一个目标,也是一种很好的研究方法。例如可以调节如下一些参数,观察它们对垃圾邮件过滤的实际效果的影响:
a) 训练数据的大小
b) 词典的大小
c) 不同的机器学习模型,包括 GaussianNB,BernoulliNB,SVC
d) 不同的 SVM 模型参数
e) 删除无关紧要的词来改进词典 (例如手动删除)
f) 采用其他特征模型 (寻找 td-idf)
最后,博客中提到的完整 python 代码详见如下链接:
http://t.cn/R6ZeuiN
若有问题,欢迎在文末留言讨论。
雷锋网(公众号:雷锋网)相关阅读:
Google:机器学习检测垃圾邮件准确率已达99.9%
python 和 scikit-learn 实现垃圾邮件过滤的更多相关文章
- WEKA使用(基础配置+垃圾邮件过滤+聚类分析+关联挖掘)
声明: 1)本文由我bitpeach原创撰写,转载时请注明出处,侵权必究. 2)本小实验工作环境为Windows系统下的WEKA,实验内容主要有三部分,第一是分类挖掘(垃圾邮件过滤),第二是聚类分析, ...
- 基于C#的机器学习--垃圾邮件过滤
在这一章,我们将建立一个垃圾邮件过滤分类模型.我们将使用一个包含垃圾邮件和非垃圾邮件的原始电子邮件数据集,并使用它来训练我们的ML模型.我们将开始遵循上一章讨论的开发ML模型的步骤.这将帮助我们理解工 ...
- Machine Learning for hackers读书笔记(三)分类:垃圾邮件过滤
#定义函数,打开每一个文件,找到空行,将空行后的文本返回为一个字符串向量,该向量只有一个元素,就是空行之后的所有文本拼接之后的字符串 #很多邮件都包含了非ASCII字符,因此设为latin1就可以读取 ...
- 使用Python登陆QQ邮箱发送垃圾邮件 简单实现
参考:Python爱好者 知乎文章 需要做的是: 1.邮箱开启SMTP功能 2.获取授权码 上述两步百度都有. 源码: #!/usr/bin/env python from email.mime.te ...
- Atitti 文本分类 以及 垃圾邮件 判断原理 以及贝叶斯算法的应用解决方案
Atitti 文本分类 以及 垃圾邮件 判断原理 以及贝叶斯算法的应用解决方案 1.1. 七.什么是贝叶斯过滤器?1 1.2. 八.建立历史资料库2 1.3. 十.联合概率的计算3 1.4. 十一. ...
- STMP发送邮件被当垃圾邮件处理的解决方法
昨天使用了.Net通过smtp发送邮件的方式发送了一封邮件到自己的QQ邮箱,但是发送成功后并没有提示邮箱收到新的邮件,而且去收件箱里面也没有新增的邮件. 这让本人觉得奇怪,所以就觉得是否被当作垃圾邮件 ...
- Office 2010 垃圾邮件过滤设置
垃圾邮件过滤设置 有同事反馈给我,某些时候应该收到的邮件,却到了垃圾邮件里,给工作带来了不便,下面简单介绍一下outlook 2010 有关垃圾邮件的过滤设置. 1: 找到相关的邮件,点右键,在”垃圾 ...
- Linux中Postfix反病毒和垃圾邮件工具(十)
amavisd-new amavisd-new呼叫器是一个连接MTA和内容检测工具(诸如病毒扫描工具和SpamAssassin)的高性能接口程序,使用perl语言写成.它一般通过SMTP.ESMTP或 ...
- Linux中Postfix反病毒和垃圾邮件(十)
amavisd-new amavisd-new呼叫器是一个连接MTA和内容检测工具(诸如病毒扫描工具和SpamAssassin)的高性能接口程序,使用perl语言写成.它一般通过SMTP.ESMTP或 ...
随机推荐
- BZOJ 5475: [WC 2019] 数树
WC2019 数树 解决了一个心头大患 考试的时候本人太智障了QwQ 本文的参考链接,膜了一发rqy的题解 题目链接 Subtask 0 好像可以直接做... 推一推就能发现,是$y^k$,其中$k$ ...
- Vue-父子组件传值
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递.一.父组件向子组件传值 使用 Prop 传递数据,父组件的数据需要通过 prop 才能下发到子组件中,子组件要显式地用 pr ...
- 反射反射,程序员的快乐+反射案例:打印和Excel导出
还是那几句话: 学无止境,精益求精 十年河东,十年河西,莫欺少年穷 学历代表你的过去,能力代表你的现在,学习代表你的将来 看过设计模式的童鞋都知道:反射反射,程序员的快乐!今天我们就利用反射来制作打印 ...
- 51nod 1295 XOR key 可持久化01字典树
题意 给出一个长度为\(n\)的正整数数组\(a\),再给出\(q\)个询问,每次询问给出3个数,\(L,R,X(L<=R)\).求\(a[L]\)至\(a[R]\)这\(R-L+1\)个数中, ...
- Spark在Windows下的环境搭建(转)
原作者:xuweimdm 原文网址:http://blog.csdn.net/u011513853/article/details/52865076 由于Spark是用Scala来写的,所以Spa ...
- C#大型电商项目优化(三)——扩展性与支付
上一篇文章引来不少非议,笔者并非对EF有看法,而是针对不同的业务场景和框架背景,挑选不同的方案.每个方案都有其优势劣势,挑选最快速,最简单的方案,是笔者的初衷. 看评论也是学习的过程,然而有些只做评价 ...
- springboot undertow替换tomcat方式
版权声明: https://blog.csdn.net/weixin_38187317/article/details/81532560说明 undertow,jetty和tomcat可 ...
- OneZero第一次随感
>本人基础薄弱,有幸加入这个团队,甚感欣慰.这是本人第一次尝试写博客,说实话,胆怯.因为能力有限,怕技能匮乏,说不好.但是我知道既然加入这个团队,就要为团队负责.万事开头难,过程也挺难(就我个人 ...
- 素数问题练习_HDOJ1262
HDOJ1262_寻找素数对 和上一篇博客一样的解法,将10000以内的所有素数求出即可解题. #include<stdio.h> #include<stdlib.h> #in ...
- 2017BUAA软工个人作业Week1
大概的功能已经满足 暂时只能用debug中的exe文件 正在改进... https://github.com/qwellk/project1/tree/product1 PSP2.1 Personal ...