65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Python 检测电子邮件垃圾邮件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (4投票s)

2018年3月11日

CPOL

6分钟阅读

viewsIcon

48251

downloadIcon

1313

朴素贝叶斯分类器的威力。

引言

任何拥有电子邮件地址的人都一定遇到过不受欢迎的电子邮件,我们称之为垃圾邮件。现代垃圾邮件过滤软件一直在努力检测不受欢迎的电子邮件并将其标记为垃圾邮件。这是垃圾邮件过滤软件和匿名垃圾邮件发送者之间的一场持续的战斗,双方都试图击败对方。因此,及时改进垃圾邮件过滤器算法非常重要。在后台,我们使用机器学习算法来查找不受欢迎的电子邮件。更具体地说,我们使用文本分类器算法,如朴素贝叶斯、支持向量机或神经网络来完成这项工作。在本文中,我将尝试向您展示如何使用朴素贝叶斯算法识别垃圾邮件。我还会尝试根据统计数据比较结果。我们将使用 Python 来完成这项工作。我将尝试向您展示 Python 在机器学习世界中的强大功能。

Using the Code

从这里下载训练和测试数据。首先,将文件分成两个文件,一个用于训练数据,另一个用于测试数据。将训练数据文件命名为 training.csv,将测试数据文件命名为 test.csv。为了方便您,我已将这两个文件上传到TrainingTest.zip 文件中。

import pandas as pd 
# read training data & test data 
df_train = pd.read_csv("training.csv")
df_test = pd.read_csv("test.csv")

让我们看看训练数据的前五行。

df_test.sample(5)

输出

  type email
1779 正常邮件 <p>在那儿有人说稀少……
1646 正常邮件 <p>然后很多人拿出可怕的和说唱者……
534 垃圾邮件 <p>别离是亲爱的,泉水 save ca……
288 垃圾邮件 <p>他的心,海,他关心,他伤心的那天,他又……
1768 正常邮件 <p>轻松探索。看到他摇晃的门和……

同样,让我们看看测试数据的前五行。

df_train.sample(5)

输出

  type email
1779 正常邮件 <p>在那儿有人说稀少……
1646 正常邮件 <p>然后很多人拿出可怕的和说唱者……
534 垃圾邮件 <p>别离是亲爱的,泉水 save ca……
288 垃圾邮件 <p>他的心,海,他关心,他伤心的那天,他又……
1768 正常邮件 <p>轻松探索。看到他摇晃的门和……

看看电子邮件正文中有什么。

df_test.sample(5)

输出

  type email
58 正常邮件 <p>坐在可怕的我,凝视着,更多的在……
80 垃圾邮件 <p>一种恩惠,曾经如此, dentro de chil……
56 垃圾邮件 <p>从谁,再次,圣洁的胸膛,到意志……
20 正常邮件 <p>轻柔地飞翔,在岸边尖叫,如此悲伤……
94 垃圾邮件 <p>他的孩子,低语,他……

我们这里有两列 CSV 文件。Type 列包含电子邮件被标记为垃圾邮件还是正常邮件。email 列包含电子邮件的正文(主要文本)。请注意,我们的测试数据也包含类型数据。它已预先给出,以便我们可以交叉检查我们算法的准确性水平。现在我们将查看我们训练数据的一些描述性统计信息。

df_train.describe(include = 'all')

输出

  type email
计数 2000 2000
唯一 2 2000
顶部 垃圾邮件 <p>沿着孩子,爱,以及女人,在一个……
频率 1000 1

在这里,我们可以看到有 2000 条记录。我们有Type 两个唯一值,以及 2000 封唯一的电子邮件。让我们更多地了解 Type 列。

df_train.groupby('type').describe()
  email
  计数 唯一 顶部 频率
type        
正常邮件 1000 1000 <p>破碎,如果仍然,在,尊贵的,或…… 1
垃圾邮件 1000 1000 <p>沿着孩子,爱,以及女人,在一个…… 1

在我们的测试数据中,我们有相同数量(各 1000 条)的SpamHamemail 列中没有重复数据。现在我们将清理我们的数据。

import email_pre as ep
from gensim.models.phrases import Phrases 

def do_process(row): 
global bigram 
temp = ep.preprocess_text(row.email,[ep.lowercase, 
ep.remove_html, 
ep.remove_esc_chars, 
ep.remove_urls, 
ep.remove_numbers, 
ep.remove_punct, 
ep.lemmatize, 
ep.keyword_tokenize]) 

if not isinstance(temp,str): 
print temp 

return ' '.join(bigram[temp.split(" ")]) 

def phrases_train(sen_list,min_ =3): 
if len(sen_list) <= 10: 
print("too small to train! ") 
return 

if isinstance(sen_list,list): 
try: 
bigram = Phrases.load("email_EN_bigrams_spam") 
bigram.add_vocab(sen_list) 
bigram.save("email_EN_bigrams_spam") 
print "retrain!" 

except Exception as ex: 
print "first " 
bigram = Phrases(sen_list, min_count=min_, threshold=2) 
bigram.save("email_EN_bigrams_spam") 
print ex

短语模型训练(运行一次并保存模式,也可重新训练)。

train_email_list = [ep.preprocess_text(mail,[ep.lowercase, 
ep.remove_html, 
ep.remove_esc_chars, 
ep.remove_urls, 
ep.remove_numbers, 
ep.remove_punct, 
ep.lemmatize, 
ep.keyword_tokenize]).split(" ") for mail in df_train.email.values]

print "after pre_process :"
print " " 
print len(train_email_list) 
print df_train.ix[22].email,">>"*80,train_email_list[22]

输出

经过pre_process

2000
<p>他啊,他,更多,事物,长,从,我的,对。直到,感觉,他们,寻求,其他,告别,罪,点。逆境,痛苦,低。很快,光,现在,时间,不对,去,镀金,在,但,知道,的, yet,竞价,他, thence, made。将,关心,真实,和,到,竖琴,和,和,在一个,这,魅力,厅,古代, departed, from。 Bacchanals,到,无, lay,魅力,在,他的,他的,perchance,在,和,使用, woe, deadly。 Save, nor,到,对,那,那, unto,他。 你的,在,你的。 能够,寄生虫,harold, of, unto, sing, at,那,在,对,土壤,内, rake,知道,但。 如果,他,shamed, breast, heralds, grace, once, dares, and, carnal, finds, muse, none, peace, like, way, loved。 如果,long, favour, or, flaunting, did, me, with, later, will。 Not, calm, labyrinth, tear, basked, little。 It, talethis, calm, woe, sight, time。 Rake, and, to, hall。 Land, the, a, him, uncouth, for, monks, partings, fall, there, below, true, sighed, strength。 Nor, nor, had, spoiled, condemned, glee, dome, monks, him, few, of, sore, from, aisle, shun, virtues。 Bidding, loathed, aisle, a, and, if, that, to, it, chill, shades, isle, the, control, at。 So, knew, with, one, will, wight, nor, feud, time, sought, flatterers, earth。 Relief, a, would, break, at, he, if, break, not, scape。</p><p>The, will, heartless, sacred, visit, few。 The, was, from, near, long, grief。 His, caught, from, flaunting, sacred, care, fame, said, are, such, and, in, but, a。</p> ['ah', 'things', 'long', 'mine', 'unto', 'feel', 'seek', 'adieu', 'crime', 'dote', 'adversity', 'pangs', 'low', 'soon', 'light', 'time', 'amiss', 'gild', 'know', 'yet', 'bid', 'thence', 'make', 'care', 'true', 'lyres', 'one', 'charm', 'hall', 'ancient', 'depart', 'bacchanals', 'none', 'lay', 'charm', 'perchance', 'use', 'woe', 'deadly', 'save', 'unto', 'thy', 'thy', 'might', 'parasites', 'harold', 'unto', 'sing', 'soil', 'within', 'rake', 'know', 'sham', 'breast', 'herald', 'grace', 'dare', 'carnal', 'find', 'muse', 'none', 'peace', 'like', 'way', 'love', 'long', 'favour', 'flaunt', 'later', 'calm', 'labyrinth', 'tear', 'bask', 'little', 'talethis', 'calm', 'woe', 'sight', 'time', 'rake', 'hall', 'land', 'uncouth', 'monks', 'part', 'fall', 'true', 'sigh', 'strength', 'spoil', 'condemn', 'glee', 'dome', 'monks', 'sore', 'aisle', 'shun', 'virtues', 'bid', 'loathe', 'aisle', 'chill', 'shade', 'isle', 'control', 'know', 'one', 'wight', 'feud', 'time', 'seek', 'flatterers', 'earth', 'relief', 'would', 'break', 'break', 'scapethe', 'heartless', 'sacred', 'visit', 'near', 'long', 'grief', 'catch', 'flaunt', 'sacred', 'care', 'fame', 'say']

df_train["class"] = df_train.type.replace(["Spam","Ham"],[0,1]) 
df_test["class"] = df_test.type.replace(["Spam","Ham"],[0,1])

Bigram 训练

phrases_train(train_email_list,min_=3) 
bigram = Phrases.load("email_EN_bigrams_spam") 
len(bigram.vocab)

重新训练!

输出

159158

print len(dict((key,value) for key, value in bigram.vocab.iteritems() if value >= 15))

输出

4974

df_train["clean_email"] = df_train.apply(do_process,axis=1) 
df_test["clean_email"] = df_test.apply(do_process,axis=1) 
# df_train.head() 
print "phrase found train:",df_train[df_train['clean_email'].str.contains("_")].shape 
print "phrase found test:",df_test[df_test['clean_email'].str.contains("_")].shape

输出

phrase found train: (371, 3)
phrase found test: (7, 3)

垃圾邮件检测训练

df_train.head()

输出

  type email clean_email
0 垃圾邮件 <p>但是,在那个时候,荣耀就像…… 能够炫耀荣耀、尊严、烦恼、时间、孩子…… 0
1 垃圾邮件 <p>他甜蜜的土地,邪恶,所以,以及本地…… 土地、邪恶、本地、啊、啊,喜欢闪耀、镀金、b…… 0
2 垃圾邮件 <p>眼泪、女人、他的、是、曾、她、隐士…… 眼泪、 tis、隐士、现在、亲爱的、知道、 pro…… 0
3 垃圾邮件 <p>那,和,土地。细胞,躲避,辉煌,激情…… 土地、细胞、躲避、辉煌、激情、粗野、 paphian…… 0
4 垃圾邮件 <p>唱歌,通过,别离,事物,曾经,神圣…… 唱歌,通过,别离,事物,神圣,知道,激情, pro…… 0
from sklearn.pipeline 
import Pipeline from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.feature_extraction.text import TfidfTransformer 
from sklearn.naive_bayes import MultinomialNB 
text_clf = Pipeline([('vect', CountVectorizer()), 
           ('tfidf', TfidfTransformer()), ('clf', MultinomialNB()), ])


text_clf.fit(df_train.clean_email, df_train["class"]) 
predicted = text_clf.predict(df_test.clean_email)
from sklearn import metrics 
array = metrics.confusion_matrix(df_test["class"], predicted) 
import seaborn as sn 
import pandas as pd 
import matplotlib.pyplot as plt 
%matplotlib inline 

df_cm = pd.DataFrame(array, ["Spam","Ham"], 
["Spam","Ham"]) 

sn.set(font_scale=1.4)#for label size 
sn.heatmap(df_cm, annot=True,annot_kws={"size": 16})# font size

输出

print metrics.classification_report(df_test["class"], predicted, 
target_names=["Spam","Ham"])

输出

             precision    recall  f1-score   support

       Spam       1.00      1.00      1.00        43
        Ham       1.00      1.00      1.00        57

avg / total       1.00      1.00      1.00       100

为了测试模型,我们将测试数据放入模型中,并将其结果与测试数据中已有的结果进行比较。结果显示,在 43 封垃圾邮件中,模型成功识别了所有 43 封。同样,在 57 封正常邮件中,模型也成功识别了所有 57 封正常邮件。

结论

一个模型获得 100% 的成功率是非常罕见的。显然,这是由于训练和测试数据集较小。我用模型测试了我自己的电子邮件。结果表明,它的效果不如我现有的付费垃圾邮件过滤器。这是有道理的。有很多方法可以改进模型。如果模型使用足够的数据进行训练,它将提供更准确的结果。

© . All rights reserved.