-
内容简介
本文主要通过对垃圾邮件和正常邮件进行关键词统计,进而得出某词在正常邮件中出现的概率和在垃圾邮件中出现的值。
从而判断
P ( 正 常 ∣ 内 容 ) > P ( 垃 圾 ∣ 内 容 ) P(正常|内容)>P(垃圾|内容) P(正常∣内容)>P(垃圾∣内容) 如果为真,则该邮件很有可能是正常邮件,否则更有可能是垃圾邮件。
注:附录1是源码,附录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=1∏nP(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.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")
- 自我总结
这是首次自己写贝叶斯的分类器,很多专业的问题了解的不够清楚,只是对原理的简单应用,如果有问题在慢慢完善。对于字典排序合并那一块,转化为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\-\.,@?^=%&:/~\+#]*[\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]
#读取文件
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
如有问题,欢迎指正。