在机器学习课上,我们学习了朴素贝叶斯算法,这是一种简单但非常有效的分类算法。老师用垃圾邮件过滤器作为示例,讲解了朴素贝叶斯算法的原理和应用。我深深被这种算法的巧妙之处所吸引,它基于特征条件独立性的假设,利用贝叶斯定理进行分类,即使在数据量较小的情况下也能表现出色。
目录
一、朴素贝叶斯算法
朴素贝叶斯算法是一种基于贝叶斯定理的概率分类算法。它假设特征之间相互独立,并利用贝叶斯定理计算后验概率,从而进行分类。 朴素贝叶斯分类器采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立。换言之,假设每个属性独立地对分类结果发生影响。
其中d为属性数目,xi为x在第i个属性上的取值。xi实际上是一个“属性-值”对,例如“色泽=青绿”。为便于讨论,在上下文明确时,有时我们用xi表示第i个属性对应的变量(如“色泽”),有时直接用其指代x在第i个属性上的取值(如“青绿”)。
以上为朴素贝叶斯分类器的表达式
显然,朴素贝叶斯分类器的训练过程就是基于训练集D来估计类先验概率P(c),并为每个属性估计条件概率P(xi|c)。
令Dc表示训练集D中第c类样本组成的集合,若有充足的独立同分布样本,则可容易地估计出类先验概率
对离散属性而言,令Dc,xi表示Dc中在第i个属性上取值为xi的样本组成的集合,则条件概率P(xi|c)可估计为
下面我们用西瓜数据集3.0训练一个朴素贝叶斯分类器,对测试例“测1”进行分类:
首先估计类先验概率P(c),显然有
然后,为每个属性估计条件概率P(xi|c):
于是,有
P(好瓜=是)×P青绿|是×P蜷缩|是×P浊响|是×P清晰|是×P凹陷|是×P硬滑|是×p密度:0.697|是×p含糖:0.460|是≈0.063,
P(好瓜=否)×P青绿|否×P蜷缩|否×P浊响|否×P清晰|否×P凹陷|否×P硬滑|否×p密度:0.697|否×p含糖:0.460|否≈6.80×10-5。
实践中常通过取对数的方式来将“连乘”转化为“连加”以避免数值下溢。
由于0.063>6.80×10-5,因此,朴素贝叶斯分类器将测试样本“测1”判别为“好瓜”。
需注意,若某个属性值在训练集中没有与某个类同时出现过,则直接基于式(7.17)进行概率估计,再根据式(7.15)进行判别将出现问题。例如,在使用西瓜数据集3.0训练朴素贝叶斯分类器时,对一个“敲声=清脆”的测试例,有
由于式(7.15)的连乘式计算出的概率值为零,因此,无论该样本的其他属性是什么,哪怕在其他属性上明显像好瓜,分类的结果都将是“好瓜=否”,这显然不太合理。
为了避免其他属性携带的信息被训练集中未出现的属性值“抹去”,在估计概率值时通常要进行“平滑”(smoothing),常用“拉普拉斯修正”(Laplacian correction)。具体来说,令N表示训练集D中可能的类别数,Ni表示第i个属性可能的取值数,则式(7.16)和(7.17)分别修正为
例如,在本节的例子中,类先验概率可估计为
类似地,P清脆|是和P清脆|否可估计为
同时,上文提到的概率P清脆|是可估计为
拉普拉斯修正实质上假设了属性值与类别均匀分布,这是在朴素贝叶斯学习过程中额外引入的关于数据的先验。
显然,拉普拉斯修正避免了因训练集样本不充分而导致概率估值为零的问题,并且在训练集变大时,修正过程所引入的先验(prior)的影响也会逐渐变得可忽略,使得估值渐趋向于实际概率值。
二、具体步骤
(1)收集训练数据集,包括特征和对应的类别标签。
(2)计算各个类别的先验概率,即P(Ci),表示在所有类别中选择类别Ci的概率。
(3)计算各个特征在给定类别下的条件概率,即P(X|Ci),表示在类别Ci下特征X出现的概率。
(4)利用贝叶斯定理计算后验概率,即P(Ci|X),表示在给定特征X的条件下选择类别Ci的概率。
(5)选择具有最大后验概率的类别作为分类结果。
三、优点与缺点
(1)优点: a. 算法简单,易于实现;
b. 对于小规模数据集,表现良好;
c. 在特征独立性强的情况下,效果较好;
d. 对噪音数据具有较强的鲁棒性。
(2)缺点: a. 朴素贝叶斯算法假设特征之间相互独立,这在某些情况下可能不符合实际;
b. 对于大规模数据集和高维数据,算法性能可能下降;
c. 对输入数据的准备工作要求较高,需要进行特征工程。
四、垃圾邮件过滤
- 读取邮件数据集并进行预处理:将文本转换为小写、过滤掉所有符号和数字等。
- 构建词典并计算词在垃圾邮件和非垃圾邮件中出现的次数。
- 计算测试邮件的垃圾邮件和非垃圾邮件的概率,并通过比较概率来判断邮件类型。
import os
import re
import string
import math
import numpy as np
# 过滤数字
def replace_num(txt_str):
txt_str = re.sub(r'\d+', '', txt_str)
return txt_str
def get_filtered_str(category):
email_list = []
translator = str.maketrans('', '', 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 = txt_str.translate(translator)
# 过滤掉全部数字
txt_str = replace_num(txt_str)
# 把全体的邮件文本 根据换行符把string划分成列表
txt_str_list = txt_str.splitlines()
# 把获取的全体单词句子列表转成字符串
txt_str = ''.join(txt_str_list)
email_list.append(txt_str)
return email_list
def get_dict_w(email_list):
all_email_words = []
word_set = set()
for email_str in email_list:
email_words = email_str.split(' ')
all_email_words.append(email_words)
word_set.update(email_words)
word_dict = {}
for word in word_set:
word_dict[word] = 0
for email_words in all_email_words:
if word in email_words:
word_dict[word] += 1
return word_dict
def get_X_probability(word_dict, email_file):
translator = str.maketrans('', '', string.punctuation)
with open(email_file, 'r', encoding='utf-8') as f:
txt_str = f.read()
txt_str = txt_str.lower()
txt_str = txt_str.translate(translator)
txt_str = replace_num(txt_str)
email_words = txt_str.split(' ')
x_set = set(word for word in email_words if word != '')
x_num = [word_dict[word] for word in x_set if word in word_dict]
w_appear_sum_num = sum(x_num) + 1
return w_appear_sum_num / (len(word_dict) + 2)
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 = get_X_probability(spam_w_dict, file_name)
p_X_c2 = get_X_probability(ham_w_dict, file_name)
A = np.log(p_X_c1) + np.log(1 / 2)
B = np.log(p_X_c2) + np.log(1 / 2)
print(f'p1={A},p2={B}')
if A > B:
print('p1>p2,所以是垃圾邮件.')
else:
print('p1<p2,所以是正常邮件.')
if __name__ == '__main__':
spam_email_list = get_filtered_str('spam')
ham_email_list = get_filtered_str('ham')
spam_w_dict = get_dict_w(spam_email_list)
ham_w_dict = get_dict_w(ham_email_list)
email_test(spam_w_dict, ham_w_dict)
四、总结
朴素贝叶斯算法是一种简单而强大的分类工具,在许多实际问题中得到广泛应用。它通过计算先验概率和条件概率,利用贝叶斯定理进行分类,具有较好的效果。然而,我们也要意识到朴素贝叶斯算法对特征独立性的假设以及对数据准备的要求,以便在实际应用中更好地掌握其优势和局限性。