朴素贝叶斯
朴素贝叶斯法(Naive Bayes model)是基于贝叶斯定理与特征条件独立假设的分类方法
朴素贝叶斯方法是在贝叶斯算法的基础上进行了相应的简化,即假定给定目标值时属性之间相互条件独立。也就是说没有哪个属性变量对于决策结果来说占有着较大的比重,也没有哪个属性变量对于决策结果占有着较小的比重。虽然这个简化方式在一定程度上降低了贝叶斯分类算法的分类效果,但是在实际的应用场景中,极大地简化了贝叶斯方法的复杂性。
朴素贝叶斯分类器概念
朴素贝叶斯分类器是一系列以假设特征之间强(朴素)独立下运用贝叶斯定理为基础的简单概率分类器。该分类器模型会给问题实例分配用特征值表示的类标签,类标签取自有限集合。它不是训练这种分类器的单一算法,而是一系列基于相同原理的算法,朴素贝叶斯分类(NBC)是以贝叶斯定理为基础并且假设特征条件之间相互独立的方法,先通过已给定的训练集,以特征词之间独立作为前提假设,学习从输入到输出的联合概率分布,再基于学习到的模型,输入X求出使得后验概率最大的输出Y。
设每个数据样本用一个n维特征向量来描述n个属性的值,即:X={x1,x2,…,xn},假定有m个类,分别用C1, C2,…,Cm表示。给定一个未知的数据样本X(即没有类标号),若朴素贝叶斯分类法将未知的样本X分配给类Ci,则一定是
由于P(X)对于所有类为常数,最大化后验概率P(Ci|X)可转化为最大化先验概率P(X|Ci)P(Ci)。如果训练数据集有许多属性和元组,计算P(X|Ci)的开销可能非常大,为此,通常假设各属性的取值互相独立,这样先验概率P(x1|Ci),P(x2|Ci),…,P(xn|Ci)可以从训练数据集求得。
- P(A)是先验概率
- P(B)是先验概率
- P(B|A)是条件概率,也叫似然概率
- P(A|B)是后验概率,一般是求解的目标
核心思想:
对于给出的待分类样本,求解在此样本出现的条件下各个类别出现的概率,哪个概率最大,就认为此待分类样本属于哪个类别。
分类具体步骤
1. 收集大量的垃圾邮件和非垃圾邮件,建立垃圾邮件集和非垃圾邮件集。
2. 提取邮件主题和邮件体中的独立字符串,例如 ABC32,¥234等作为TOKEN串并统计提取出的TOKEN串出现的次数即字频。按照上述的方法分别处理垃圾邮件集和非垃圾邮件集中的所有邮件。
3. 每一个邮件集对应一个哈希表,hashtable_good对应非垃圾邮件集而hashtable_bad对应垃圾邮件集。表中存储TOKEN串到字频的映射关系。
4. 计算每个哈希表中TOKEN串出现的概率P=(某TOKEN串的字频)/(对应哈希表的长度)。
5. 综合考虑hashtable_good和hashtable_bad,推断出当新来的邮件中出现某个TOKEN串时,该新邮件为垃圾邮件的概率。数学表达式为:
A 事件 ---- 邮件为垃圾邮件;
t1,t2 …….tn 代表 TOKEN 串
则 P ( A|ti )表示在邮件中出现 TOKEN 串 ti 时,该邮件为垃圾邮件的概率。
设
P1 ( ti ) = ( ti 在 hashtable_good 中的值)
P2 ( ti ) = ( ti 在 hashtable_ bad 中的值)
则
6. 建立新的哈希表hashtable_probability存储TOKEN串ti到P(A|ti)的映射。
7. 至此,垃圾邮件集和非垃圾邮件集的学习过程结束。根据建立的哈希表 hashtable_probability可以估计一封新到的邮件为垃圾邮件的可能性。
当新到一封邮件时,按照步骤2,生成TOKEN串。查询hashtable_probability得到该TOKEN 串的键值。
假设由该邮件共得到N个TOKEN 串,t1,t2…….tn,hashtable_probability中对应的值为 P1 , P2 , ……PN, P(A|t1 ,t2, t3……tn) 表示在邮件中同时出现多个TOKEN串t1,t2……tn时,该邮件为垃圾邮件的概率。
由复合概率公式可得P(A|t1 ,t2, t3……tn)=(P(t1 A)*P(t2 A)*……P(tn A))/[(P(t1 A)*P(t2 A)*……P(tn A))+(P(t1 B)*P(t2 B)*……P(tn B))],当 P(A|t1 ,t2, t3……tn) 超过预定阈值时,就可以判断邮件为垃圾邮件。
朴素贝叶斯算法优缺点:
优点
1、在数据较少的情况下仍然有效,可以处理多类别问题;
2、稳定的分类效率;
3、对缺失数据不太敏感,算法比较简单;
4、分类准确率高,速度快;
缺点:
1、由于使用了样本属性独立性的假设,所以如果特征属性有关联时效果不太好;
2、需要计算先验概率,而先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳;
3、适用数据类型:标称型数据
具体实现
数据集说明:非垃圾邮件ham和垃圾邮件spam各25封,测试邮件7封,
非垃圾邮件ham:
垃圾邮件spam:
测试部分具体内容
"""
Created on Sun Nov 19 17:56:40 2023
@author: HP_USER
"""
import os
import re
import string
import math
import numpy as np
# 过滤数字
def replace_num(txt_str):
txt_str = txt_str.replace(r'0', '')
txt_str = txt_str.replace(r'1', '')
txt_str = txt_str.replace(r'2', '')
txt_str = txt_str.replace(r'3', '')
txt_str = txt_str.replace(r'4', '')
txt_str = txt_str.replace(r'5', '')
txt_str = txt_str.replace(r'6', '')
txt_str = txt_str.replace(r'7', '')
txt_str = txt_str.replace(r'8', '')
txt_str = txt_str.replace(r'9', '')
return txt_str
def get_filtered_str(category):
email_list = []
translator = re.compile('[%s]' % re.escape(string.punctuation))
for curDir, dirs, files in os.walk(f'C:/Users/HP_USER/Desktop/email/{category}'):
for file in files:
file_name = os.path.join(curDir, file)
with open(file_name, 'r', encoding='gbk') 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'C:/Users/HP_USER/Desktop/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 = len(os.listdir(f'C:/Users/HP_USER/Desktop/email/ham'))
w_appear_sum_num = 1
for num in ham_X_num:
w_appear_sum_num += num
# print(f'\nspam_w_appear_sum_num={w_appear_sum_num}')
# 求概率
w_c2_p = w_appear_sum_num / (ham_len+2)
return w_c2_p
def email_test(spam_w_dict,ham_w_dict):
for curDir, dirs, files in os.walk(f'C:/Users/HP_USER/Desktop/email/seshi'):
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)
# 因为分母一致,所以只比较 分子即可
if A > B:
print('注意!垃圾邮件.')
if A <= B:
print('正常邮件.')
if __name__=='__main__':
spam_email_list = get_filtered_str('spam')
ham_email_list = get_filtered_str('ham')
spam_w_dict = get_dict_spam_dict_w(spam_email_list)
ham_w_dict = get_dict_ham_dict_w(spam_email_list,ham_email_list)
# print(f'\n从垃圾邮件中提取的特征及每个特征出现的邮件数:')
# print(f'spam_w_dict={spam_w_dict}')
# print(f'\n普通邮件中垃圾邮件特征出现的邮件数为:')
# print(f'ham_w_dict={ham_w_dict}')
email_test(spam_w_dict, ham_w_dict)
出现的问题
程序无法分类,测试数据全显示正常邮件
问题所在:遍历垃圾文件和非垃圾邮件的指定目录错误,错把测试邮件文件夹放入
解决方法:修改遍历目录
参考文献:
【朴素贝叶斯】中文垃圾邮件分类_复现高斯朴素贝叶斯并应用于中文垃圾邮件分类_可乐大牛的博客-CSDN博客