直接用scikit-learn的TfidfVectorizer+LogisticRegression可构建准确率95%+的垃圾邮件分类器;手动实现朴素贝叶斯易因稀疏词、HTML残留、乱码等导致平滑失效,而sklearn已高度优化;需自定义停用词、设置max_features=10000和ngram_range=(1,2),并用class_weight='balanced'应对数据不平衡。

直接用 scikit-learn 的 TfidfVectorizer + LogisticRegression 就能跑通一个可用的垃圾邮件分类器,准确率通常在 95%+;别从头写朴素贝叶斯,也别一上来就上 BERT。
为什么不用自己实现朴素贝叶斯?
因为 sklearn.naive_bayes.MultinomialNB 已经高度优化,且默认参数在邮件文本上表现稳定;但更关键的是:真实邮件数据里存在大量稀疏词、短链接、乱码符号(如 vi@gr@)、HTML 标签残留——这些会让手动实现的平滑逻辑出问题。
实操建议:
- 先用
re.sub(r']+>', '', text)去掉 HTML 标签,再用re.sub(r'http\S+|www\S+|https\S+', 'URL', text)统一替换链接 -
TfidfVectorizer的stop_words='english'不够用,要额外传入自定义停用词表(比如加入'subject','re','fw'等邮件常见无意义前缀) - 设置
max_features=10000和ngram_range=(1, 2),能显著提升对“free money”、“urgent offer”这类短语的捕捉能力
训练时遇到 “ValueError: X has 0 features” 怎么办?
这是 TfidfVectorizer 没提取出任何 token 的典型错误,根本原因通常是文本预处理后全为空串。常见触发场景:
立即学习“Python免费学习笔记(深入)”;
- 原始数据里有纯空格、换行符或
字符,.strip()后长度为 0 - 正则清洗过度,比如误删了所有字母(
re.sub(r'[^\w\s]', '', text)会干掉所有标点,但若配合lower()和后续分词,可能让英文单词全变空) - 训练集里某条样本是空字符串,
TfidfVectorizer默认跳过它,但如果整个 batch 都被过滤,就会报这个错
快速验证方法:print([len(t.strip()) for t in raw_texts[:5]]),确保不全是 0;修复后加一句 X = vectorizer.fit_transform([t for t in texts if t.strip()]) 更稳妥。
预测时 label 总是偏向 ham(正常邮件)?
这不是模型问题,是数据不平衡导致的——公开数据集如 spamassassin 或 enron-spam 里 spam 占比常低于 20%,而 LogisticRegression 默认以 accuracy 为目标,天然倾向多数类。
必须做的三件事:
- 用
class_weight='balanced'初始化分类器:LogisticRegression(class_weight='balanced') - 评估时别看 accuracy,改用
classification_report(y_true, y_pred),重点关注 spam 类的f1-score - 如果线上部署,把预测概率阈值从默认 0.5 调低到 0.3:
model.predict_proba(X)[:, 1] > 0.3,否则漏判 spam 太多
真正难的不是调模型,而是持续维护特征工程——新出现的垃圾邮件话术(比如“AI-generated invoice”)、新型编码混淆(Base64 片段混在正文)、或者企业邮箱里合法但高频的“URGENT”关键词,都会让旧规则失效。每次更新都要重新跑一遍向量化 pipeline,而不是只换模型权重。


















