贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法。
一、朴素贝叶斯
1. 贝叶斯定理
贝叶斯理论是以18世纪的一位神学家托马斯.贝叶斯(Thomas Bayes)命名。通常,事件A在事件B(发生)的条件下的概率,与事件B在事件A(发生)的条件下的概率是不一样的。然而,这两者是有确定的关系的,贝叶斯定理就是这种关系的陈述。
其核心算法
-
先验概率
先验概率是在考虑任何新证据之前,基于先前的经验、知识或信息而获得的概率。这是在收集新数据或进行实证研究之前,对某个事件或假设的初始信仰或估计。先验概率通常基于以往的观察、领域专业知识或其他可用信息。
例如,考虑一个掷硬币的例子。如果我们没有任何信息,我们可能会将正面和反面的概率都估计为0.5,这是一个无偏见的先验概率。但如果我们知道硬币是公正的,我们的先验概率可能会更强烈地偏向0.5。
-
后验概率
后验概率是在考虑了新的证据或数据之后,基于先前的先验概率经过更新得到的概率。在贝叶斯统计学中,后验概率是通过使用贝叶斯定理来结合先验概率和新的证据(数据)而得到的。
2.生成式模型
生成式模型的目标是对整个数据分布进行建模,即学习如何生成数据。它关注的是数据的生成过程,以及给定一些条件,如何生成样本数据。生成式模型试图从训练数据中学习数据的潜在分布。
特点:
- 能够生成新的样本,因为它们学习了数据的生成过程。
- 在处理缺失数据或生成新样本时有优势。
- 在某些情况下,生成式模型对噪声和变化的鲁棒性较好。
3. 判别式模型
判别式模型关注的是对不同类别之间的决策边界进行建模,即学习类别之间的差异。它们通过学习输入数据与相应类别之间的映射关系,来做出对新样本的分类决策。
特点:
- 专注于学习类别之间的差异,因此在分类任务上通常表现较好。
- 通常对噪声和变化的鲁棒性相对较差。
- 不能生成新的样本,因为它们并未学习数据的生成过程。
4.朴素贝叶斯分类器
在分类问题中,我们考虑一个样本属于某一类别的概率。假设有一个样本X,有特征 (X = (x_1, x_2, \ldots, x_n)),而类别标签为C。朴素贝叶斯分类器的任务是计算给定特征的条件下属于类别C的概率,即 (P(C|X))。
其公式可重写为
朴素贝叶斯算法假设所有特征在给定类别的条件下是相互独立的。这被称为朴素贝叶斯的"朴素"之处,因为在实际问题中,很少有特征是完全独立的。这一假设简化了概率计算,但也是朴素贝叶斯算法的一个局限。
5.MAP分类准则
MAP(最大后验概率)分类准则是一种用于分类问题的方法,它基于贝叶斯理论和条件概率来进行决策。MAP分类准则旨在选择具有最大后验概率的类别作为最终的分类结果。
二、垃圾邮件分类
1.训练朴素贝叶斯分类器的模型
ham_count
和spam_count
分别用于统计正常邮件和垃圾邮件的词汇总数。ham_word_count
和spam_word_count
是用于记录每个词汇在正常邮件和垃圾邮件中出现的次数的字典,使用defaultdict
初始化,确保不存在的词汇有默认值为- 通过循环遍历正常邮件文件夹中的每封邮件。
- 使用
codecs.open
打开文件,读取邮件内容。 - 将邮件内容按空格分割成单词,然后遍历每个单词,更新
ham_word_count
中该单词的出现次数,并增加ham_count
的计数。
def train():
ham_count = 0
spam_count = 0
ham_word_count = defaultdict(int)
spam_word_count = defaultdict(int)
# 遍历正常邮件文件夹,统计词汇频率
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
words = email_content.split()
for word in words:
ham_word_count[word] += 1
ham_count += 1
# 遍历垃圾邮件文件夹,统计词汇频率
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
words = email_content.split()
for word in words:
spam_word_count[word] += 1
spam_count += 1
# 计算每个词汇在正常邮件和垃圾邮件中出现的概率
ham_prob = {}
spam_prob = {}
for word in ham_word_count:
ham_prob[word] = ham_word_count[word] / ham_count
for word in spam_word_count:
spam_prob[word] = spam_word_count[word] / spam_count
# 计算正常邮件和垃圾邮件的先验概率
total_count = ham_count + spam_count
ham_prior_prob = ham_count / total_count
spam_prior_prob = spam_count / total_count
return ham_prob, spam_prob, ham_prior_prob, spam_prior_prob
2.进行邮件分类
classify
函数接收一封邮件的内容email_content
以及之前计算得到的概率和先验概率作为参数。- 将邮件内容按空格分割成单词存储在
words
列表中。 ham_score
和spam_score
分别初始化为先验概率ham_prior_prob
和spam_prior_prob
。- 通过循环遍历邮件中的每个单词,在
ham_prob
和spam_prob
中查找对应单词的概率,并乘到ham_score
和spam_score
上。 - 最后根据得分比较判断邮件分类为 "正常邮件" 或 "垃圾邮件
classify_emails
函数遍历了所有的邮件文件,分别从正常邮件和垃圾邮件文件夹中读取每一封邮件。- 对每一封邮件,它调用
classify
函数进行分类,并打印出该邮件的分类结果。这里通过文件路径读取邮件内容,然后调用classify
函数来确定邮件的分类结果。
# 进行分类操作
def classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob):
words = email_content.split()
ham_score = ham_prior_prob
spam_score = spam_prior_prob
for word in words:
if word in ham_prob:
ham_score *= ham_prob[word]
if word in spam_prob:
spam_score *= spam_prob[word]
# 判断分类结果
if ham_score > spam_score:
return "正常邮件"
else:
return "垃圾邮件"
# 读取每封邮件进行分类
def classify_emails(ham_prob, spam_prob, ham_prior_prob, spam_prior_prob):
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
result = classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob)
print(f"文件 {file_path} 分类结果: {result}")
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
result = classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob)
print(f"文件 {file_path} 分类结果: {result}")
3.使用TF-IDF进行特征选择:
- 创建了
TfidfVectorizer
对象,用于将文本数据转换为TF-IDF特征。 - 创建了两个空列表
X
和y
,分别用于存储文本数据和相应的类别标签。 - 通过循环读取正常邮件和垃圾邮件文件,将邮件内容添加到
X
列表,同时将相应的类别标签添加到y
列表。 - 使用
fit_transform
方法将文本数据转换为TF-IDF特征矩阵X
。 - 类似于正常邮件的遍历过程,这里用于统计垃圾邮件中每个单词的出现次数,同时增加
spam_count
的计数。 - 通过循环遍历正常邮件和垃圾邮件的词汇频率字典,计算每个词汇在对应类别中的概率。
def train_with_feature_selection():
# 使用TF-IDF进行特征选择
vectorizer = TfidfVectorizer()
X = []
y = []
# 读取正常邮件
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
X.append(email_content)
y.append(0) # 0 表示正常邮件
# 读取垃圾邮件
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
X.append(email_content)
y.append(1) # 1 表示垃圾邮件
X = vectorizer.fit_transform(X)
4.运行结果
5. 完整代码
import os
import codecs
from collections import defaultdict
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn import metrics
# 定义文件路径
ham_folder = r'D:\QQ\Ch04\email\ham'
spam_folder = r'D:\QQ\Ch04\email\spam'
# 统计词汇出现频率和计算概率
def train():
ham_count = 0
spam_count = 0
ham_word_count = defaultdict(int)
spam_word_count = defaultdict(int)
# 遍历正常邮件文件夹,统计词汇频率
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
words = email_content.split()
for word in words:
ham_word_count[word] += 1
ham_count += 1
# 遍历垃圾邮件文件夹,统计词汇频率
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
words = email_content.split()
for word in words:
spam_word_count[word] += 1
spam_count += 1
# 计算每个词汇在正常邮件和垃圾邮件中出现的概率
ham_prob = {}
spam_prob = {}
for word in ham_word_count:
ham_prob[word] = ham_word_count[word] / ham_count
for word in spam_word_count:
spam_prob[word] = spam_word_count[word] / spam_count
# 计算正常邮件和垃圾邮件的先验概率
total_count = ham_count + spam_count
ham_prior_prob = ham_count / total_count
spam_prior_prob = spam_count / total_count
return ham_prob, spam_prob, ham_prior_prob, spam_prior_prob
# 进行分类操作
def classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob):
words = email_content.split()
ham_score = ham_prior_prob
spam_score = spam_prior_prob
for word in words:
if word in ham_prob:
ham_score *= ham_prob[word]
if word in spam_prob:
spam_score *= spam_prob[word]
# 判断分类结果
if ham_score > spam_score:
return "正常邮件"
else:
return "垃圾邮件"
# 读取每封邮件进行分类
def classify_emails(ham_prob, spam_prob, ham_prior_prob, spam_prior_prob):
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
result = classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob)
print(f"文件 {file_path} 分类结果: {result}")
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
result = classify(email_content, ham_prob, spam_prob, ham_prior_prob, spam_prior_prob)
print(f"文件 {file_path} 分类结果: {result}")
# 特征选择和优化方法
def train_with_feature_selection():
# 使用TF-IDF进行特征选择
vectorizer = TfidfVectorizer()
X = []
y = []
# 读取正常邮件
for i in range(1, 26):
file_path = os.path.join(ham_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
X.append(email_content)
y.append(0) # 0 表示正常邮件
# 读取垃圾邮件
for i in range(1, 26):
file_path = os.path.join(spam_folder, str(i) + '.txt')
with codecs.open(file_path, 'r', encoding='gbk') as f:
email_content = f.read()
X.append(email_content)
y.append(1) # 1 表示垃圾邮件
X = vectorizer.fit_transform(X)
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练朴素贝叶斯分类器
clf = MultinomialNB()
clf.fit(X_train, y_train)
# 预测
y_pred = clf.predict(X_test)
# 输出分类结果和评估指标
print("分类精度:", metrics.accuracy_score(y_test, y_pred))
print("精确率:", metrics.precision_score(y_test, y_pred))
print("召回率:", metrics.recall_score(y_test, y_pred))
print("F1 分数:", metrics.f1_score(y_test, y_pred))
# 主函数
def main():
train_with_feature_selection()
if __name__ == '__main__':
main()
三、小结
可以看出,朴素贝叶斯分类器在垃圾邮件过滤方面表现出色,可以有效地将垃圾邮件与正常邮件区分开。尽管取得了较好的实验结果,但需要关注模型是否过拟合。考虑采取措施提高模型的泛化能力。朴素贝叶斯分类算法是一种简单而有效的机器学习算法,在许多实际应用中都表现良好。其优点:
- 简单有效:朴素贝叶斯算法易于实现和理解,适用于处理大型数据集。
- 高效性:由于特征条件独立假设,朴素贝叶斯分类器可以在相对短的时间内进行训练和预测。
- 可扩展性:适用于大规模的多类别分类问题,对于文本分类等任务尤其有效。
- 对小规模数据表现良好:即使在小规模数据集上,朴素贝叶斯分类器通常也能取得不错的表现。
总体而言,朴素贝叶斯分类器在垃圾邮件分类任务上展现出了良好的性能,但实验结果的稳健性和泛化能力仍是需要进一步研究和改进的方向。