【NLP】基于贝叶斯的垃圾邮件分类

  1. 内容简介
    本文主要通过对垃圾邮件和正常邮件进行关键词统计,进而得出某词在正常邮件中出现的概率和在垃圾邮件中出现的值。
    从而判断
    P ( 正 常 ∣ 内 容 ) > P ( 垃 圾 ∣ 内 容 ) P(正常|内容)>P(垃圾|内容) P()>P() 如果为真,则该邮件很有可能是正常邮件,否则更有可能是垃圾邮件。
    注:附录1是源码,附录2是数据集

  2. 算法说明
    若要证明以上不等式,必须先计算P(正常|内容)。但是根据内容来判断是否正常是无法计算的。不过我们可以计算P(内容|正常), 也就是说,正常情况下,出现该内容的概率。
    贝叶斯定理正好可以帮我们转化(可参考朴素贝叶斯和噪声信道模型
    P ( 正 常 ∣ 内 容 ) = P ( 内 容 ∣ 正 常 ) × P ( 正 常 ) P ( 内 容 ) P(正常|内容)=\frac{P(内容|正常)×P(正常)}{P(内容)} P()=P()P()×P()
    以上不等式转化为
    P ( 内 容 ∣ 正 常 ) × P ( 正 常 ) P ( 内 容 ) > P ( 内 容 ∣ 垃 圾 ) × P ( 垃 圾 ) P ( 内 容 ) \frac{P(内容|正常)×P(正常)}{P(内容)}>\frac{P(内容|垃圾)×P(垃圾)}{P(内容)} P()P()×P()>P()P()×P()
    消去分母得
    P ( 内 容 ∣ 正 常 ) × P ( 正 常 ) > P ( 内 容 ∣ 垃 圾 ) × P ( 垃 圾 ) P(内容|正常)×P(正常) >P(内容|垃圾)×P(垃圾) P()×P()>P()×P()其中P(正常)和P(垃圾)就是日常生活中正常邮件和垃圾邮件的比例,是先验概率。
    将邮件内容分词处理得到{w1,w2,…wn}则
    P ( 内 容 ∣ 正 常 ) × P ( 正 常 ) = P ( w 1 , w 2 , w 3 , . . . . w n ∣ 正 常 ) × P ( 正 常 ) P(内容|正常)×P(正常)=P(w_1,w_2,w_3,....w_n|正常)×P(正常) P()×P()=P(w1,w2,w3,....wn)×P()
    P ( w 1 , w 2 , w 3 , . . . . w n ∣ 正 常 ) × P ( 正 常 ) = P ( 正 常 ) × ∏ i = 1 n P ( w i ∣ 正 常 ) P(w_1,w_2,w_3,....w_n|正常)×P(正常)=P(正常)×\prod_{i=1}^nP(w_i|正常) P(w1,w2,w3,....wn)×P()=P()×i=1nP(wi)
    由上可知,只要求的P(单词|正常),即正常邮件的情况下出现该单词的概率即可。
    可构建一个字典,形如dict={"单词":[正常情况下出现次数,垃圾情况下出现次数]}
    那么概率的计算将转化为:
    P ( 单 词 ∣ 正 常 ) = d i c t [ ′ 单 词 ′ ] [ 0 ] d i c t [ ′ 单 词 ′ ] [ 0 ] + d i c t [ ′ 单 词 ′ ] [ 1 ] P(单词|正常)=\frac{dict['单词'][0]}{dict['单词'][0]+dict['单词'][1] } P()=dict[][0]+dict[][1]dict[][0]

  3. 模型设计
    建立模型的工作流程如下图所示。
    建立模型的工作流程
    3.1 数据读取
    本次使用的数据集ham_5000.utf8,spam_5000.utf8,各含有5000条正常邮件和无5000条垃圾邮件,各取50条作为测试数据存入email_test.xt(前五十条为垃圾邮件)。

#读取文件
stop_word=[]
normal_email=[]
abnormal_email=[]
email_test=[]
with open('corpurs/ham_5000.utf8','r') as f:
	for x in f:
		normal_email.append(x.rstrip())
with open('corpurs/spam_5000.utf8','r') as f:
	for x in f:
		abnormal_email.append(x.rstrip())
with open('corpurs/stop_word.txt','r') as f:
	for x in f:
		stop_word.append(x.rstrip())
with open('corpurs/email_test.utf8','r') as f:
	for x in f:
		email_test.append(x.rstrip())

N=len(normal_email)#记录正常邮件的个数
M=len(abnormal_email)

p_abnormal=N/(M+N)#计算先验概率P(垃圾)
p_no=1-p_abnormal#计算先验概率P(正常)

abnormal_email_text=" ".join(abnormal_email)#将邮件列表转化为文本备用。
normal_email_text=" ".join(normal_email)

3.2 数据预处理
对于中文系统,共进行了预处理内容:

  • 标准化(将电话网址邮箱等数据,使用正则表达式用电话号网址,邮箱号替换)
  • 移除停用词
  • 移除低频词
#标准化数据
def normalizing(text):
	email_pattern=re.compile('[0-9A-Za-z_-]*@[0-9a-zA-Z]+\.[A-Za-z.]*')
	text=re.sub(email_pattern,"邮箱号",text)
	qq_pattern=re.compile('[1-9][0-9]{7,12}')
	text=re.sub(qq_pattern,"扣扣号",text)
	http_pattern=re.compile('(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?')
	text=re.sub(http_pattern,"网址",text)
	else_pattern=re.compile('[0-9a-zA-Z+_#,。 ,./`;*\-+!@#$%^&*()_+"::“】\n【].*?')
	text=re.sub(else_pattern," ",text)
	return text
#除去停用词
def remove_stopwords(stop_word,dic):
	stop_word.append('\n')
	stop_word.append(' ')
	for x in stop_word:
		if  dic.__contains__(x) :
			# print(x)
			del dic[x]
	return dic
def data_preprocessing(_text):
	_text=normalizing(_text)#标准化手机号 网址 邮箱 等数据
	_ls=jieba.cut(_text)
	_dic=Counter(list(_ls))
	_dic=remove_stopwords(stop_word,_dic)#除去停用词
	#转为dataframe 然后排序,有更好的方法可代替此处
	_df=pd.DataFrame(list(_dic.values()),index=_dic.keys())
	_df=_df.sort_index(by=0,ascending=False)
	#去除低频词,取高频前5000
	return _df[:5000]

3.3 特征提取
由于本系统只是粗略搭建,并没有提取特征。暂且把记录频率的字典构造放到此处。

abnormal_email_dict=data_preprocessing(abnormal_email_text).to_dict()[0]
normal_email_dict=data_preprocessing(normal_email_text).to_dict()[0]
#合并两个字典
total=set(abnormal_email_dict.keys()) | set(normal_email_dict.keys())
'''
total_dic={ '单词':[广告邮件出现次数,正常邮件出现次数]  }
'''
#重新计算出现的频率
total_dic={}
for x in list(total):
	temp=[0,0]
	if abnormal_email_dict.__contains__(x):
		temp[0]=abnormal_email_dict[x]
	if normal_email_dict.__contains__(x):
		temp[1]=normal_email_dict[x]
	total_dic[x]=temp
#这样就建立好了字典

3.4建模部分
建模部分的流程图如下所示。
在这里插入图片描述
首先将邮件内容进行替换、分词、分别计算垃圾邮件和正常邮件的条件概率累乘积。然后比较正常和垃圾的概率大小:

  • 如果P(内容|正常)概率大,返回1 表示正常邮件
  • 如果P(内容|垃圾)概率大,返回0 表示垃圾邮件
## 重写log函数
def log(p):
	if p<=0:#处理非法数据
		p=0.000001
	if p==1.0:#处理概率为1 的数据
		p=p-0.000001
	return math.log(p)

##计算条件概率
def condition_probability(word,y=1):
	#正常 y=1 广告 y=0
	if total_dic.__contains__(word):
		temp=total_dic[word]
		# print(word,temp)
		p=(temp[y]+1)/(2*(temp[0]+temp[1]))
		# print(word,p)
		return p
	# 如果字典中不存在,使用先验概率代替
	return p_no if y==1 else p_abnormal

#贝叶斯建模
def bayes(content):
	content=normalizing(content)
	words=jieba.cut(content)
	correct,incorrect=log(p_no),log(p_abnormal)
	for w in words:
		if w=="" or w==" ":
			continue
		#正常 y=1 广告 y=0
		correct+=log(condition_probability(w,y=1))
		incorrect+=log(condition_probability(w,y=0))
	# print(correct,incorrect)
	return 1 if correct>incorrect else 0


3.5测试部分
本系统的测试内容为:(参考评估一个分类器:精确率和召回率

  • 正确率
  • 精确度和召回率
  • f1-measure
res=[]
fact=[0 for _ in range(50)]+[1 for _ in range(50)]
for  content in email_test:
	res.append(bayes(content))
print("\n==================测试结束===================\n")
print(f"测试数目:{len(res)}")
TP,FP,TN,FN=0,0,0,0
for i in range(len(fact)):
	if fact[i]==res[i]:
		if fact[i]==1:
			TP+=1
		else:
			TN+=1
	else:
		if fact[i]==1 :
			FN+=1
		else:
			FP+=1
precision=100*TP/(TP+FP)
recall=100*TP/(TP+FN)
f1=(2*precision*recall)/(precision+recall)
print(f"正确率:{TP+TN}%   \n精确率:{precision}% \n召回率:{recall}%\nF1-measure:{f1}%")
print("\n==================测试结束===================\n")
  1. 自我总结
    这是首次自己写贝叶斯的分类器,很多专业的问题了解的不够清楚,只是对原理的简单应用,如果有问题在慢慢完善。对于字典排序合并那一块,转化为dataframe效率太低,以后找到好的办法在完善吧。

附录1 完整代码

# 基于贝叶斯的邮件分类系统
import jieba
import re
import pandas as pd
import math
from collections import Counter

#标准化数据
def normalizing(text):
	email_pattern=re.compile('[0-9A-Za-z_-]*@[0-9a-zA-Z]+\.[A-Za-z.]*')
	text=re.sub(email_pattern,"邮箱号",text)
	qq_pattern=re.compile('[1-9][0-9]{7,12}')
	text=re.sub(qq_pattern,"扣扣号",text)
	http_pattern=re.compile('(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?')
	text=re.sub(http_pattern,"网址",text)
	else_pattern=re.compile('[0-9a-zA-Z+_#,。 ,./`;*\-+!@#$%^&*()_+"::“】\n【].*?')
	text=re.sub(else_pattern," ",text)
	return text
#除去停用词
def remove_stopwords(stop_word,dic):
	stop_word.append('\n')
	stop_word.append(' ')
	for x in stop_word:
		if  dic.__contains__(x) :
			# print(x)
			del dic[x]
	return dic

#数据预处理
def data_preprocessing(_text):
	_text=normalizing(_text)#标准化手机号 网址 邮箱 等数据
	_ls=jieba.cut(_text)
	_dic=Counter(list(_ls))
	_dic=remove_stopwords(stop_word,_dic)#除去停用词
	#转为dataframe 然后排序,有更好的方法可代替此处
	_df=pd.DataFrame(list(_dic.values()),index=_dic.keys())
	_df=_df.sort_index(by=0,ascending=False)
	#去除低频词,取高频前5000
	return _df[:5000]
#读取文件
stop_word=[]
normal_email=[]
abnormal_email=[]
email_test=[]
with open('corpurs/ham_5000.utf8','r') as f:
	for x in f:
		normal_email.append(x.rstrip())
with open('corpurs/spam_5000.utf8','r') as f:
	for x in f:
		abnormal_email.append(x.rstrip())
with open('corpurs/stop_word.txt','r') as f:
	for x in f:
		stop_word.append(x.rstrip())
with open('corpurs/email_test.utf8','r') as f:
	for x in f:
		email_test.append(x.rstrip())

N=len(normal_email)
M=len(abnormal_email)

p_abnormal=N/(M+N)
p_no=1-p_abnormal

abnormal_email_text=" ".join(abnormal_email)
normal_email_text=" ".join(normal_email)

#数据预处理

abnormal_email_dict=data_preprocessing(abnormal_email_text).to_dict()[0]
normal_email_dict=data_preprocessing(normal_email_text).to_dict()[0]

total=set(abnormal_email_dict.keys()) | set(normal_email_dict.keys())

'''
 重新计算模型

total_dic={ '单词':[广告邮件出现次数,正常邮件出现次数]  }

'''
total_dic={}
for x in list(total):
	temp=[0,0]
	if abnormal_email_dict.__contains__(x):
		temp[0]=abnormal_email_dict[x]
	if normal_email_dict.__contains__(x):
		temp[1]=normal_email_dict[x]
	total_dic[x]=temp

# print(total_dic)




## 重写log函数
def log(p):
	if p<=0:#处理非法数据
		p=0.000001
	if p==1.0:#处理概率为1 的数据
		p=p-0.000001
	return math.log(p)

##计算条件概率
def condition_probability(word,y=1):
	#正常 y=1 广告 y=0
	if total_dic.__contains__(word):
		temp=total_dic[word]
		# print(word,temp)
		p=(temp[y]+1)/(2*(temp[0]+temp[1]))
		# print(word,p)
		return p
	# 如果字典中不存在,使用先验概率代替
	return p_no if y==1 else p_abnormal

#贝叶斯建模
def bayes(content):
	content=normalizing(content)
	words=jieba.cut(content)
	correct,incorrect=log(p_no),log(p_abnormal)
	for w in words:
		if w=="" or w==" ":
			continue
		#正常 y=1 广告 y=0
		correct+=log(condition_probability(w,y=1))
		incorrect+=log(condition_probability(w,y=0))
	# print(correct,incorrect)
	return 1 if correct>incorrect else 0


#评估系统

res=[]
fact=[0 for _ in range(50)]+[1 for _ in range(50)]

for  content in email_test:
	res.append(bayes(content))
print("\n==================测试结束===================\n")
print(f"测试数目:{len(res)}")

TP,FP,TN,FN=0,0,0,0

for i in range(len(fact)):
	if fact[i]==res[i]:
		if fact[i]==1:
			TP+=1
		else:
			TN+=1
	else:
		if fact[i]==1 :
			FN+=1
		else:
			FP+=1
precision=100*TP/(TP+FP)
recall=100*TP/(TP+FN)
f1=(2*precision*recall)/(precision+recall)
print(f"正确率:{TP+TN}%   \n精确率:{precision}% \n召回率:{recall}%\nF1-measure:{f1}%")
print("\n==================测试结束===================\n")

附录2 数据集 提取码: 9v1v

如有问题,欢迎指正。

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值