首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >机器学习算法之朴素贝叶斯:Naive Bayes 手动计算公式 + 垃圾邮件过滤案例 + Python/Java完整代码

机器学习算法之朴素贝叶斯:Naive Bayes 手动计算公式 + 垃圾邮件过滤案例 + Python/Java完整代码

原创
作者头像
jack.yang
修改2026-03-29 13:22:34
修改2026-03-29 13:22:34
120
举报
文章被收录于专栏:大模型系列大模型系列

一句话答案:朴素贝叶斯是一种“假设特征互相独立”的概率分类算法,虽“天真”却极高效。10行代码就能实现垃圾邮件识别,准确率超85%!

如果你在搜索:

  • “朴素贝叶斯怎么算的?”
  • “Naive Bayes 手动计算例子”
  • “Python 和 Java 怎么实现朴素贝叶斯?”
  • “为什么叫‘朴素’?它真的准吗?”

那么,这篇文章就是为你写的——从数学推导到代码落地,一步不跳


一、什么是朴素贝叶斯?为什么“朴素”反而好用?

朴素贝叶斯(Naive Bayes)是基于贝叶斯定理的分类算法,核心思想是:

“已知结果,反推最可能的原因”

比如:

  • 收到一封邮件,内容有“免费”“赢大奖” → 判断是否为垃圾邮件
  • 用户评论含“烂透了”“差评” → 判断情感是负面

🤔 为什么叫“朴素”(Naive)?

因为它做了一个强假设

所有特征彼此独立(例如:“免费”和“赢大奖”出现互不影响)

现实中这显然不成立(垃圾邮件常同时出现这两个词),但神奇的是——即使假设错误,分类效果依然很好!

💡 原因:我们只关心“哪个类别概率最大”,不要求概率值绝对准确。只要排序对,结果就对。


二、数学原理:贝叶斯定理 + 独立性假设

我们要计算:

根据贝叶斯定理:

由于 P(X)对所有类别相同,可忽略。目标变为:

再利用“特征独立”假设:

最终决策公式:


三、手工推演:一步步计算垃圾邮件分类(带完整数据)

📊 训练数据集(4封邮件)

邮件ID

内容(分词后)

类别

1

["免费", "赢", "大奖"]

垃圾邮件

2

["免费", "课程"]

垃圾邮件

3

["会议", "安排", "明天"]

正常邮件

4

["项目", "进展", "顺利"]

正常邮件

假设使用伯努利模型(只关心词是否出现,不计频次)

🔢 步骤1:计算先验概率 (P(C))

  • 总邮件数 = 4
  • 垃圾邮件数 = 2 → P(垃圾) = 2/4 = 0.5
  • 正常邮件数 = 2 → P(正常) = 2/4 = 0.5

🔢 步骤2:构建词汇表(Vocabulary)

所有唯一词:["免费", "赢", "大奖", "课程", "会议", "安排", "明天", "项目", "进展", "顺利"] → 共10个词

🔢 步骤3:计算条件概率 (P词|类别)

使用拉普拉斯平滑(α=1),避免零概率:

分母+2:因为伯努利模型只有“出现/未出现”两种状态

垃圾邮件类(2封):
  • “免费”出现2次 → (P(免费|垃圾) = (2+1)/(2+2) = 3/4 = 0.75)
  • “赢”出现1次 → (P(赢|垃圾) = (1+1)/4 = 0.5)
  • “课程”出现1次 → (P(课程|垃圾) = 2/4 = 0.5)
  • “会议”未出现 → (P(会议|垃圾) = (0+1)/4 = 0.25)
正常邮件类(2封):
  • “会议”出现1次 → (P(会议|正常) = 2/4 = 0.5)
  • “免费”未出现 → (P(免费|正常) = 1/4 = 0.25)

其他词同理,略。

🔢 步骤4:预测新邮件 ["免费", "会议"]

计算两类后验概率(忽略公共分母):

垃圾邮件:

P(垃圾)⋅P(免费∣垃圾)⋅P(会议∣垃圾)=0.5×0.75×0.25=0.09375

正常邮件:

P(正常)⋅P(免费∣正常)⋅P(会议∣正常)=0.5×0.25×0.5=0.0625

结论:0.09375 > 0.0625 → 判定为 垃圾邮件

尽管“会议”通常是正常词,但“免费”的权重更高,模型做出了合理判断。


四、Python 实现(scikit-learn + 手写版)

✅ 方式1:使用 scikit-learn(推荐生产环境)

代码语言:javascript
复制
from sklearn.naive_bayes import BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer

# 数据
texts = [
    "免费 赢 大奖",
    "免费 课程",
    "会议 安排 明天",
    "项目 进展 顺利"
]
labels = [1, 1, 0, 0]  # 1=垃圾, 0=正常

# 向量化(二值化)
vectorizer = CountVectorizer(binary=True)
X = vectorizer.fit_transform(texts)

# 训练
clf = BernoulliNB(alpha=1.0)  # alpha=1 即拉普拉斯平滑
clf.fit(X, labels)

# 预测
new_text = vectorizer.transform(["免费 会议"])
print("预测结果:", "垃圾邮件" if clf.predict(new_text)[0] == 1 else "正常邮件")
# 输出: 垃圾邮件

✅ 方式2:手写核心逻辑

代码语言:javascript
复制
import numpy as np

class NaiveBayes:
    def __init__(self):
        self.vocab = {}
        self.prior = {}
        self.cond_prob = {}
    
    def fit(self, docs, labels, alpha=1):
        # 构建词汇表
        words = set(w for doc in docs for w in doc)
        self.vocab = {w: i for i, w in enumerate(words)}
        n_vocab = len(self.vocab)
        
        # 统计类别
        unique_labels = list(set(labels))
        label_count = {l: labels.count(l) for l in unique_labels}
        total = len(labels)
        
        # 先验概率
        self.prior = {l: count / total for l, count in label_count.items()}
        
        # 条件概率(伯努利)
        self.cond_prob = {l: np.zeros(n_vocab) for l in unique_labels}
        for doc, label in zip(docs, labels):
            doc_vec = np.zeros(n_vocab)
            for w in doc:
                if w in self.vocab:
                    doc_vec[self.vocab[w]] = 1
            self.cond_prob[label] += doc_vec
        
        # 拉普拉斯平滑
        for label in unique_labels:
            n_docs = label_count[label]
            self.cond_prob[label] = (self.cond_prob[label] + alpha) / (n_docs + 2 * alpha)
    
    def predict(self, doc):
        scores = {}
        for label in self.prior:
            log_prob = np.log(self.prior[label])
            for w in doc:
                if w in self.vocab:
                    idx = self.vocab[w]
                    log_prob += np.log(self.cond_prob[label][idx])
            scores[label] = log_prob
        return max(scores, key=scores.get)

# 使用
docs = [["免费","赢","大奖"], ["免费","课程"], ["会议","安排","明天"], ["项目","进展","顺利"]]
nb = NaiveBayes()
nb.fit(docs, [1,1,0,0])
print(nb.predict(["免费", "会议"]))  # 输出: 1

五、Java 实现(纯手写,无第三方库)

代码语言:javascript
复制
import java.util.*;

public class NaiveBayes {
    private Map<String, Integer> vocab = new HashMap<>();
    private Map<Integer, Double> prior = new HashMap<>();
    private Map<Integer, double[]> condProb = new HashMap<>();
    private int vocabSize;

    public void fit(List<List<String>> docs, List<Integer> labels, double alpha) {
        // 构建词汇表
        Set<String> wordSet = new HashSet<>();
        for (List<String> doc : docs) {
            wordSet.addAll(doc);
        }
        int idx = 0;
        for (String word : wordSet) {
            vocab.put(word, idx++);
        }
        vocabSize = vocab.size();

        // 统计标签
        Map<Integer, Integer> labelCount = new HashMap<>();
        for (int label : labels) {
            labelCount.put(label, labelCount.getOrDefault(label, 0) + 1);
        }

        // 先验概率
        int totalDocs = labels.size();
        for (int label : labelCount.keySet()) {
            prior.put(label, (double) labelCount.get(label) / totalDocs);
        }

        // 条件概率(伯努利)
        for (int label : labelCount.keySet()) {
            double[] prob = new double[vocabSize];
            int docsWithLabel = labelCount.get(label);
            
            // 统计每个词在该类中出现次数
            for (int i = 0; i < docs.size(); i++) {
                if (labels.get(i) == label) {
                    Set<String> docWords = new HashSet<>(docs.get(i));
                    for (String word : docWords) {
                        if (vocab.containsKey(word)) {
                            int wIdx = vocab.get(word);
                            prob[wIdx] += 1.0;
                        }
                    }
                }
            }
            
            // 拉普拉斯平滑
            for (int i = 0; i < vocabSize; i++) {
                prob[i] = (prob[i] + alpha) / (docsWithLabel + 2 * alpha);
            }
            condProb.put(label, prob);
        }
    }

    public int predict(List<String> doc) {
        Map<Integer, Double> scores = new HashMap<>();
        for (int label : prior.keySet()) {
            double logProb = Math.log(prior.get(label));
            Set<String> docWords = new HashSet<>(doc);
            for (String word : docWords) {
                if (vocab.containsKey(word)) {
                    int wIdx = vocab.get(word);
                    logProb += Math.log(condProb.get(label)[wIdx]);
                }
            }
            scores.put(label, logProb);
        }

        // 返回概率最大的标签
        return Collections.max(scores.entrySet(), Map.Entry.comparingByValue()).getKey();
    }

    // 测试
    public static void main(String[] args) {
        List<List<String>> docs = Arrays.asList(
            Arrays.asList("免费", "赢", "大奖"),
            Arrays.asList("免费", "课程"),
            Arrays.asList("会议", "安排", "明天"),
            Arrays.asList("项目", "进展", "顺利")
        );
        List<Integer> labels = Arrays.asList(1, 1, 0, 0);

        NaiveBayes nb = new NaiveBayes();
        nb.fit(docs, labels, 1.0); // alpha=1

        List<String> testDoc = Arrays.asList("免费", "会议");
        System.out.println("预测结果: " + (nb.predict(testDoc) == 1 ? "垃圾邮件" : "正常邮件"));
        // 输出: 垃圾邮件
    }
}

六、优缺点 & 适用场景总结

优点

缺点

✅ 训练快,预测快(O(n))

❌ 特征独立假设太强

✅ 小样本表现好

❌ 无法建模特征交互(如“not good”)

✅ 对缺失值鲁棒

❌ 概率输出可能不准

✅ 内存占用小,适合嵌入式

❌ 连续特征需假设分布(如高斯)

🎯 最佳应用场景:

  • 文本分类(新闻、邮件、评论)
  • 垃圾信息检测
  • 情感分析(正面/负面)
  • 实时分类系统(如API过滤)

七、后续算法预告(均含手工推演 + 双语言代码)

本系列将持续更新以下算法,每篇均包含:

  • 真实数据手工一步步计算
  • Python + Java 完整可运行代码

即将发布:

  1. K近邻(KNN):从距离计算到手写数字识别
  2. 决策树(ID3/C4.5):信息增益如何分裂节点?
  3. 支持向量机(SVM):硬间隔、软间隔、核函数全解析
  4. 逻辑回归:从sigmoid到梯度下降

✅ 结语

朴素贝叶斯用最天真的假设,实现了最实用的分类。它不追求完美,只求快速、稳健、可解释。

记住:在AI世界,有时“简单有效”比“复杂精确”更重要。

现在,你已经能:

  • 手动计算朴素贝叶斯分类结果
  • 用Python或Java从零实现它
  • 理解为何它仍是工业界首选之一

 相关链接

  • 📂 大模型技术专栏: 欢迎您到访 「大模型系列」。 在这个由参数驱动、以数据为燃料的新智能时代,大语言模型(LLM)已不再是实验室里的前沿概念,而是正在重塑搜索、办公、编程、教育、医疗乃至整个数字世界的底层引擎。从 GPT 到 Llama,从 Claude 到 Qwen,从推理到多模态,大模型正以前所未有的速度进化——它们既是工具,也是平台,更可能是下一代人机交互的“操作系统”。 本系列将带你:
    • 🔍 深入原理:从 Transformer 架构、注意力机制到训练范式(预训练、微调、RLHF);
    • ⚙️ 动手实践:本地部署、模型微调、RAG 构建、Agent 设计等实战指南;
    • 🧠 理解边界:幻觉、偏见、安全对齐、推理瓶颈与当前能力天花板;
    • 🌍 洞察趋势:开源 vs 闭源、端侧部署、MoE 架构、世界模型与 AGI 路径;
    • 💼 落地应用:如何在企业中安全、高效、低成本地集成大模型能力。

    无论你是想写代码调用 API 的开发者,设计 AI 产品的 PM,评估技术路线的管理者,还是单纯好奇智能本质的思考者,这里都有值得你驻足的内容。 不追 hype,只讲逻辑;不谈玄学,专注可复现的认知。 让我们一起,在这场百年一遇的智能革命中,看得更清,走得更稳 https://cloud.tencent.com/developer/column/107314

  • 👤 关于作者专注技术落地,深耕硬核干货 本文作者致力于大模型相关技术的生态建设与实战落地。不同于浅层的概念科普,作者坚持 “手算 + 代码” 的深度分享模式,主张通过手动推演理解算法本质,结合生产级代码验证理论可行性。 请关注我主页:https://cloud.tencent.com/developer/user/2276240

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是朴素贝叶斯?为什么“朴素”反而好用?
    • 🤔 为什么叫“朴素”(Naive)?
  • 二、数学原理:贝叶斯定理 + 独立性假设
  • 三、手工推演:一步步计算垃圾邮件分类(带完整数据)
    • 📊 训练数据集(4封邮件)
    • 🔢 步骤1:计算先验概率 (P(C))
    • 🔢 步骤2:构建词汇表(Vocabulary)
    • 🔢 步骤3:计算条件概率 (P词|类别)
      • 垃圾邮件类(2封):
      • 正常邮件类(2封):
    • 🔢 步骤4:预测新邮件 ["免费", "会议"]
      • 垃圾邮件:
      • 正常邮件:
  • 四、Python 实现(scikit-learn + 手写版)
    • ✅ 方式1:使用 scikit-learn(推荐生产环境)
    • ✅ 方式2:手写核心逻辑
  • 五、Java 实现(纯手写,无第三方库)
  • 六、优缺点 & 适用场景总结
    • 🎯 最佳应用场景:
  • 七、后续算法预告(均含手工推演 + 双语言代码)
  • ✅ 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档