使用贝叶斯识别垃圾邮件分类

1 简介

垃圾邮件或者垃圾短信通常让网络运营商和用户十分头疼,尤其是如今大数据时代,每个人都被分析分类的很准确,而且个人信息比如邮箱,电话泄露的机会也越来越大,广告商更愿意在此做手脚,达到广告定点投放的任务。并且由于这些广告和自身的信息又十分紧密,或者常常携带一些“正常消息”来蒙混过关,因此,能够对垃圾邮件做分类并尽可能地隔离这些邮件,显得越来越重要。

本文将用到朴素贝叶斯的方法,使用python,实现对英文垃圾邮件的分类。

所有代码及数据集放在我的仓库中,https://github.com/Guoxn1/ai,根据文章类别和名称就可以找到了。如果给到您帮助,请给我的仓库一个star,这将是我持续创作的动力。

和原来不一样的地方:

1.数据在data目录下,下面有三个文件夹,其中ham存储正常邮件,spam存储垃圾邮件,test是测试用例。其中test和前面两个文件夹内容不重复(已做处理)。

2.这节还会用到sklearn的英文停用词,以此来提高准确率。

3.计算词频的时候,原作者用的是计算正常邮件中的词在正常邮件中出现的词频和垃圾邮件中的词在正常邮件中出现的词频。本文是正常邮件中的词在所有邮件中出现的词频和垃圾邮件中的词在所有邮件中出现的词频。好处就是宽容性更好,因为有了更大的词汇数,可以对“未知词”进行处理,缺点是可能导致很多词用到拉普拉斯平滑,从而降低准确率。

2 朴素贝叶斯分类原理

P(Y|X) = (P(X|Y) * P(Y)) / P(X)

通俗来说,判断一个邮件是不是垃圾邮件,就对其中每个词进行判断,看其在垃圾邮件和正常邮件的概率(词频),由于是朴素贝叶斯,直接相乘就行,最后看概率,谁大就是哪种情况。

如何看一个词在垃圾邮件和正常邮件的概率呢,就需要用到贝叶斯公式。贝叶斯公式的左侧就表示一个词X在状态Y下的概率,右侧是Y状态下X出现的概率和Y出现的概率相乘再除以X出现的概率。

举个例子:

hello在正常邮件中出现的概率就是正常邮件中X的概率*正常邮件出现的概率。这里不再除以分母,因为不管是正常邮件还是垃圾邮件分母都一样,比较时可以不看分母。

有了一个词的概率,就可以算整个文章所有词的概率,因为这里我们假设所有词出现是独立的,所以概率就是相乘状态,为了避免数值过小导致截断,改为log(概率),也就是概率相加,后面在代码中会体现。

3 数据预处理

3.1 删除不想关的数据

比如,停用词,标点,数字。

3.2 调整英文单词都是大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import os
import re
from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS

# 定义一个函数,对文件中的内容进行预处理,比如删除一些值
def clear_content(content):
# 只保留英文字符
filtered_content = re.sub(r'[^a-zA-Z\s]', '', content)
filtered_content = filtered_content.lower()
# 根据换行,将其分成一个一个列表 或者把其中换行 制表符 改为空格
filtered_content = filtered_content.replace("\n"," ")
filtered_content = filtered_content.replace("\t"," ")
# 切分成单词
filtered_content_list = filtered_content.split(" ")
filtered_content_without_stopwords = [word for word in filtered_content_list if word not in list(ENGLISH_STOP_WORDS)]
filtered_content_without_stopwords = [word for word in filtered_content_without_stopwords if word.strip() != ""]
return filtered_content_without_stopwords


# 定义一个函数,对输入的文件夹的文件进行遍历
def preprocess(folderpath):
folderpath = folderpath

email_list = []
for filename in os.listdir(folderpath):
content = ""
file_path = os.path.join(folderpath,filename)
with open(file_path,mode="r",encoding="gbk") as f:
content = f.read()
content = clear_content(content)

email_list.append(content)
return email_list


获得所有email的单词的列表(二维的)。

1
2
3
ham_email_list = preprocess("data/ham")
spam_email_list = preprocess("data/spam")
print(spam_email_list)

4 数据处理-构建词频字典

构建正常的:

所有的邮件包括正常和垃圾邮件的单词,在正常邮件出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def get_ham_dic(ham_email_list,spam_email_list):
word_set = set()

# 记录所有种类的单词,正常邮件和垃圾邮件种类的单词

for email in ham_email_list:
for word in email:
word_set.add(word)
for email in spam_email_list:
for word in email:
word_set.add(word)
# 计算每个词在正常邮件出现的次数

word_dict = {}

for word in word_set:
word_dict[word] = 0

for email in ham_email_list:
for word1 in email:
if (word==word1):

word_dict[word]+=1
break
return word_dict

ham_w_dict = get_ham_dic(ham_email_list,spam_email_list)
print(ham_w_dict)


字典中每个key代表所有所有邮件的一个词,value代表它在正常邮件出现的次数。

所有的邮件包括正常和垃圾邮件的单词,在垃圾邮件出现的次数。

构建垃圾邮件的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def get_spam_dic(ham_email_list,spam_email_list):
all_words = []
word_set = set()
# 记录所有种类的单词,正常邮件和垃圾邮件种类的单词
for email in ham_email_list:
for word in email:
word_set.add(word)
for email in spam_email_list:
for word in email:
word_set.add(word)

# 计算每个词在垃圾邮件出现的次数

word_dict = {}

for word in word_set:
word_dict[word] = 0

for email in spam_email_list:
for word1 in email:
if (word==word1):

word_dict[word]+=1
break
return word_dict

spam_w_dict = get_spam_dic(ham_email_list,spam_email_list)
print(spam_w_dict)


字典中每个key代表所有所有邮件的一个词,value代表它在垃圾邮件出现的次数。

5 数据处理-计算概率

计算一个文档在正常邮件中的概率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 计算在正常邮件中出现的概率
def get_ham_rate(filename,ham_w_dict):
with open(filename,mode="r") as f:
content = f.read()
content = clear_content(content)
test_set = set()
for word in content:
test_set.add(word)

ham_email_num = len(os.listdir(f"data/ham"))
# 记录每个词的数目
ham_num = []
for x in test_set:
for w in ham_w_dict:
if x==w:
ham_num.append(ham_w_dict[w])

# 拉普拉斯平滑
laplasi = 1
# 这里采用了加法,因为乘法会过小,相当于用到了log,后面会有体现
for num in ham_num:
laplasi += num
ham_rate = laplasi/(ham_email_num+2)
return ham_rate




计算一个文档在垃圾邮件中的概率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 计算在垃圾邮件中出现的概率
def get_spam_rate(filename,spam_w_dict):
with open(filename,mode="r") as f:
content = f.read()
content = clear_content(content)
test_set = set()
for word in content:
test_set.add(word)

spam_email_num = len(os.listdir(f"data/spam"))
# 记录每个词的数目
spam_num = []
for x in test_set:
for w in spam_w_dict:
if x==w:
spam_num.append(spam_w_dict[w])

# 拉普拉斯平滑
laplasi = 1
# 这里采用了加法,因为乘法会过小,相当于用到了log,后面会有体现
for num in spam_num:
laplasi += num
spam_rate = laplasi/(spam_email_num+2)
return spam_rate



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~~~

~~~python
import numpy as np
def email_divide(folderpath):

for filename in os.listdir(folderpath):
file_path = os.path.join(folderpath,filename)
print(f"{file_path}")
ham = get_ham_rate(file_path,ham_w_dict)+ np.log(1 / 2)
spam = get_spam_rate(file_path,spam_w_dict)+ np.log(1 / 2)
if spam > ham:
print('p1>p2,所以是垃圾邮件.')
else:
print('p1<p2,所以是正常邮件.')
email_divide("data/test")
image-20231026214256839

最后的结果还是较为准确的。


使用贝叶斯识别垃圾邮件分类
http://example.com/2023/10/22/使用贝叶斯识别垃圾邮件/
作者
Guoxin
发布于
2023年10月22日
许可协议