Homura's Blog TECHNICAL JOURNAL

2026.10.17

从零开始的学AI生活:1-从贝叶斯分类器到张量、神经网络与自动微分

第 1 章:从贝叶斯分类器到张量、神经网络与自动微分


1. 在第0章提到的贝叶斯分类器

一个朴素贝叶斯分类器做的事情是:

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

逐项解释:

  • \(x\):输入特征,例如一封邮件里的词
  • \(y\):类别,例如 spam / ham
  • \(P(y{\mid}x)\):看到 \(x\) 后,类别是 \(y\) 的后验概率
  • \(\arg\max_y\):在所有可能类别里,选择让 \(P(y{\mid}x)\) 最大的那个类别
  • \(\hat{y}\):预测结果,帽子表示“估计值”

贝叶斯公式把它展开:

\[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.1 一个具体例子

假设有两个类别:

spam
ham

先验:

\[P(\text{spam})=0.3\] \[P(\text{ham})=0.7\]

现在看到词:

free

似然:

\[P(\text{free}|\text{spam})=0.8\] \[P(\text{free}|\text{ham})=0.1\]

那么:

\[P(\text{free}|\text{spam})P(\text{spam}) =0.8*0.3=0.24\] \[P(\text{free}|\text{ham})P(\text{ham}) =0.1*0.7=0.07\]

因为:

\[0.24>0.07\]

所以分类器更倾向判为 spam。


2. 生成式模型和判别式模型的区别

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

它显式建模:

\[P(x|y)\]

再配合 \(P(y)\) 推出 \(P(y{\mid}x)\)。

这叫“生成式”,因为它尝试描述数据是如何生成的。

2.2 神经网络通常先做判别式模型

神经网络更常见做法是直接学习:

\[P(y|x)\]

也就是:

输入一个 \(x\),直接输出每个类别的概率。

它不先显式估计 \(P(x{\mid}y)\)。

2.3 为什么这样更适合高维文本

在文本里,词之间高度相关:

New York
not good
machine learning

朴素贝叶斯的“条件独立”假设太强,真实语言不满足。

神经网络则可以通过多层非线性变换,自动学习特征之间的关系。


3. 张量和矩阵乘法

神经网络的基本运算是矩阵乘法。

3.1 一个输入向量

假设一个样本有两个特征:

\[x= \begin{bmatrix} 1\\ 2 \end{bmatrix}\]

形状是:

(2,)

3.2 一个权重矩阵

假设有 3 个输出特征:

\[W= \begin{bmatrix} 1 & 0\\ 0 & 1\\ 1 & 1 \end{bmatrix}\]

形状是:

(3, 2)

3.3 矩阵乘法

\[Wx= \begin{bmatrix} 1*1+0*2\\ 0*1+1*2\\ 1*1+1*2 \end{bmatrix} = \begin{bmatrix} 1\\ 2\\ 3 \end{bmatrix}\]

结果是:

shape = (3,)

3.4 加偏置

如果:

\[b= \begin{bmatrix} 0\\ 1\\ -1 \end{bmatrix}\]

那么:

\[Wx+b= \begin{bmatrix} 1\\ 3\\ 2 \end{bmatrix}\]

这就是一个线性层的输出。

3.5 形状规则

对于:

\[z=Wx+b\]

常见形状是:

x: (输入维度,)
W: (输出维度, 输入维度)
b: (输出维度,)
z: (输出维度,)

如果一次处理一批样本:

X: (批大小, 输入维度)
W: (输出维度, 输入维度)
b: (输出维度,)
Z: (批大小, 输出维度)

4. 线性分类器和 logit

一个线性分类器先算:

\[z=Wx+b\]

这里:

  • \(z\) 是 logit 向量(输出的原始分数向量)
  • 每个元素对应一个类别
  • logit 可以是任意实数,可正可负
  • logit 本身还不是概率

例如三分类:

\[z=[2.0,\ 1.0,\ 0.1]\]

它看起来像:

类别 0:2.0
类别 1:1.0
类别 2:0.1

但我们不能直接说“类别 0 的概率是 2.0”。

概率必须满足:

  1. 每个值都在 0 到 1 之间
  2. 所有值加起来等于 1

所以需要 softmax。


5. Softmax:把分数变成概率

5.1 公式

\[P(y=i|x)=\frac{\exp(z_i)}{\sum_j \exp(z_j)}\]

逐项解释:

  • \(z_i\):第 \(i\) 个类别的 logit
  • \(\exp(z_i)\):把实数变成正数
  • 分母 \(\sum_j \exp(z_j)\):把所有正数加起来,用于归一化
  • \(P(y=i{\mid}x)\):第 \(i\) 类的概率

5.2 具体计算

假设:

\[z=[2.0,\ 1.0,\ 0.1]\]

先算指数:

\[\exp(2.0)=7.389\] \[\exp(1.0)=2.718\] \[\exp(0.1)=1.105\]

求和:

\[7.389+2.718+1.105=11.212\]

归一化:

\[P(y=0|x)=\frac{7.389}{11.212}=0.659\] \[P(y=1|x)=\frac{2.718}{11.212}=0.242\] \[P(y=2|x)=\frac{1.105}{11.212}=0.099\]

最终:

\[P(y|x)=[0.659,\ 0.242,\ 0.099]\]

满足:

\[0.659+0.242+0.099=1\]

5.3 Softmax 的性质

  1. 输出永远大于 0
  2. 所有输出加起来等于 1
  3. logit 越大,概率越大
  4. 它是“软”的 argmax
  5. 它可导,适合反向传播

5.4 温度

后续生成文本时会看到:

\[\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}\]

其中 \(T\) 是温度:

  • \(T<1\):分布更尖
  • \(T>1\):分布更平
  • \(T=1\):原始 softmax

6. 为什么线性模型不够

一个线性分类器只能用一条直线、一个平面或超平面分开类别。

如果数据是线性可分的:

○ ○ ○
--------
× × ×

线性模型够用。

但很多真实数据是这样的:

○ ○ ○
× × ×
○ ○ ○

或者:

内圈是 ×,外圈是 ○

这时单条直线分不开。

神经网络的作用是:

  1. 先把输入变换到另一个空间
  2. 在新空间里让它变得线性可分
  3. 再做分类

这就是“表示学习”。


7. 激活函数:引入非线性

如果没有非线性,多个线性层叠加仍然等价于一个线性层。

假设:

\[h=W_1x+b_1\] \[y=W_2h+b_2\]

代入后:

\[y=W_2(W_1x+b_1)+b_2\]

展开:

\[y=(W_2W_1)x+(W_2b_1+b_2)\]

令:

\[W'=W_2W_1\] \[b'=W_2b_1+b_2\]

则:

\[y=W'x+b'\]

这仍然是线性变换。

所以必须在层间加入非线性函数。


8. ReLU

8.1 公式

\[\text{ReLU}(x)=\max(0,x)\]

含义:

  • 如果 \(x<0\),输出 0
  • 如果 \(x\ge 0\),输出 \(x\)

8.2 例子

输入:

[-2.0, -0.5, 0.0, 1.5]

输出:

[0.0, 0.0, 0.0, 1.5]

8.3 特点

优点:

  • 计算极快
  • 能缓解梯度消失
  • 让网络具备稀疏激活

缺点:

  • 输入小于 0 时梯度为 0
  • 可能出现“神经元死亡”

8.4 几何直觉

ReLU 相当于把负半轴“折平”。

它不是一条直线,所以能折出非线性边界。


9. GELU

9.1 直觉公式

一个常见近似是:

\[\text{GELU}(x)\approx x\cdot \Phi(x)\]

其中:

\[\Phi(x)\]

是标准正态分布的累积分布函数。

直观理解:

  • \(x\) 很负时,输出接近 0
  • \(x\) 很正时,输出接近 \(x\)
  • 在 0 附近过渡更平滑

9.2 和 ReLU 的区别

ReLU 是硬截断:

小于 0 直接变 0

GELU 是软平滑:

小于 0 也会有一点点输出

9.3 例子

大致效果:

ReLU:
[-2.0, -0.5, 0.0, 1.5]
 ->
[0.0, 0.0, 0.0, 1.5]

GELU:
[-2.0, -0.5, 0.0, 1.5]
 ->
约 [-0.046, -0.346, 0.0, 1.399]

Transformer 中大量使用 GELU 或 SwiGLU 系激活函数。


10. tanh

10.1 公式

\[\tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\]

输出范围:

\[(-1,1)\]

10.2 例子

输入:

[-2.0, 0.0, 1.0]

输出约为:

[-0.964, 0.0, 0.762]

10.3 特点

优点:

  • 输出以 0 为中心
  • 比 sigmoid 更适合循环网络

缺点:

  • 两端饱和,容易梯度消失

现代 Transformer 里更常用 GELU,而不是 tanh。


11. Sigmoid

11.1 公式

\[\sigma(x)=\frac{1}{1+e^{-x}}\]

输出范围:

\[(0,1)\]

11.2 例子

输入:

[-1, 0, 1]

输出约为:

[0.269, 0.500, 0.731]

11.3 用途

  • 二分类输出
  • 门控结构
  • RLHF 偏好概率

12. 多层感知机 MLP

MLP 是最基础的神经网络结构,也叫做前馈神经网络。

它由多层线性变换和激活函数组成:

\[z_1=W_1x+b_1\] \[a_1=\text{ReLU}(z_1)\] \[z_2=W_2a_1+b_2\] \[q=\text{softmax}(z_2)\]

含义:

  • \(x\):输入
  • \(W_1,b_1\):第一层权重和偏置
  • \(z_1\):第一层线性输出
  • \(a_1\):第一层激活后的隐藏表示
  • \(W_2,b_2\):第二层权重和偏置
  • \(z_2\):logits
  • \(q\):类别概率分布

12.1 什么是隐藏层

第一层输出叫隐藏层:

  • 它不是原始输入
  • 也不是最终标签
  • 它是模型自己学出来的中间特征

例如原始输入是像素,隐藏层可能学到:

  • 边缘
  • 角落
  • 纹理
  • 局部形状

对文本来说,隐藏层可能学到:

  • 短语级特征
  • 语义相似性
  • 上下文关联

12.2 一个具体形状例子

假设:

输入维度:2
隐藏层维度:4
输出类别数:3

那么:

x:  (2,)
W1: (4, 2)
b1: (4,)
z1: (4,)
a1: (4,)
W2: (3, 4)
b2: (3,)
z2: (3,)
q:  (3,)

形状变化是:

2 -> 4 -> 3

如果一次输入 32 个样本:

X:  (32, 2)
Z1: (32, 4)
A1: (32, 4)
Z2: (32, 3)
Q:  (32, 3)

12.3 具体前向计算

令:

\[x= \begin{bmatrix} 1\\ 2 \end{bmatrix}\] \[W_1= \begin{bmatrix} 1 & 0\\ 0 & 1\\ 1 & 1\\ -1 & 0 \end{bmatrix} ,\quad b_1= \begin{bmatrix} 0\\ 0\\ 0\\ 1 \end{bmatrix}\]

先算:

\[z_1=W_1x+b_1\]

逐项计算:

\[z_{1,1}=1*1+0*2+0=1\] \[z_{1,2}=0*1+1*2+0=2\] \[z_{1,3}=1*1+1*2+0=3\] \[z_{1,4}=-1*1+0*2+1=0\]

所以:

\[z_1=[1,2,3,0]\]

经过 ReLU:

\[a_1=\text{ReLU}(z_1)=[1,2,3,0]\]

再假设:

\[W_2= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0 \end{bmatrix} ,\quad b_2=[0,0,0.1]\]

则:

\[z_2=W_2a_1+b_2=[1,2,3.1]\]

最后 softmax 得到三个类别的概率。


13. 为什么多层网络有用

单层线性模型只能学线性边界。

MLP 可以看成:

  1. 第一层把原始空间扭曲、折叠
  2. 第二层在扭曲后的空间里做线性分类

换句话说:

MLP 不是画出更复杂的曲线,而是把数据变换到一个更容易分开的空间。

这就是表示学习。


14. 交叉熵损失

softmax 输出的是概率分布,例如:

\[q=[0.2,0.7,0.1]\]

真实标签是类别 1,用 one-hot 表示:

\[p=[0,1,0]\]

交叉熵公式:

\[\mathcal{L}(p,q)=-\sum_i p_i\log q_i\]

因为只有类别 1 的 (p_i=1),其他都是 0,所以:

\[\mathcal{L}=-\log q_1=-\log(0.7)\]

计算:

\[-\log(0.7)=0.357\]

如果模型预测:

\[q_1=0.3\]

则:

\[-\log(0.3)=1.204\]

可以看到:

  • 正确类别概率越大,损失越小
  • 正确类别概率越小,损失越大

交叉熵的含义是:

如果真实类别是 (y),模型只关心它给正确类别分配了多少概率。


15. 训练过程

训练一个分类器通常包括:

  1. 前向传播:算出预测概率
  2. 计算损失:比较预测和真实标签
  3. 反向传播:计算梯度
  4. 更新参数:做一次梯度下降

公式:

\[\theta_{\text{new}}=\theta_{\text{old}}-\eta\nabla_\theta \mathcal{L}\]

含义:

  • \(\theta\):所有可学习参数,包括 \(W_1,b_1,W_2,b_2\)
  • \(\nabla_\theta \mathcal{L}\):损失对参数的梯度
  • \(\eta\):学习率
  • \(\theta_{\text{new}}\):更新后的参数

15.1 一个具体更新例子

假设:

\[w=0.5\]

损失对 \(w\) 的梯度是:

\[\frac{\partial \mathcal{L}}{\partial w}=2\]

学习率:

\[\eta=0.1\]

那么:

\[w_{\text{new}}=0.5-0.1*2=0.3\]

这个式子就是梯度下降的核心。


16. 自动微分

手动推导每一层梯度很麻烦。

PyTorch 会记录你做的运算,形成计算图:

x
↓
Linear
↓
ReLU
↓
Linear
↓
Softmax
↓
CrossEntropyLoss

调用:

loss.backward()

后,框架会从 loss 出发,反向逐层计算梯度,并自动写入每个参数的 .grad。

这就是自动微分。


17. 代码

以下demo实现了:

  1. 构造两个非线性可分的二维数据集
  2. 训练一个 logistic regression
  3. 训练一个两层 MLP
  4. 比较两者准确率

运行:

#!/usr/bin/env python
"""Chapter 1: tensors, autograd, and a tiny classifier."""

from __future__ import annotations

import argparse
from dataclasses import dataclass

import torch
from torch import nn


@dataclass
class Args:
    samples: int = 512
    noise: float = 0.25
    epochs: int = 200
    lr: float = 0.03
    seed: int = 1337


def make_two_moons(samples: int, noise: float, device: torch.device) -> tuple[torch.Tensor, torch.Tensor]:
    """Create a small nonlinear binary-classification dataset."""
    angles = torch.rand(samples, device=device) * torch.pi
    upper = torch.stack((torch.cos(angles), torch.sin(angles)), dim=1)
    lower = torch.stack((1 - torch.cos(angles), 0.5 - torch.sin(angles)), dim=1)
    x = torch.cat((upper, lower), dim=0)
    x += torch.randn_like(x) * noise
    y = torch.cat((torch.zeros(samples, dtype=torch.long), torch.ones(samples, dtype=torch.long))).to(device)
    return x, y


def accuracy(model: nn.Module, x: torch.Tensor, y: torch.Tensor) -> float:
    with torch.inference_mode():
        pred = model(x).argmax(dim=1)
        return (pred == y).float().mean().item()


def train(model: nn.Module, x: torch.Tensor, y: torch.Tensor, epochs: int, lr: float) -> list[float]:
    optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
    loss_fn = nn.CrossEntropyLoss()
    losses: list[float] = []
    for _ in range(epochs):
        optimizer.zero_grad(set_to_none=True)
        logits = model(x)
        loss = loss_fn(logits, y)
        loss.backward()
        optimizer.step()
        losses.append(loss.item())
    return losses


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--samples", type=int, default=512)
    parser.add_argument("--noise", type=float, default=0.25)
    parser.add_argument("--epochs", type=int, default=200)
    parser.add_argument("--lr", type=float, default=0.03)
    parser.add_argument("--seed", type=int, default=1337)
    parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
    args = parser.parse_args()

    torch.manual_seed(args.seed)
    device = torch.device(args.device)
    x, y = make_two_moons(args.samples, args.noise, device)

    logistic = nn.Sequential(nn.Linear(2, 2)).to(device)
    mlp = nn.Sequential(
        nn.Linear(2, 32),
        nn.GELU(),
        nn.Linear(32, 2),
    ).to(device)

    logistic_losses = train(logistic, x, y, args.epochs, args.lr)
    mlp_losses = train(mlp, x, y, args.epochs, args.lr)

    print(f"device: {device}")
    print(f"final logistic loss: {logistic_losses[-1]:.4f}, accuracy: {accuracy(logistic, x, y):.4f}")
    print(f"final MLP loss:      {mlp_losses[-1]:.4f}, accuracy: {accuracy(mlp, x, y):.4f}")


if __name__ == "__main__":
    main()

输出:

final logistic loss: 0.3243, accuracy: 0.8467
final MLP loss:      0.1519, accuracy: 0.9424

从结果上看 MLP 在非线性数据上明显更好。


18. 和 LLM 的关系

LLM 本质上也是一个条件概率模型:

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

区别在于:

  • 输入不是固定长度特征向量,而是 token 序列
  • 模型不是简单 MLP,而是 Transformer
  • 输出不是类别标签,而是词表上的分布
  • 训练目标仍然是交叉熵

可以粗略理解为:

LLM 是一个超大规模、带注意力机制的序列版 MLP。

下一章我们开始把“文本”变成模型能吃的数字。


19. 符号与名词详解

符号 / 名词 含义
\(x\) 输入向量或输入张量
\(y\) 类别标签
\(\hat{y}\) 模型预测的类别
\(W\) 权重矩阵,决定线性变换
\(b\) 偏置向量,允许整体平移
\(z\) 线性层输出或 logit
logit softmax 前的实数分数
softmax 把 logits 变成概率分布
\(P(y{\mid}x)\) 后验概率
\(P(x{\mid}y)\) 似然
\(P(y)\) 先验概率
\(P(x)\) 证据,用于归一化
\(\exp\) 指数函数 \(e0^x\)
\(\log\) 对数函数,是指数函数的逆运算
\(\sum\) 求和
\(\arg\max\) 使函数值最大的输入
ReLU \(\max(0,x)\)
GELU 平滑版 ReLU,Transformer 常用
tanh 输出范围 \((-1,1)\) 的激活函数
sigmoid 输出范围 \((0,1)\) 的激活函数
MLP 多层感知机,多层线性层加激活函数
hidden layer 隐藏层,中间特征表示
one-hot 只有一个位置为 1 的类别表示
交叉熵 分类常用损失,等价于 \(-\log q_y\)
\(\mathcal{L}\) 损失函数
\(\nabla_\theta\) 关于参数的梯度
\(\eta\) 学习率
梯度下降 沿负梯度方向更新参数
反向传播 用链式法则逐层计算梯度
自动微分 框架自动求导
生成式模型 显式建模数据生成过程
判别式模型 直接学习条件分布 \(P(y{\mid}x)\)