【附Python源码】基于朴素贝叶斯的垃圾邮件识别实现

2 阅读4分钟

【附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 封垃圾邮件被错分为正常邮件)。

算法特性分析

朴素贝叶斯在文本分类任务中表现优异,主要原因包括:

  1. 维度灾难缓解:独立性假设使得参数估计仅需统计一维边缘分布,无需处理高维联合分布
  2. 计算效率高:训练和预测的时间复杂度均与文档长度线性相关
  3. 数据稀疏鲁棒性:拉普拉斯平滑机制有效处理未登录词

当然,独立性假设在实际场景中往往不成立(例如 "cheap" 和 "viagra" 在垃圾邮件中常共现),但这并未显著影响分类性能。研究表明,只要各类别的特征相关性分布相对均匀,朴素贝叶斯仍能保持较好的分类边界。

源码地址:github.com/anjuxi/Naiv…