第 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”。
概率必须满足:
- 每个值都在 0 到 1 之间
- 所有值加起来等于 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 的性质
- 输出永远大于 0
- 所有输出加起来等于 1
- logit 越大,概率越大
- 它是“软”的 argmax
- 它可导,适合反向传播
5.4 温度
后续生成文本时会看到:
\[\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}\]其中 \(T\) 是温度:
- \(T<1\):分布更尖
- \(T>1\):分布更平
- \(T=1\):原始 softmax
6. 为什么线性模型不够
一个线性分类器只能用一条直线、一个平面或超平面分开类别。
如果数据是线性可分的:
○ ○ ○
--------
× × ×
线性模型够用。
但很多真实数据是这样的:
○ ○ ○
× × ×
○ ○ ○
或者:
内圈是 ×,外圈是 ○
这时单条直线分不开。
神经网络的作用是:
- 先把输入变换到另一个空间
- 在新空间里让它变得线性可分
- 再做分类
这就是“表示学习”。
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 可以看成:
- 第一层把原始空间扭曲、折叠
- 第二层在扭曲后的空间里做线性分类
换句话说:
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. 训练过程
训练一个分类器通常包括:
- 前向传播:算出预测概率
- 计算损失:比较预测和真实标签
- 反向传播:计算梯度
- 更新参数:做一次梯度下降
公式:
\[\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实现了:
- 构造两个非线性可分的二维数据集
- 训练一个 logistic regression
- 训练一个两层 MLP
- 比较两者准确率
运行:
#!/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)\) |