使用 Python 检测电子邮件垃圾邮件
朴素贝叶斯分类器的威力。
引言
任何拥有电子邮件地址的人都一定遇到过不受欢迎的电子邮件,我们称之为垃圾邮件。现代垃圾邮件过滤软件一直在努力检测不受欢迎的电子邮件并将其标记为垃圾邮件。这是垃圾邮件过滤软件和匿名垃圾邮件发送者之间的一场持续的战斗,双方都试图击败对方。因此,及时改进垃圾邮件过滤器算法非常重要。在后台,我们使用机器学习算法来查找不受欢迎的电子邮件。更具体地说,我们使用文本分类器算法,如朴素贝叶斯、支持向量机或神经网络来完成这项工作。在本文中,我将尝试向您展示如何使用朴素贝叶斯算法识别垃圾邮件。我还会尝试根据统计数据比较结果。我们将使用 Python 来完成这项工作。我将尝试向您展示 Python 在机器学习世界中的强大功能。
Using the Code
从这里下载训练和测试数据。首先,将文件分成两个文件,一个用于训练数据,另一个用于测试数据。将训练数据文件命名为 training.csv,将测试数据文件命名为 test.csv。为了方便您,我已将这两个文件上传到Training 和 Test.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 | ||
1779 | 正常邮件 | <p>在那儿有人说稀少…… |
1646 | 正常邮件 | <p>然后很多人拿出可怕的和说唱者…… |
534 | 垃圾邮件 | <p>别离是亲爱的,泉水 save ca…… |
288 | 垃圾邮件 | <p>他的心,海,他关心,他伤心的那天,他又…… |
1768 | 正常邮件 | <p>轻松探索。看到他摇晃的门和…… |
同样,让我们看看测试数据的前五行。
df_train.sample(5)
输出
type | ||
1779 | 正常邮件 | <p>在那儿有人说稀少…… |
1646 | 正常邮件 | <p>然后很多人拿出可怕的和说唱者…… |
534 | 垃圾邮件 | <p>别离是亲爱的,泉水 save ca…… |
288 | 垃圾邮件 | <p>他的心,海,他关心,他伤心的那天,他又…… |
1768 | 正常邮件 | <p>轻松探索。看到他摇晃的门和…… |
看看电子邮件正文中有什么。
df_test.sample(5)
输出
type | ||
58 | 正常邮件 | <p>坐在可怕的我,凝视着,更多的在…… |
80 | 垃圾邮件 | <p>一种恩惠,曾经如此, dentro de chil…… |
56 | 垃圾邮件 | <p>从谁,再次,圣洁的胸膛,到意志…… |
20 | 正常邮件 | <p>轻柔地飞翔,在岸边尖叫,如此悲伤…… |
94 | 垃圾邮件 | <p>他的孩子,低语,他…… |
我们这里有两列 CSV 文件。Type
列包含电子邮件被标记为垃圾邮件还是正常邮件。email
列包含电子邮件的正文(主要文本)。请注意,我们的测试数据也包含类型数据。它已预先给出,以便我们可以交叉检查我们算法的准确性水平。现在我们将查看我们训练数据的一些描述性统计信息。
df_train.describe(include = 'all')
输出
type | ||
计数 | 2000 | 2000 |
唯一 | 2 | 2000 |
顶部 | 垃圾邮件 | <p>沿着孩子,爱,以及女人,在一个…… |
频率 | 1000 | 1 |
在这里,我们可以看到有 2000 条记录。我们有Type
两个唯一值,以及 2000 封唯一的电子邮件。让我们更多地了解 Type
列。
df_train.groupby('type').describe()
计数 | 唯一 | 顶部 | 频率 | |
type | ||||
正常邮件 | 1000 | 1000 | <p>破碎,如果仍然,在,尊贵的,或…… | 1 |
垃圾邮件 | 1000 | 1000 | <p>沿着孩子,爱,以及女人,在一个…… | 1 |
在我们的测试数据中,我们有相同数量(各 1000 条)的Spam
和Ham
。email
列中没有重复数据。现在我们将清理我们的数据。
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 | 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% 的成功率是非常罕见的。显然,这是由于训练和测试数据集较小。我用模型测试了我自己的电子邮件。结果表明,它的效果不如我现有的付费垃圾邮件过滤器。这是有道理的。有很多方法可以改进模型。如果模型使用足够的数据进行训练,它将提供更准确的结果。