目前垃圾邮件检测主要是基于规则以及机器学习,本章主要介绍基于机器学习的检测技术。
垃圾邮件识别使用的数据集为Enron-Spam数据集,Enron-Spam数据集是目前在电子邮件相关研究中使用最多的公开数据集(见图12-2),其邮件数据是安然公司(Enron Corporation,原是世界最大的综合性天然气和电力公司之一,在北美地区是头号天然气和电力批发销售商)150位高级管理人员的往来邮件。这些邮件在安然公司接受美国联邦能源监管委员会调查时被其公布到网上。
机器学习领域使用Enron-Spam数据集来研究文档分类、词性标注、垃圾邮件识别等,由于Enron-Spam数据集都是真实环境下的真实邮件,非常具有实际意义。
Enron-Spam数据集合如图12-3所示,使用不同文件夹区分正常邮件和垃圾邮件。
图12-2 Enron-Spam数据集主页
图12-3 Enron-Spam数据集文件夹结构
正常邮件内容举例如下:
Subject: christmas baskets the christmas baskets have been ordered . we have ordered several baskets . individual earth - sat freeze - notis smith barney group baskets rodney keys matt rodgers charlie notis jon davis move team phillip randle chris hyde harvey freese facilities
垃圾邮件内容举例如下:
Subject: fw : this is the solution i mentioned lsc oo thank you , your email address was obtained from a purchased list , reference # 2020 mid = 3300 . if you wish to unsubscribe from this list , please click here and enter your name into the remove box . if you have previously unsubscribed and are still receiving this message , you may email our abuse control center , or call 1 - 888 - 763 - 2497 , or write us at : nospam , 6484 coral way , miami , fl , 33155 " . 2002 web credit inc . all rights reserved .
Enron-Spam数据集对应的网址为:
http://www2.aueb.gr/users/ion/data/enron-spam/
文本类数据的特征提取有两个非常重要的模型:
·词集模型,单词构成的集合,标记词集中的每个单词是否出现。
·词袋模型,在词集的基础上如果一个单词在文档中出现不止一次,统计其出现的次数(频数)。
两者本质上是有区别的:词袋是在词集的基础上增加了频率的维度;词集只关注有和没有,词袋还要关注有几个。假设我们要对一封邮件进行特征化,最常见的方式就是词袋。
导入相关的函数库:
>>> from sklearn.feature_extraction.text import CountVectorizer
实例化分词对象:
>>> vectorizer = CountVectorizer(min_df=1) >>> vectorizer CountVectorizer(analyzer=...'word', binary=False, decode_error=...'strict', dtype=<... 'numpy.int64'>, encoding=...'utf-8', input=...'content', lowercase=True, max_df=1.0, max_features=None, min_df=1, ngram_range=(1, 1), preprocessor=None, stop_words=None, strip_accents=None, token_pattern=...'(?u)\\b\\w\\w+\\b', tokenizer=None, vocabulary=None)
将文本进行词袋处理:
>>> corpus = [ ... 'This is the first document.', ... 'This is the second second document.', ... 'And the third one.', ... 'Is this the first document?', ... ] >>> X = vectorizer.fit_transform(corpus) >>> X <4x9 sparse matrix of type '<... 'numpy.int64'>' with 19 stored elements in Compressed Sparse ... format>
获取对应的特征名称:
>>> vectorizer.get_feature_names() == ( ... ['and', 'document', 'first', 'is', 'one', ... 'second', 'the', 'third', 'this'])
获取词袋数据,至此我们已经完成了词袋化:
>>> X.toarray() array([[0, 1, 1, 1, 0, 0, 1, 0, 1], [0, 1, 0, 1, 0, 2, 1, 0, 1], [1, 0, 0, 0, 1, 0, 1, 1, 0], [0, 1, 1, 1, 0, 0, 1, 0, 1]]...)
但是如何可以使用现有词袋的特征,对其他文本进行特征提取呢?我们定义词袋的特征空间叫作词汇表vocabulary:
vocabulary=vectorizer.vocabulary_
针对其他文本进行词袋处理时,可以直接使用现有的词汇表:
>>> new_vectorizer = CountVectorizer(min_df=1, vocabulary=vocabulary)
在本例中,将整个邮件包括标题当成一个字符串处理,其中回车和换行需要过滤掉,代码如下:
def load_one_file(filename): x="" with open(filename) as f: for line in f: line=line.strip('\n') line=line.strip('\r') x+=line return x
遍历指定文件夹下全部文件,加载数据:
def load_files_from_dir(rootdir): x=[] list = os.listdir(rootdir) for i in range(0, len(list)): path = os.path.join(rootdir, list[i]) if os.path.isfile(path): v=load_one_file(path) x.append(v) return x
Enron-Spam数据集的数据分散在6个文件夹即Enron1~Enron6中,正常文件在ham文件夹下,垃圾邮件在spam文件夹下,依次加载全部数据,代码如下:
def load_all_files(): ham=[] spam=[] for i in range(1,7): path="../data/mail/enron%d/ham/" % i print "Load %s" % path ham+=load_files_from_dir(path) path="../data/mail/enron%d/spam/" % i print "Load %s" % path spam+=load_files_from_dir(path) return ham,spam
使用词袋模型,向量化正常邮件和垃圾邮件样本,其中ham文件夹下的样本标记为0,表示正常邮件;spam文件夹下的样本标记为1,表示垃圾邮件。
def get_features_by_wordbag(): ham, spam=load_all_files() x=ham+spam y=[0]*len(ham)+[1]*len(spam) vectorizer = CountVectorizer( decode_error='ignore', strip_accents='ascii', max_features=max_features, stop_words='english', max_df=1, min_df=1 ) print vectorizer x=vectorizer.fit_transform(x) x=x.toarray() return x,y
CountVectorize函数比较重要的几个参数为:
·decode_error,处理解码失败的方式,分为‘strict’‘ignore’‘replace’3种方式。
·strip_accents,在预处理步骤中移除重音的方式。
·max_features,词袋特征个数的最大值。
·stop_words,判断word结束的方式。
·max_df,df最大值。
·min_df,df最小值。
·binary,默认为False,当与TF-IDF结合使用时需要设置为True。
本例中处理的数据集均为英文,所以针对解码失败直接忽略,使用ignore方式,stop_words的方式使用english,strip_accents方式为ascii。
在本套图书的第二本《Web安全之机器学习实战》中我们详细介绍了垃圾邮件检测领域经常使用的几种算法,包括MLP、朴素贝叶斯和深度学习的CNN,其中MLP的各方面性能指标都比较均衡,我们这里重点介绍如何使用MLP进行模型训练。
首先,我们定义几个全局参数:max_features表示词袋提取的最大特征数,mode_file表示训练好的MLP模型保存的文件名,vocabulary_file表示词袋模型保存的文件,代码如下:
max_features=15000 mode_file="spam_mlp.pkl" vocabulary_file="spam_vocabulary.pkl"
随机划分数据集,获得训练集和测试集,其中测试集占20%:
train_X, test_X, train_y, test_y = train_test_split(x, y, test_size=0.2, random_state=66)
创建MLP分类器,包含两层隐藏层,节点数分别为5和2:
clf = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes = (5, 2), random_state = 1)
使用训练数据进行模型训练,然后在测试数据集上效果验证,考核指标为准确率和召回率,输出混淆矩阵观察漏报的和误报的个数,代码如下:
clf.fit(x_train, y_train) y_pred = clf.predict(x_test) print "accuracy_score:" print metrics.accuracy_score(y_test, y_pred) print "recall_score:" print metrics.recall_score(y_test, y_pred) print "precision_score:" print metrics.precision_score(y_test, y_pred) print metrics.confusion_matrix(y_test, y_pred)
训练好的模型持久化成模型文件:
joblib.dump(clf,mode_file)
运行程序,训练的效果为:准确率96.24%,召回率96.56%,漏报21个,误报23个。
recall_score: 0.965573770492 precision_score: 0.962418300654 [[1573 23] [ 21 589]]
在生产环境中,模型的训练和使用通常是分开的,下面我们介绍如何使用已经训练好的模型文件,对邮件进行检测。我们定义垃圾邮件检测的类Spam_Check,Spam_Check初始化时加载训练好的模型文件,加载后的模型文件可以直接当成分类器使用,代码如下:
class Spam_Check(object): def __init__(self): self.name="Spam_Check" self.clf=joblib.load(mode_file)
创建特征提取对象,对指定邮件内容进行特征提取,代码如下:
features_extract = Features(vocabulary_file) featurevectors=features_extract.extract("thank you ,your email address was obtained from a purchased list ,reference # 2020 mid = 3300 . if you wish to unsubscribe")
使用分类器的predict_proba对特征向量进行预测,需要特别说明的是,返回的是各个标签的概率值,需要取其中代表垃圾邮件标签的概率值,并与我们设置的阈值进行比较,如果大于阈值认为是垃圾邮件,反之为正常邮件。local_model_threshold为我们定义的阈值,我们可以根据实际运行情况调整大小,不一定非要定为0.5,通常取值在0.6~0.9,代码如下:
def check_spam(self,featurevectors): #[[ 0.96085352 0.03914648]] 返回的是垃圾邮件的概率 y_pred = self.clf.predict_proba([featurevectors])[0,-1] #大于阈值的判断为垃圾邮件 label = float(y_pred >= local_model_threshold) return label