python垃圾邮件识别_Python贝叶斯推理垃圾邮件分类

针对贝叶斯垃圾邮件分类,阮一峰大神在多年前曾经写过一篇博客文章,他写的有些观点看起来很简单明了,不过我有点不是很理解其推导过程,虽然最后的结果等价,但是我还是觉得他的那套简单推导,感觉不太容易理解,可以看我后面的分析!贝叶斯推断及其互联网应用(二):过滤垃圾邮件 - 阮一峰的网络日志​www.ruanyifeng.com

阮一峰大神的博客里部分公式图片已经失效,可以看看别人转载的文章贝叶斯推断及其互联网应用(二):过滤垃圾邮件 - csguo - 博客园​www.cnblogs.com

首先呢,我先用大白话,把垃圾邮件分类的整个过程说明白,然后我们再开始码代码。为什么要这样做呢?因为我觉得,作为一名优秀的软件工程师,必须先想明白整个过程,再开始敲代码,不然熬夜加班你怪谁呢。思先于行,因为思路即代码!

1。概念思路

不管是邮件,还是短信,或者论坛贴吧,我们都会看到类似下面的垃圾信息

卖房的推广信息

再比如开假发票

总之这些对于我们正经人来说,都是垃圾,老子不想看这些信息,那我们就要一起设计一个简单的垃圾邮件过滤器,把他们干掉。

那么,问题来了,我们怎么实现垃圾邮件的识别呢???????

一开始,我们估计一点思路也没有,没有思路没有关系,我们可以问自己一个简单的问题

为什么,我们自己看到一封新邮件,就能知道这封邮件是不是垃圾邮件呢,我们自己的大脑是根据什么信息去做出这个判断的呢?如果我们能把自己大脑怎么识别垃圾邮件想明白,那用计算机简化模拟这个过程不就搞定了。

我们大脑怎么识别的呢??最开始,我们自己估计也分不清垃圾邮件,但是看得邮件多了经历多了,我就会发现有些邮件是骗子,是在推销,是在卖假发票,根据什么判断的呢,因为有些词在垃圾邮件中出现的频率明显高,比如:垃圾邮件里,会经常出现发票,赚钱,优惠,折扣,促销的词汇,因为他们的目的性很强,就是为了推广,促销,走货!!

而正常邮件里,大多数的词汇,主要用于日常的生活或者工作,像你,我,他,嗯呢,好哒,这些人称和对话词会出现的比较多,因为我们要沟通和表达彼此的状态。

不管你们服不服,我敢打包票,垃圾信息中,绝对看不到“我”这个词!!!!

当我们发现,正常邮件和垃圾邮件两者存在上面差异的时候,这心里有没有感觉到点什么呢,我们找到了某种特征差异,而所有分类问题最核心的点就是找到那个最容易分割的特征差异!

现在再想想,有没有一点思路呢?????

不要急,我们先看看,到现在为止,我们都做了什么

第1步:我们阅读了好多封邮件,然后我们发现在垃圾邮件中,一些词出现的频率明显偏高,正常邮件中,也有一些词的频率偏高

那根据这些信息,来一封新邮件,我们有没有可能推断新是不是垃圾邮件呢?

第2步:一封新的邮件,肯定会出现不同的词,如果这些词跟垃圾邮件中出现的高频词更接近,比如出现了赚钱,促销,打折等词汇,那是不是就意味着新邮件更接近垃圾邮件呢???!!!!

搞定!!!思路通了!!!!

那下面就是把这个概念思路,转化为更加明确而具体的思路表达,才能正式开始敲代码!

2。正式思路

我们现在要做的就是把上面的概念思路,用更加具体而正式的思路,去表达。

好吧,那就开始我们的贝叶斯之旅吧!

首先,我们先把第一步,转化一下我们阅读了好多封邮件,然后我们发现在垃圾邮件中,一些词出现的频率明显偏高,正常邮件中,也有一些词的频率偏高。

上面这句话,就是让我们算一下所有垃圾邮件中赚钱或者打折这些词出现的概率,以及在正常邮件中出现的概率,于是我们会得到几个条件概率值(这里为了简化,只算两个词的)P(赚钱|垃圾邮件) P(打折|垃圾邮件)

P(赚钱|正常邮件) P(打折|正常邮件)

上面的条件概率是什么意思呢?比如P(赚钱|垃圾邮件) 这个值的含义,就是所有垃圾邮件中有多少出现了赚钱这个词。

这就是条件概率,简单吧!

要注意,P(赚钱|垃圾邮件) 不一定等于 P(赚钱)[所有邮件中有多少出现了赚钱这个词]

下面就是转化第二步第2步:一封新的邮件,肯定会出现不同的词,如果这些词跟垃圾邮件中出现的高频词更接近,比如出现了赚钱,促销,打折等词汇,那是不是就意味着新邮件更接近垃圾邮件

我们先从最简单的着手,假如新邮件只出现了一个词:赚钱,那这封邮件是垃圾邮件的概率是多少???

有没有发现这个问题变换了一个角度

刚才我们的角度是垃圾邮件中出现某个词的概率是多少,现在换成了如果出现某个词,那是垃圾邮件的概率是多少?

其实,这个很简单,就是让我们去算 P(垃圾邮件|赚钱) 这个值是多少

P(垃圾邮件|赚钱) 的含义是什么呢,简单点说是说所有出现赚钱的邮件中有多少是垃圾邮件

于是我们开始推算吧

上面这一步的意思是,我要想得到所有包含赚钱的邮件中有多少概率是垃圾邮件,就是让所有既是垃圾邮件又出现赚钱的邮件数目除以所有出现赚钱的邮件数目。

下一步我们先看分子怎么得到

这一步的意思是,既是垃圾邮件又出现赚钱的邮件:就相当于从垃圾邮件中找出所有包含赚钱的邮件。

我如果把最开始的第一个公式分母乘到左边,你就会看到之前说的一个问题的两种角度,这也是贝叶斯推理的核心点。

再下一步,我们看分母怎么得到

这一步的意思就是,所有包含赚钱的邮件:相当于从垃圾邮件中找出所有包含赚钱的邮件,再加上从正常邮件中找出所有包含赚钱的邮件。

好吧, 我直接说吧,上面这个就是全概率公式!!!!!!

到现在为止,我们就得到了最终的公式表达

等号左边的每个值,两个条件概率在第一步就已经算出来了,而P(垃圾邮件)和P(正常邮件)这两个概率值,一般大多数是采用都是50%的方式,当然你用邮件库的统计数据也行。

好了,到这里,我们就把新邮件,只包含一个词(赚钱)反推是垃圾邮件概率的情况,完成了!!!

而实际中,新邮件中会有很多不同的词,那应该怎么玩呢??

其实针对多个词,我们只要先研究明白两个词的情况,其他更多词的情况递推就可以了。

好嘞,现在新邮件中假如有两个词:赚钱和折扣,那是垃圾邮件的概率是多少呢

P( 垃圾邮件|(赚钱*折扣) )

这个意思就是,同时出现赚钱和折扣这两个词的邮件中有多少概率是垃圾邮件。

关于这个表达式怎么分解,有几种玩法

玩法1:

P( 垃圾邮件|(赚钱*折扣) ) = P( 垃圾邮件|赚钱)*P(垃圾邮件|折扣)

这样算对吗??????

玩法2:

P( 垃圾邮件|(赚钱*折扣) ) = P( 垃圾邮件|赚钱)+P(垃圾邮件|折扣)

这样算对吗??????

不用想,上面的肯定是错的!!!关键是,为什么错??????

玩法1的错误是,赚钱和折扣这两个词在邮件中的出现可以假设独立,

P(赚钱*折扣) = P( 赚钱)*P(折扣)

但是加上不同条件的条件概率乘积有什么意义。。。。。

你来跟我解释解释,P( 垃圾邮件|赚钱)*P(垃圾邮件|折扣),这两个值乘在一起有什么实际含义。。。。。。

玩法2的错误,跟玩法1一样,条件概率的条件不一样,所以求和没有任何实际含义!!!!

那正确的玩法是什么呢?

玩法3:

上面最关键的一步就是

P((赚钱*折扣)|垃圾邮件)=P(赚钱|垃圾邮件)*P(折扣|垃圾邮件)

为什么在玩法1中,我们不能拆开相乘的,在这里可以拆开相乘呢???

因为我们假设赚钱和折扣这两个词的出现相互独立(真实未必这样,但是如果不假设,就没法简化),这就意味着,所有邮件中,他们的出现都是独立,这种独立,不受正常邮件,也不受垃圾邮件影响。

千万注意:条件概率只有在同条件下,才可以拆分。。。。。。

如果有更多的词,同时我们假设P(垃圾邮件)和P(正常邮件)都是0,.5那就可以消去,那上面的这个就可以最终表达为

备注:Wi代表新邮件里的第i个词。

补充一下:

这个公式跟阮一峰大神的公式不太一样,他的思路

先算出每个词出现对应的垃圾邮件的概率

S代表Spam垃圾邮件,H代表Health正常邮件,W代表一个词Word

这一步,我们俩是一样的,但是两个词的推导不一样

最终他的推导结果

跟我上面算的公式,不一样!!!!!!

于是我把两个分别做了一个化简对比,发现两者是等价的!!!!

那问题就来了,选哪个好呢????

词一旦多了之后,就会发现,分子和分母有多个条件概率相乘,那就要看,为了保证计算精度,肯定是选条件概率值更大的相乘,更容易保留计算精度。

于是我们看

P(赚钱|垃圾邮件) 和 P(垃圾邮件|赚钱)

哪个值更大呢???

我感觉,出现赚钱是垃圾邮件的概率,肯定大于垃圾邮件出现赚钱的概率。所以阮一峰大神的更胜一筹。

只不过,我现在也不理解他的推导过程,怎么看,都感觉有点别扭!!!!!!!

因为你让P(垃圾|赚钱)*P(垃圾|打折)两条件都不一样的条件概率乘在一起,代表什么实际含义呢????反正我是搞不懂。。。。

如果有大神,能够用更加简单明了的对阮一峰的进行解释,欢迎拍砖过来。

3。程序结构

根据上面的思路,我们现在就可以写程序的结构,也就是伪代码了

第一步:生成频率表genEmailWordsRatefilterEmail:正则表达式过滤邮件正文

cutEmail:中文分词切割一封邮件内容为一个词语组

cutAllEmail:切割所有邮件内容,生成EmailWordTable次数统计表字典,同时保存文件

第二步:生成频率字典ProcessEmailTable

将EmailWordTable中统计的词语次数,换算成概率表,这里注意,最终生成的概率表这里做了适当改动,就是只保留垃圾邮件和正常邮件差异比较大的词语。。因为特征差异主要就体现在这些词语上。

第三步:识别新邮件

根据前面的识别公式,checkOneMail识别一封新邮件

识别方式为:新邮件的分词得到集合mailSet,然后看新邮件的词是否在EmailWordTable字典里,如果出现过的话,利用贝叶斯全概率公式反推该封邮件是垃圾邮件的概率,同时假设每个词出现是独立事件。

用到的是trec06c库的6万多封中文邮件,下载地址,可以到这里下载所有原邮件https://plg.uwaterloo.ca/~gvcormac/treccorpus06/​plg.uwaterloo.ca

#coding: utf-8

import re

import jieba

EmailIndexPath="D:\\trec06c\\trec06c\\full\\index"

EmailWordTable={}#每个词的出现频次

#正则表达式过滤所有非中文词语

def filterEmail(email):

email = re.sub("[a-zA-Z0-9_]+|\W","",email)

return email

#切割一封邮件为一个词语组的集合,注意不是列表,是用集合

def cutEmail(filename):

with open(filename, "r",encoding='GB2312', errors='ignore') as f:

lines=f.read()

words=jieba.cut(filterEmail(lines))

return {x for x in words}

#切割所有邮件

def cutAllEmail():

SpamCount=0

HamCount=0

with open(EmailIndexPath, "r",encoding='GB2312') as f:

lines = f.readlines()

for line in lines:

items=line.split()

print(items[1])

EmailSet=cutEmail(items[1])

if items[0].upper() == "SPAM":

SpamCount+=1

if items[0].upper() == "HAM":

HamCount+=1

for a in EmailSet:

if a in EmailWordTable:

EmailWordTable[a][0]+=1

else:

EmailWordTable[a]=[1,0,0]

if items[0].upper() == "SPAM":

EmailWordTable[a][1]+=1

if items[0].upper() == "HAM":

EmailWordTable[a][2]+=1

return SpamCount,HamCount

#生成概率表

def genEmailWordsRate():

SpamCount,HamCount=cutAllEmail()

print('Spam:'+str(SpamCount))

print('Ham:'+str(HamCount))

#所有邮件,生成概率表

with open("EmailTable.txt","w") as f:

lines=[]

for item in sorted(EmailWordTable.items(),key=lambda x:x[1][0],reverse=True):

string ='{}'.format(item[0])

string+='{}'.format(float(item[1][0])/(SpamCount+HamCount))

string+='{}'.format(float(item[1][1])/SpamCount)

string+='{}\n'.format(float(item[1][2])/HamCount)

lines.append(string)

f.writelines(lines)

#对概率表做处理,只保留垃圾和正常邮件差异比较大的词

def ProcessEmailTable():

EmailTable={}

with open("EmailTable.txt","r") as f:

lines=f.readlines()

for line in lines:

items=line.split()

key=items[0]

if abs(float(items[2])-float(items[3])) > 0.15:

EmailTable[key]=[float(items[1]),float(items[2]),float(items[3])]

if float(items[3])>0.05 and float(items[2])<0.01:

EmailTable[key]=[float(items[1]),float(items[3])/20,float(items[3])]

elif float(items[2])>0.05 and float(items[3])<0.01:

EmailTable[key]=[float(items[1]),float(items[2]),float(items[2])/20]

lines=[]

with open("EmailModel.txt","w") as f:

for item in sorted(EmailTable.items(),key=lambda x:x[1][0],reverse=True):

string ='{}'.format(item[0])

string+='{:0.4f}'.format(item[1][0])

string+='{:0.4f}'.format(item[1][1])

string+='{:0.4f}\n'.format(item[1][2])

lines.append(string)

f.writelines(lines)

return EmailTable

#检测一份新邮件

def checkOneMail(EmailTable,mailSet):

SpamRate=0.5

HamRate= 0.5

#print(mailSet)

for word in mailSet:

if word in EmailTable:

SpamRate*=EmailTable[word][1]

HamRate*=EmailTable[word][2]

res= SpamRate/(SpamRate+HamRate)

#print(res)

if res <0.4:

result='ham'

else:

result='spam'

return result

#检测所有邮件的准确率

def checkAllEmail(EmailTable):

errCount=0

totalCount=0

fw=open("EmailCheck.txt","w")

with open(EmailIndexPath, "r",encoding='GB2312') as f:

lines = f.readlines()

linesNew=[]

for line in lines:

items=line.split()

EmailSet=cutEmail(items[1])

res=checkOneMail(EmailTable,EmailSet)

totalCount+=1

if res != items[0]:

linesNew.append(res+' '+line)

errCount+=1

print(totalCount)

print(errCount/totalCount)

fw.writelines(linesNew)

if __name__ == "__main__":

#genEmailWordsRate()

EmailTable=ProcessEmailTable()

checkAllEmail(EmailTable)

体现数据结构的就是:

1。将一封邮件切割为的词组,用一个集合表,而非是用一个列表。

2。记录整体词汇的词频,用的是一个字典。

3。考虑到计算词频时间比较长,这里用到了中间文件保存数据

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值