【AI 技术讲座精选】如何创建简易且能分辨垃圾邮件的ML分类器

编译:AI100,本文已获授权,转载请联系AI100.

原文链接:https://hackernoon.com/how-to-build-a-simple-spam-detecting-machine-learning-classifier-4471fe6b816e

在本篇教程中,我们会先提出要解决的问题,然后再利用名为朴素贝叶斯分类器(NaiveBayes Classifier)的机器学习技术解决相应的问题,非常简单。本篇教程需要读者具备编程和数据方面的相关经验,但不必具备机器学习方面的经验。

垃圾邮件的检测

在公司中,你是一位为数以百万计用户提供邮件服务的软件工程师。

最近,垃圾邮件问题十分棘手并且已经导致部分客户流失。然而,当前的垃圾邮件过滤器只能筛选出那些之前被用户被标记过的垃圾邮件,垃圾邮件发送者也变得越来越狡猾。为了防止客户流失,你的任务就是提前预测出当前正在发送的邮件是否为垃圾邮件。

训练并测试数据

为了创建出预测垃圾邮件的算法,你必须让程序知道什么样的邮件是垃圾邮件(以及什么样的邮件是正常的邮件)。幸运的是,你有用户先前标记的所有垃圾邮件。你还需要找到测试垃圾邮件过滤器准确性的方法。其中一个想法就是利用训练垃圾软件过滤器的数据对其进行测试。但是,这种做法会带来机器学习过程中的过度拟合问题,也就意味着你的模型太过偏向于数据的训练。因此,当其处于此训练集以外的数据中时,运行结果不会太理想。避免这种结果的普遍做法是从训练数据和测试数据中分别抽取70%和30%的标记数据。这么做能够保证其是在测试不同的数据而非训练数据。你一定要注意,数据集中不能全是垃圾邮件,既要有垃圾邮件也要有正常的邮件。如果的确想要与真正的邮件数据极度相似的训练数据的话,我在帖子底端给出了一个很好的数据集链接,你可以参考一下。

贝叶斯定理(Bayes’ Theorem)

贝叶斯定理的数学表达式,如下所示:

从本质上来说,贝叶斯定理的数学表达式为那些无法直接计算的条件概率提供了便捷的途径。例如,如果你想计算某些人在某个年龄段罹患癌症的概率,但是你只有关于癌症年龄分部的数据,那么就可以把这些数据放进贝叶斯定理的数学表达式中,而不必进行全国性的研究。如果数据理论把你弄得晕头转向,完全不必担心,当它转化为代码时会更有意义的。不过,我衷心地建议你,如果无法理解许多逻辑谬误的基础贝叶斯定理的话,稍后请重新阅读一下这一部分,然后试着理解该定理。

朴素贝叶斯分类器

对于我们的问题,我们可以把A设定为垃圾邮件的概率,B设定为邮件内容。如果P(A|B)>P(¬A|B),那么我们就可以把邮件归类为垃圾邮件,反之就可以把相应的邮件归类为正常邮件。请注意,贝叶斯定理的结果是两边都以P(B)为除数,为了方便比较,我们把P(B)从方程式中去掉。现在方程式是这样的:P(A)*P(B|A) > P(¬A)*P(B|¬A)。计算P(A)和P(¬A)并不难,它们只是训练集中垃圾邮件和正常邮件的百分比:

#runs once on training data

def train:

total = 0

numSpam = 0

for email in trainData:

if email.label == SPAM:

numSpam += 1

total += 1

pA = numSpam/(float)total

pNotA = (total — numSpam)/(float)total

更难计算的部分是P(B|A)和 P(B|¬A)。为了计算这些数据,我们准备利用词袋模型。词袋模型是一个非常简单的模型,它把一段文本当作单个词的包(a bag of individual words),顺序并不重要。对于每个单词,我们都分别计算它出现在垃圾邮件和正常邮件中次数的百分比。我们称这种概率为P(B_i|A_x)。我们需要用一个具体的实例来计算P(free | spam),我们计算free这个词在所有垃圾邮件中出现次数的总和,然后除以垃圾文件中所有词的总数。虽然这些是静态值,但是我们可以在训练过程中计算出这些值。

#runs once on training data

def train:

total = 0

numSpam = 0

for email in trainData:

if email.label == SPAM:

numSpam += 1

total += 1

processEmail(email.body, email.label)

pA = numSpam/(float)total

pNotA = (total — numSpam)/(float)total#counts the words in a specific email

def processEmail(body, label):

for word in body:

if label == SPAM:

trainPositive[word] = trainPositive.get(word, 0) + 1

positiveTotal += 1

else:

trainNegative[word] = trainNegative.get(word, 0) + 1

negativeTotal += 1#gives the conditional probability p(B_i | A_x)

def conditionalWord(word, spam):

if spam:

return trainPositive[word]/(float)positiveTotal

return trainNegative[word]/(float)negativeTotal

只要知道每个单词i的P(B_i|A_x)值的结果,我们就能得到全部邮件的P(B|A_x)。请注意,在初始训练的时候,我们无法获得P(B|A_x)值的结果,只能在分类的时候获取该数值。

#gives the conditional probability p(B | A_x)

def conditionalEmail(body, spam):

result = 1.0

for word in body:

result *= conditionalWord(word, spam)

return result

最终,我们获得了需要进行整合的所有元件。我们所需的最后一部分就是调用每封邮件并且利用我们之前的功能对邮件进行分类。

#classifies a new email as spam or not spam

def classify(email):

isSpam = pA * conditionalEmail(email, True) # P (A | B)

notSpam = pNotA * conditionalEmail(email, False) # P(¬A | B)

return isSpam > notSpam

祝贺你!你已经成功地从头开始编码了一个朴素贝叶斯分类器!

可是,你仍需要做一些改进以使分类器达到最佳运行状态而且没有错误:

拉普拉斯平滑方法(Laplace Smoothing):

我们未曾提及的一件事就是:如果分类邮件中出现了一个从未在训练集中出现过的单词,接下来会发生什么。我们需要添加一个平滑因子来处理这种情况。最好的例证就是在修改过的代码下面添加平滑因子alpha,如图所示:

#gives the conditional probability p(B_i | A_x) with smoothing

def conditionalWord(word, spam):

if spam:

return (trainPositive.get(word,0)+alpha)/(float)(positiveTotal+alpha*numWords)

return (trainNegative.get(word,0)+alpha)/(float)(negativeTotal+alpha*numWords)

对数空间函数(Log-Space)

当前的实现方法非常依赖于浮点乘法。为了规避所有可能的问题,我们通常会乘以非常小的数,函数通常在方程式中执行对数运算进而将所有的乘法运算转换成加法运算。我未曾在示例代码中执行这种函数,但是强烈建议你在实践中运用一下这种函数。

TF-IDF算法

总体来说,文本分类器的词包模型是相当朴素的并且可以通过TF-IDF这样的算法对其进行优化处理。

N-Grams算法

我们能进行的另一个优化处理,不仅仅只是计算单个词的概率。在N-Grams技术中,设想其是一个拥有N个连续单词的集并且利用他们计算概率。因为在英语中1-gram 中的‘good’传达的意思并不是the 2-gram 中的‘not good’。

Tokenization(符号化)

其中一件非常有意思的事情就是,你是如何分类不同的单词的。例如,Free、free和FREE这是三个相同的单词吗?对于标点又如何处理呢?

请注意,编写示例代码是为了最优化教学,而不是为了运行这些代码。这些清晰、简单地改进方法能够大幅地提升代码的运行速度。

示例数据集:

https://spamassassin.apache.org/publiccorpus/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容