1.贝叶斯定理
朴素贝叶斯是贝叶斯决策理论的一部分,因此我们在学习朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论
先验概率:即基于统计的概率,是基于以往历史经验和分析得到的结果,不需要依赖当前发生的条件。
后验概率:则是从条件概率而来,由因推果,是基于当下发生了事件之后计算的概率,依赖于当前发生的条件。
条件概率:记事件A发生的概率为P(A),事件B发生的概率为P(B),则在B事件发生的前提下,A事件发生的概率即为条件概率,记为P(A|B)。
贝叶斯公式:贝叶斯公式则是通过P(B|A)来求P(A|B)
P(A|B) = (P(B|A) * P(A)) / P(B)
全概率公式:表示若事件构成一个完备事件组且都有正概率,则对任意一个事件A都有公式成立:
P(A) = ∑[i=1 to n] P(A|Bᵢ) * P(Bᵢ)
其中,P(A)表示事件A的概率,P(A|Bᵢ)表示在事件Bᵢ发生的条件下,事件A发生的概率,P(Bᵢ)表示事件Bᵢ的概率。
将其带入带贝叶斯公式:
2.朴素贝叶斯
2.1应用情景
朴素贝叶斯算法是一种基于贝叶斯定理和特征条件独立假设的分类算法。在机器学习领域,它被广泛应用于文本分类、垃圾邮件过滤、情感分析等任务中。
2.2特征条件假设
该算法的核心思想是根据已知类别的特征来预测新样本所属的类别。它基于特征之间的条件独立性假设,即假设给定类别下特征之间相互独立,这使得计算简化,降低了模型的复杂度。例如对于给定训练集,其中每个样本包含特征为n,即x=(x1,x2,.....xn),类标记集合有k中类别y=(y1,y2,....yk)。
2.3朴素贝叶斯分类器
朴素贝叶斯分类器是基于贝叶斯定理和特征条件独立假设的分类算法。在给定一个样本 x 和类别 y 的情况下,朴素贝叶斯分类器可以计算出样本属于每个类别的后验概率 P(y∣x),然后选择具有最高后验概率的类别作为样本的预测类别。
根据贝叶斯定理,后验概率表示为:
P(y∣x)=P(x∣y)P(y)/p(x)
其中,P(x∣y) 是在给定类别 y 的条件下,样本 x 出现的概率,P(y) 是类别 y 的先验概率;P(x) 是样本 x 出现的概率。
由于朴素贝叶斯假设特征之间相互独立,因此条件概率 P(x∣y) 可以被分解为每个特征 xi 的条件概率的乘积:
P(x∣y)=(i=1∏n)P(xi∣y)
将上述式子带入贝叶斯公式中,可以得到
分类器可表示为:
经过简化省略常数P(x)后得到:
2.4拉普拉斯平滑
概念:拉普拉斯平滑(Laplace smoothing)是一种常用的朴素贝叶斯分类器的平滑方法,用于解决在训练集中出现次数为零的特征或类别对分类器造成的影响问题。当训练集中某个特征在某个类别下的出现次数为0时,根据极大似然估计法,这个特征对应的条件概率为0,会导致整个后验概率为0,从而无法进行分类。拉普拉斯平滑通过给所有特征和类别的出现次数加上一个常数 a,来解决这个问题。
示例如下:p(y)=(|Dy|+a)/(|D|+a*N)
2.5防溢出策略
条件概率乘法计算过程中,因子一般较小(均是小于1的实数)。当属性数量增多时候,会导致累乘结果下溢出的现象。
在代数中有ln(a*b)=lna+lnb,因此可以把条件概率累乘转化成对数累加。分类结果仅需对比概率的对数累加法运算后的数值,以确定划分的类别。
3实战——朴素贝叶斯算法进行垃圾邮件分类
3.1数据集准备
我们准备ham和spam各25封,其中ham为正常邮件,spam为垃圾邮件。并选取ham和spam中的一些邮件作为测试集。
正常邮件如图:
垃圾邮件如图所示:
3.2代码实现
数据的导入
import os
import re
import string
import math
DATA_DIR = 'enron'
target_names = ['ham', 'spam']
def get_data(DATA_DIR):
subfolders = ['enron%d' % i for i in range(1,7)] #获得enron下面的文件夹
data = []
target = []
for subfolder in subfolders:
#垃圾邮件 spam
spam_files = os.listdir(os.path.join(DATA_DIR, subfolder, 'spam')) #将文件夹路径进行组合
for spam_file in spam_files: #遍历所有垃圾文件
with open(os.path.join(DATA_DIR, subfolder, 'spam', spam_file), encoding="latin-1") as f:
data.append(f.read())
target.append(1)
#正常邮件 pam
ham_files = os.listdir(os.path.join(DATA_DIR, subfolder, 'ham'))
for ham_file in ham_files:
with open(os.path.join(DATA_DIR, subfolder, 'ham', ham_file), encoding="latin-1") as f:
data.append(f.read())
target.append(0)
return data, target
X, y = get_data(DATA_DIR)
#print(X,y)
其中1表示垃圾邮件,0表示正常邮件。
定义一个类对导入数据进行预处理
def get_filtered_str(category):
email_list = []
translator = re.compile('[%s]' % re.escape(string.punctuation))
for curDir, dirs, files in os.walk(f'./email/{category}'):
for file in files:
file_name = os.path.join(curDir, file)
with open(file_name, 'r', encoding='utf-8') as f:
txt_str = f.read()
# 全部小写
txt_str = txt_str.lower()
# 过滤掉所有符号
txt_str = translator.sub(' ', txt_str)
# 过滤掉全部数字
txt_str = replace_num(txt_str)
# 把全体的邮件文本 根据换行符把string划分成列表
txt_str_list = txt_str.splitlines()
# 把获取的全体单词句子列表转成字符串
txt_str = ''.join(txt_str_list)
# print(txt_str)
email_list.append(txt_str)
return email_list
数据的处理和数据集划分和垃圾邮件特征提取
def get_dict_spam_dict_w(spam_email_list):
'''
:param email_list: 每个邮件过滤后形成字符串,存入email_list
:param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
:return:
'''
all_email_words = []
# 用set集合去重
word_set = set()
for email_str in spam_email_list:
# 把每个邮件的文本 变成单词
email_words = email_str.split(' ')
# 把每个邮件去重后的列表 存入列表
all_email_words.append(email_words)
for word in email_words:
if(word!=''):
word_set.add(word)
# 计算每个垃圾词出现的次数
word_dict = {}
for word in word_set:
# 创建字典元素 并让它的值为1
word_dict[word] = 0
# print(f'word={word}')
# 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
for email_words in all_email_words:
for email_word in email_words:
# print(f'spam_email={email_word}')
# 把从set中取出的word 和 每个email分词后的word对比看是否相等
if(word==email_word):
word_dict[word] += 1
# 找到一个就行了
break
# 计算垃圾词的概率
# spam_len = len(os.listdir(f'./email/spam'))
# print(f'spam_len={spam_len}')
# for word in word_dict:
# word_dict[word] = word_dict[word] / spam_len
return word_dict
def get_dict_ham_dict_w(spam_email_list,ham_email_list):
'''
:param email_list: 每个邮件过滤后形成字符串,存入email_list
:param all_email_words: 列表。把所有的邮件内容,分词。一个邮件的词 是它的一个列表元素
:return:
'''
all_ham_email_words = []
# 用set集合去重 得到垃圾邮件的特征w
word_set = set()
#获取垃圾邮件特征
for email_str in spam_email_list:
# 把每个邮件的文本 变成单词
email_words = email_str.split(' ')
for word in email_words:
if (word != ''):
word_set.add(word)
for ham_email_str in ham_email_list:
# 把每个邮件的文本 变成单词
ham_email_words = ham_email_str.split(' ')
# print(f'ham_email_words={ham_email_words}')
# 把每个邮件分割成单词的 的列表 存入列表
all_ham_email_words.append(ham_email_words)
# print(f'all_ham_email_words={all_ham_email_words}')
# 计算每个垃圾词出现的次数
word_dict = {}
for word in word_set:
# 创建字典元素 并让它的值为1
word_dict[word] = 0
# print(f'word={word}')
# 遍历每个邮件,看文本里面是否有该单词,匹配方法不能用正则.邮件里面也必须是分词去重后的!!! 否则 比如出现re是特征, 那么remind 也会被匹配成re
for email_words in all_ham_email_words:
# print(f'ham_email_words={email_words}')
for email_word in email_words:
# 把从set中取出的word 和 每个email分词后的word对比看是否相等
# print(f'email_word={email_word}')
if(word==email_word):
word_dict[word] += 1
# 找到一个就行了
break
return word_dict
# 获取测试邮件中出现的 垃圾邮件特征
def get_X_c1(spam_w_dict,file_name):
# 获取测试邮件
# file_name = './email/spam/25.txt'
# 过滤文本
translator = re.compile('[%s]' % re.escape(string.punctuation))
with open(file_name, 'r', encoding='utf-8') as f:
txt_str = f.read()
# 全部小写
txt_str = txt_str.lower()
# 过滤掉所有符号
txt_str = translator.sub(' ', txt_str)
# 过滤掉全部数字
txt_str = replace_num(txt_str)
# 把全体的邮件文本 根据换行符把string划分成列表
txt_str_list = txt_str.splitlines()
# 把获取的全体单词句子列表转成字符串
txt_str = ''.join(txt_str_list)
# 把句子分成词
email_words = txt_str.split(' ')
# 去重
x_set = set()
for word in email_words:
if word!='':
x_set.add(word)
# print(f'\ntest_x_set={x_set}')
spam_len = len(os.listdir(f'./email/spam'))
# 判断测试邮件的词有哪些是垃圾邮件的特征
spam_X_num = []
for xi in x_set:
for wi in spam_w_dict:
if xi == wi:
spam_X_num.append(spam_w_dict[wi])
# print(f'\nspam_X_num={spam_X_num}')
w_appear_sum_num = 1
for num in spam_X_num:
w_appear_sum_num += num
# print(f'\nham_w_appear_sum_num={w_appear_sum_num}')
# 求概率
w_c1_p = w_appear_sum_num / (spam_len + 2)
return w_c1_p
# 获取测试邮件中出现的非垃圾邮件特征
def get_X_c2(ham_w_dict,file_name):
# 过滤文本
translator = re.compile('[%s]' % re.escape(string.punctuation))
with open(file_name, 'r', encoding='utf-8') as f:
txt_str = f.read()
# 全部小写
txt_str = txt_str.lower()
# 过滤掉所有符号
txt_str = translator.sub(' ', txt_str)
# 过滤掉全部数字
txt_str = replace_num(txt_str)
# 把全体的邮件文本 根据换行符把string划分成列表
txt_str_list = txt_str.splitlines()
# 把获取的全体单词句子列表转成字符串
txt_str = ''.join(txt_str_list)
# 把句子分成词
email_words = txt_str.split(' ')
# 去重
x_set = set()
for word in email_words:
if word!='':
x_set.add(word)
# print(f'\ntest_x_set={x_set}')
# 判断测试邮件的词有哪些是垃圾邮件的特征
ham_X_num = []
for xi in x_set:
for wi in ham_w_dict:
if xi == wi:
ham_X_num.append(ham_w_dict[wi])
# print(f'\nham_X_num={ham_X_num}')
# 先求分子 所有词出现的总和
ham_len
用测试集对分类器进行测试
def email_test(spam_w_dict,ham_w_dict):
for curDir, dirs, files in os.walk(f'./email/test'):
for file in files:
file_name = os.path.join(curDir, file)
print('---------------------------------------------------------------')
print(f'测试邮件: {file}')
# 获取条件概率 p(X|c1)
p_X_c1 = get_X_c1(spam_w_dict,file_name)
# 获取条件概率 p(X|c2)
p_X_c2 = get_X_c2(ham_w_dict,file_name)
# print(f'\nX_c1={p_X_c1}')
# print(f'\nX_c2={p_X_c2}')
# #注意:Log之后全部变为负数
A = np.log(p_X_c1) + np.log(1 / 2)
B = np.log(p_X_c2) + np.log(1 / 2)
# 除法会出现问题,-1 / 负分母 结果 < -2/同一个分母
print(f'p1={A},p2={B}')
# 因为分母一致,所以只比较 分子即可
if A > B:
print('p1>p2,所以是垃圾邮件.')
if A <= B:
print('p1<p2,所以是正常邮件.')
3.2运行结果
4实验总结
利用朴素贝叶斯算法实现垃圾邮件分类的好处就是朴素贝叶斯模型发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率,并且算法也比较简单,容易实现,对于小规模的数据效果很不错。但其缺点也是十分明显的,我们在显示生活中的各个特征并不一定是相互独立的,而且这种方法一定要事先知道先验概率。同时,朴素贝叶斯算法对于数据中的噪声和特征缺失比较敏感。在实际应用中,需要对数据进行预处理、特征选择和特征工程等步骤,以提高分类器的性能。