【附Python源码】基于朴素贝叶斯的垃圾邮件识别实现
文本分类是自然语言处理领域的基础任务之一,而垃圾邮件过滤则是其最典型的应用场景。
本项目实现了一个基于朴素贝叶斯算法的垃圾邮件分类器,不依赖现成的机器学习框架(除评估指标外),完整呈现算法的核心逻辑。
朴素贝叶斯算法原理
朴素贝叶斯分类器建立在贝叶斯定理基础之上,其核心假设是特征之间相互独立。对于邮件分类任务,该假设意味着每个单词的出现概率与其他单词无关。
贝叶斯定理表述如下:
P(类别|文档) = P(文档|类别) × P(类别) / P(文档)
实际计算中,由于分母 P(文档) 对所有类别相同,只需比较分子部分即可。根据独立性假设,文档概率可分解为各单词条件概率的乘积:
P(文档|类别) = ∏ P(单词|类别)
数据集概况
本项目采用的邮件数据集包含 5171 条记录,其中正常邮件 3672 条,垃圾邮件 1499 条。数据字段包括邮件文本内容、文本标签(ham/spam)以及数值化标签(0/1)。
数据集地址:www.kaggle.com/datasets/ve…
数据加载与划分代码如下:
import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv('spam_ham_dataset.csv')
X_train, X_test, y_train, y_test = train_test_split(
df['text'], df['label_num'], test_size=0.2, random_state=42
)
按照 8:2 的比例划分后,训练集包含 4136 条样本,测试集包含 1035 条样本。
分类器实现
类结构设计
分类器的核心在于维护以下几类统计信息:
from collections import defaultdict
class NaiveBayesSpamClassifier:
def __init__(self):
self.spam_word_counts = defaultdict(int) # 垃圾邮件词频
self.ham_word_counts = defaultdict(int) # 正常邮件词频
self.spam_total_words = 0 # 垃圾邮件总词数
self.ham_total_words = 0 # 正常邮件总词数
self.spam_docs = 0 # 垃圾邮件文档数
self.ham_docs = 0 # 正常邮件文档数
self.vocab = set() # 词汇表
self.p_spam = 0 # 垃圾邮件先验概率
self.p_ham = 0 # 正常邮件先验概率
文本预处理
邮件文本需要经过标准化处理才能用于统计。预处理流程包括:统一转换为小写、移除非字母字符、分词、过滤短词(长度小于等于 2 的单词通常信息价值较低)。
import re
def preprocess_text(self, text):
text = text.lower()
text = re.sub(r'[^a-z\s]', ' ', text)
words = text.split()
return [word for word in words if len(word) > 2]
模型训练
训练阶段的核心任务是统计词频并计算先验概率。对于每条训练样本,根据其标签将单词分别累加到对应的词频字典中,同时维护词汇表和各类别的文档计数。
def fit(self, X, y):
for text, label in zip(X, y):
words = self.preprocess_text(text)
if label == 1: # 垃圾邮件
self.spam_docs += 1
for word in words:
self.spam_word_counts[word] += 1
self.spam_total_words += 1
self.vocab.add(word)
else: # 正常邮件
self.ham_docs += 1
for word in words:
self.ham_word_counts[word] += 1
self.ham_total_words += 1
self.vocab.add(word)
total_docs = self.spam_docs + self.ham_docs
self.p_spam = self.spam_docs / total_docs
self.p_ham = self.ham_docs / total_docs
预测与平滑处理
预测阶段需要计算后验概率。为避免多个小概率相乘导致的数值下溢问题,实际实现中采用对数概率进行计算。
另一个关键问题是零概率处理:若测试集中出现训练集未收录的单词,直接计算会导致整个概率为零。解决方案是引入拉普拉斯平滑,即在分子加 1、分母加上词汇表大小:
import numpy as np
def predict_single(self, text):
words = self.preprocess_text(text)
vocab_size = len(self.vocab)
log_p_spam = np.log(self.p_spam)
log_p_ham = np.log(self.p_ham)
for word in words:
# 拉普拉斯平滑
spam_count = self.spam_word_counts.get(word, 0)
ham_count = self.ham_word_counts.get(word, 0)
p_word_given_spam = (spam_count + 1) / (self.spam_total_words + vocab_size)
p_word_given_ham = (ham_count + 1) / (self.ham_total_words + vocab_size)
log_p_spam += np.log(p_word_given_spam)
log_p_ham += np.log(p_word_given_ham)
return 1 if log_p_spam > log_p_ham else 0
实验结果
在测试集上的评估结果如下:
| 指标 | 数值 |
|---|---|
| 准确率 | 98.07% |
| 精确率 | 96.27% |
| 召回率 | 96.93% |
| F1 分数 | 96.60% |
混淆矩阵显示,在 1035 条测试样本中,仅出现 20 例误判(11 封正常邮件被错分为垃圾邮件,9 封垃圾邮件被错分为正常邮件)。
算法特性分析
朴素贝叶斯在文本分类任务中表现优异,主要原因包括:
- 维度灾难缓解:独立性假设使得参数估计仅需统计一维边缘分布,无需处理高维联合分布
- 计算效率高:训练和预测的时间复杂度均与文档长度线性相关
- 数据稀疏鲁棒性:拉普拉斯平滑机制有效处理未登录词
当然,独立性假设在实际场景中往往不成立(例如 "cheap" 和 "viagra" 在垃圾邮件中常共现),但这并未显著影响分类性能。研究表明,只要各类别的特征相关性分布相对均匀,朴素贝叶斯仍能保持较好的分类边界。