从零开始的学AI生活:0-基本概念

Posted by on October 07, 2026 · 11 mins read

第 0 章:贝叶斯分类器回顾


0. 为什么从贝叶斯分类器开始

我对AI的初始印象就是在大学课程里学的贝叶斯分类器,它解决的问题是:

给定观测到的特征 \(x\),类别 \(y\) 的概率是多少?

现代 LLM 回答的问题在形式上非常相似:

给定前文 \(x_{< t}\),下一个 token \(x_t\) 的概率是多少?

1. 条件概率

\[P(A|B)\]

含义:

  • \(A\):关心的事件
  • \(B\):已知发生的事件
  • \(P(A \mid B)\):在 \(B\) 已经发生的条件下,\(A\) 发生的概率

例子:

\[P(\text{spam}|\text{包含“免费”})\]

表示:已知一封邮件包含“免费”,它是垃圾邮件的概率。

2. 联合概率

\[P(A,B)\]

含义:

  • \(A\) 和 \(B\) 同时发生的概率

关系:

\[P(A,B)=P(A|B)P(B)\]

也等于:

\[P(A,B)=P(B|A)P(A)\]

这两个式子只是先后顺序不同,描述的是同一件事实。

3. 贝叶斯定理

因为: \(P(x,y) = P(x|y)P(y) = P(y|x)P(x)\)

所以有: \(P(y|x)=\frac{P(x|y)P(y)}{P(x)}\)

各项含义:

符号 名称 含义
\(P(y)\) 先验概率 在看到 \(x\) 之前,对类别 \(y\) 的信念
\(P(x \mid y)\) 似然 假设类别是 \(y\) 时,看到特征 \(x\) 的可能性
\(P(x)\) 证据 \(x\) 本身出现的总概率,起到归一化作用
\(P(y \mid x)\) 后验概率 看到 \(x\) 之后,更新得到的类别概率

直觉解释

贝叶斯定理做的事情是:

  1. 你原本有一个先验信念\(P(y)\)
  2. 你观察到证据\(x\)
  3. 你用似然 \(P(x \mid y)\) 修正这个信念
  4. 得到后验 \(P(y \mid x)\)

这里的信念,可以简单理解为历史频率,贝叶斯定律简单理解就是:根据历史上\(y\)发生时,\(x\)出现的频率,来计算当前\(x\)发生时,\(y\)发生的概率。

举例说明:


先看一个具体例子

假设你在判断一封邮件是不是垃圾邮件。

在还没看内容之前,你知道:

  • 历史上 30% 的邮件是垃圾邮件

那么你的“先验信念”就是:

P(spam) = 0.3

意思是:

在没有任何额外信息时,你认为这封邮件是垃圾邮件的置信度是 30%。


看到证据后,信念会更新

现在你打开邮件,看到:

Free money click here

这些词在垃圾邮件里很常见,在正常邮件里很少见。

于是你会想:

“看到这些词的情况下,这封邮件是垃圾邮件的可能性好像变大了。”

这个“变大”不是拍脑袋,而是由似然来量化的:

P(这些词|spam)

和:

P(这些词|ham)

前者远大于后者,即在垃圾邮件中出现这些词的概率远大于正常邮件中出现这些词的概率。


后验就是更新后的信念

贝叶斯定理做的事情是:

先验信念 × 证据强度 → 后验信念

比如更新后变成:

P(spam|这些词) = 0.92

这就表示:

看到邮件内容之后,你现在有 92% 的把握认为它是垃圾邮件。

注意:

  • 它不是说你“确定”它是垃圾邮件
  • 它说的是你的置信度
  • 这个置信度会随着证据继续变化

4. 最大后验决策

分类器通常选择后验概率最大的类别:

\[\hat{y}=\arg\max_y P(y|x)\]

含义:

  • \(\hat{y}\):预测的类别
  • \(\arg\max_y\):使后面这个值最大的 \(y\)

因为 \(P(x)\) 对所有类别相同,所以可以简化为:

\[\hat{y}=\arg\max_y P(x|y)P(y)\]

这叫 MAP,最大后验概率决策。

5. 朴素贝叶斯假设

如果 \(x\) 是很多特征的集合:

\[x=(x_1,x_2,\dots,x_n)\]

直接建模 \(P(x \mid y)\) 很难。朴素贝叶斯假设:

\[P(x_1,x_2,\dots,x_n|y)=\prod_{i=1}^n P(x_i|y)\]

也就是说:

在给定类别 \(y\) 的条件下,各特征相互独立。

这个假设很强,现实中往往不成立,但它让计算变得非常简单。

6. 文本例子

假设有两个类别:

  • spam(垃圾邮件)
  • ham(正常邮件)

邮件内容:

Free money click here

朴素贝叶斯会计算:

\[P(\text{spam}|\text{Free},\text{money},\text{click},\text{here}) \propto P(\text{spam})\prod_i P(w_i|\text{spam})\]

以及:

\[P(\text{ham}|\text{Free},\text{money},\text{click},\text{here}) \propto P(\text{ham})\prod_i P(w_i|\text{ham})\]

然后比较两边大小。

7. 为什么用 log

很多小概率相乘会导致数值下溢:

\[0.001^{100}=1e-300\]

因此实际实现通常取对数:

\[\log P(y)+\sum_i \log P(x_i|y)\]

好处:

  1. 避免数值下溢
  2. 把乘法变成加法
  3. 计算更稳定

8. Laplace 平滑

如果某个词在训练集中从未出现过,则:

\[P(w|y)=0\]

整个乘积会直接变成 0。Laplace 平滑的做法是:

\(P(w|y)=\frac{\text{count}(w,y)+1}{\text{count}(y)+|V|}\) 其中:

  • \(\text{count}(w,y)\):词 \(w\) 在类别 \(y\) 中出现的次数
  • \(\text{count}(y)\):类别 \(y\) 的总词数
  • \(\mid V \mid\):词表大小

直观上就是“给每个词至少留一点概率”。

9. 生成式和判别式

朴素贝叶斯是生成式模型:

  • 显式建模 \(P(x \mid y)\) 和 \(P(y)\)
  • 再用贝叶斯定理得到 \(P(y \mid x)\)

逻辑回归和神经网络是判别式模型:

  • 直接建模 \(P(y \mid x)\)
  • 不显式建模 \(P(x \mid y)\)

现代 LLM 更接近判别式条件模型:

\[P(x_t|x_{<t})\]

但它通常由一个生成式训练目标学得。

10. 代码

以下代码实现了一个玩具版朴素贝叶斯垃圾邮件分类器,包括:

  1. 分词
  2. 词频统计
  3. 先验概率
  4. Laplace 平滑
  5. log 空间预测
#!/usr/bin/env python
from __future__ import annotations

import argparse
import math
import re
from collections import Counter
from dataclasses import dataclass


TOKEN_PATTERN = re.compile(r"\w+")


@dataclass(frozen=True)
class Sample:
    text: str
    label: str


class NaiveBayesClassifier:
    def __init__(self, smoothing: float = 1.0) -> None:
        self.smoothing = smoothing
        self.labels: list[str] = []
        self.label_counts: Counter[str] = Counter()
        self.word_counts: dict[str, Counter[str]] = {}
        self.vocabulary: set[str] = set()
        self.total_words_per_label: dict[str, int] = {}

    @staticmethod
    def tokenize(text: str) -> list[str]:
        return TOKEN_PATTERN.findall(text.lower())

    def fit(self, samples: list[Sample]) -> None:
        for sample in samples:
            self.label_counts[sample.label] += 1
            self.word_counts.setdefault(sample.label, Counter())
            tokens = self.tokenize(sample.text)
            self.word_counts[sample.label].update(tokens)
            self.vocabulary.update(tokens)

        self.labels = sorted(self.label_counts)
        self.total_words_per_label = {
            label: sum(self.word_counts[label].values()) for label in self.labels
        }

    def log_prior(self, label: str) -> float:
        total = sum(self.label_counts.values())
        return math.log(self.label_counts[label] / total)

    def log_likelihood(self, token: str, label: str) -> float:
        count = self.word_counts[label][token]
        total = self.total_words_per_label[label]
        smoothed = (count + self.smoothing) / (total + self.smoothing * len(self.vocabulary))
        return math.log(smoothed)

    def predict_log_scores(self, text: str) -> dict[str, float]:
        tokens = self.tokenize(text)
        scores: dict[str, float] = {}
        for label in self.labels:
            score = self.log_prior(label)
            for token in tokens:
                score += self.log_likelihood(token, label)
            scores[label] = score
        return scores

    def predict(self, text: str) -> str:
        scores = self.predict_log_scores(text)
        return max(scores.items(), key=lambda item: item[1])[0]


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--smoothing", type=float, default=1.0)
    args = parser.parse_args()

    training_data = [
        Sample("Free money click here", "spam"),
        Sample("Win a free prize now", "spam"),
        Sample("Cheap pills online", "spam"),
        Sample("Meeting tomorrow at ten", "ham"),
        Sample("Project review is attached", "ham"),
        Sample("Lunch after the standup", "ham"),
    ]
    test_data = [
        "Free prize click now",
        "Review the project tomorrow",
        "Cheap online offer",
        "Lunch meeting after standup",
        "totally unseen words",
    ]

    classifier = NaiveBayesClassifier(smoothing=args.smoothing)
    classifier.fit(training_data)

    print(f"labels: {classifier.labels}")
    print(f"vocabulary size: {len(classifier.vocabulary)}")
    print(f"log priors: { {label: classifier.log_prior(label) for label in classifier.labels} }")
    print("\npredictions:")
    for text in test_data:
        scores = classifier.predict_log_scores(text)
        prediction = classifier.predict(text)
        formatted_scores = ", ".join(f"{label}={score:.3f}" for label, score in scores.items())
        print(f"  {text!r} -> {prediction} ({formatted_scores})")


if __name__ == "__main__":
    main()

11. 符号与名词详解

符号 / 名词 含义
\(P(A)\) 事件 \(A\) 发生的概率,取值范围 \([0,1]\)
\(P(A \mid B)\) 条件概率,已知 \(B\) 发生时 \(A\) 发生的概率
\(P(A,B)\) 联合概率,\(A\) 和 \(B\) 同时发生的概率
\(P(x \mid y)\) 似然,类别为 \(y\) 时观察到 \(x\) 的概率
\(P(y \mid x)\) 后验概率,观察到 \(x\) 后类别为 \(y\) 的概率
\(P(y)\) 先验概率,未观察 \(x\) 前对 \(y\) 的信念
\(P(x)\) 证据,\(x\) 出现的总概率,用于归一化
\(\arg\max\) 使函数取最大值的输入
\(\prod\) 连乘符号
\(\sum\) 求和符号
\(\log\) 对数函数,把乘法变成加法
MAP Maximum A Posteriori,最大后验概率决策
朴素假设 给定类别时特征条件独立
Laplace 平滑 给未出现事件分配小概率,避免零概率
生成式模型 显式建模 \(P(x,y)\) 或 \(P(x \mid y)\)
判别式模型 直接建模 \(P(y \mid x)\)