深度学习常用公式手册

深度学习常用公式手册

按章节整理的深度学习核心公式,公式采用论文常用格式(LaTeX 排版) 。每条公式包含:公式 → 符号说明 → 应用场景 → PyTorch 实现 → 图形

符号读音/含义速查见《深度学习符号速查表》。

章节目录

  1. 激活函数
  2. 归一化
  3. 损失函数
  4. 优化器与学习率调度
  5. 正则化
  6. 卷积与池化
  7. 序列模型与注意力
  8. 强化学习
  9. 生成模型
  • 附录 A:公式-API 速查表
  • 附录 B:公式图像集(函数曲线/图形)

第一章 激活函数

1.1 Sigmoid

公式

σ ( x ) = 1 1 + e − x , σ ′ ( x ) = σ ( x ) ( 1 − σ ( x ) ) \sigma(x) = \frac{1}{1 + e^{-x}}, \qquad \sigma'(x) = \sigma(x)\big(1 - \sigma(x)\big) σ(x)=1+e−x1,σ′(x)=σ(x)(1−σ(x))

符号说明 :把任意实数压缩到 ( 0 , 1 ) (0, 1) (0,1),可解释为概率;导数最大仅 0.25 0.25 0.25,深层网络中连乘易梯度消失。

应用场景:二分类输出层;LSTM/GRU 的门控(遗忘门、输入门);任何需要"开关概率"的地方。隐藏层已基本弃用(梯度消失)。

PyTorch

python 复制代码
import torch
import torch.nn.functional as F

logits = torch.randn(8)                 # 8 个样本的二分类原始得分(任意形状均可)
p = torch.sigmoid(logits)               # 逐元素压到 (0,1) → 可视为 P(y=1|x)
print(p.shape)                          # (8,):与输入同形,sigmoid 不改变形状

# ⚠ 实战不要写 F.binary_cross_entropy(sigmoid(z), y)(先σ再log,p≈0时log下溢−inf):
loss = F.binary_cross_entropy_with_logits(logits, y)
# 该版本内部用 log-sigmoid 直接算 log σ(z):数值稳定且少一次指数运算

g = torch.sigmoid(x)                    # 另一主战场:LSTM/GRU 的门(输出当 0~1 开关用)

代码解读

  • torch.sigmoid 是纯逐元素函数,梯度 σ ( 1 − σ ) \sigma(1-\sigma) σ(1−σ) 由 autograd 自动接上,无需手写;
  • ..._with_logits 是官方推荐的稳定写法:把 log ⁡ σ ( z ) \log\sigma(z) logσ(z) 合并成一个 kernel,避免中间概率下溢;
  • 分工:推理 要概率时取 p训练 算损失时永远把 logits 直接交给损失函数。

示意图 :曲线形状见 formula_activations.png(蓝线,两端饱和);导数峰值仅 0.25 见 graph_act_deriv.png

图形 :见 formula_activations.png(蓝色曲线,两端饱和);导数曲线见附录 B.1

1.2 tanh

公式

tanh ⁡ ( x ) = e x − e − x e x + e − x = 2 σ ( 2 x ) − 1 , tanh ⁡ ′ ( x ) = 1 − tanh ⁡ 2 ( x ) \tanh(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}} = 2\sigma(2x) - 1, \qquad \tanh'(x) = 1 - \tanh^{2}(x) tanh(x)=ex+e−xex−e−x=2σ(2x)−1,tanh′(x)=1−tanh2(x)

符号说明 :零中心化(输出均值 ≈ 0 \approx 0 ≈0)的 S 形函数,值域 ( − 1 , 1 ) (-1, 1) (−1,1);梯度峰值为 1 1 1,是 sigmoid 的 4 倍,但仍会饱和。

应用场景:RNN 默认隐藏激活;LSTM 的候选记忆与输出变换;需要输出有正有负的场景。

PyTorch

python 复制代码
h = torch.tanh(z)                       # 逐元素压到 (−1,1),输出零中心化
rnn = torch.nn.RNN(input_size=128, hidden_size=256)
out, h_n = rnn(x)                       # 其内部隐藏层即 h_t = tanh(W_hh h_{t−1} + W_xh x_t + b)
# 选 tanh 而非 sigmoid 的原因:导数峰值 1 vs 0.25,且输出均值≈0(下游层输入不偏置)

代码解读

  • nn.RNN 不需要显式写 tanh------模块内部已按公式 7.1 组装好,out 是全部时刻隐藏态、h_n 是末时刻状态;
  • 若想让 RNN 换激活(如 ReLU),构造时传 nonlinearity='relu'(仅 nn.RNN 支持,LSTM 固定)。

示意图 :tanh 曲线见 formula_activations.png(绿线,零中心 S 形)

图形 :见 formula_activations.png(绿色曲线)

1.3 ReLU 及其变体

公式

R e L U ( x ) = max ⁡ ( 0 ,   x ) , L e a k y R e L U ( x ) = max ⁡ ( α x ,   x ) , G E L U ( x ) = x   Φ ( x ) \mathrm{ReLU}(x) = \max(0,\, x), \qquad \mathrm{LeakyReLU}(x) = \max(\alpha x,\, x), \qquad \mathrm{GELU}(x) = x\,\Phi(x) ReLU(x)=max(0,x),LeakyReLU(x)=max(αx,x),GELU(x)=xΦ(x)

符号说明 : Φ \Phi Φ 为标准正态分布的 CDF;ReLU 正区间梯度恒为 1 1 1 → 不衰减、计算零成本,代价是 x < 0 x<0 x<0 时梯度为 0 0 0("死神经元");LeakyReLU( α = 0.01 \alpha=0.01 α=0.01)与 GELU 修复负区间。

应用场景:ReLU = CNN/MLP 隐藏层默认;LeakyReLU 用于担心死神经元的深层网络;GELU = Transformer(BERT/GPT)默认。

PyTorch

python 复制代码
import torch.nn.functional as F

z = torch.randn(64, 256)                  # 某层预激活值 (B=64, 特征=256)
h = F.relu(z)                             # 负半轴直接置 0:输出稀疏、正区间梯度恒 1
h = F.leaky_relu(z, negative_slope=0.01)  # 负半轴留 0.01 斜率 → 修"死神经元"
h = F.gelu(z)                             # 平滑版 x·Φ(x):BERT/GPT 的 FFN 默认

model = torch.nn.Sequential(              # 模块式写法:按顺序堆叠成网络
    torch.nn.Linear(784, 256), torch.nn.ReLU(),   # 惯例:Linear 后紧跟激活
    torch.nn.Linear(256, 10),
)

代码解读

  • F.* 是无状态函数(用完即走),nn.* 是可嵌入 Sequential 的模块------激活函数无参数,两者行为完全相同;
  • "死神经元":某神经元一旦输入恒负,ReLU 梯度恒 0 → 该神经元永久停更新;LeakyReLU 的小斜率保留梯度通路;
  • GELU 按概率"软门控"(值越大越放行),曲线光滑 → 优化更稳,是 Transformer 的经验默认。

示意图 :四种激活的曲线对比见 formula_activations.png

图形


第二章 归一化

2.1 BatchNorm(批归一化)

公式

μ B = 1 m ∑ i = 1 m x i , σ B 2 = 1 m ∑ i = 1 m ( x i − μ B ) 2 \mu_{\mathcal{B}} = \frac{1}{m}\sum_{i=1}^{m} x_i, \qquad \sigma_{\mathcal{B}}^{2} = \frac{1}{m}\sum_{i=1}^{m} (x_i - \mu_{\mathcal{B}})^{2} μB=m1i=1∑mxi,σB2=m1i=1∑m(xi−μB)2

x ^ i = x i − μ B σ B 2 + ϵ , y i = γ   x ^ i + β \hat{x}i = \frac{x_i - \mu{\mathcal{B}}}{\sqrt{\sigma_{\mathcal{B}}^{2} + \epsilon}}, \qquad y_i = \gamma\, \hat{x}_i + \beta x^i=σB2+ϵ xi−μB,yi=γx^i+β

符号说明 :统计在一个 mini-batch B \mathcal{B} B(大小 m m m)内进行; ϵ ≈ 10 − 5 \epsilon \approx 10^{-5} ϵ≈10−5 防除零; γ , β \gamma, \beta γ,β 为逐特征可学习的缩放/平移,恢复表达能力。推理时改用训练期滑动平均的全局统计量。

应用场景 :CNN/MLP 加速收敛、允许更大学习率、缓解内部协变量偏移;对 batch 大小敏感( m = 1 m=1 m=1 时失效)。

PyTorch

python 复制代码
bn = torch.nn.BatchNorm1d(256)      # 声明: 256 组可学习 (γ,β) + 2 份滑动平均统计 (running_mean/var)
z = torch.randn(32, 256)            # (B=32, 特征=256)
h = bn(z)                           # 训练态: 沿 B 维算 μ,σ → 逐列归一化 → γx̂+β
print(bn.running_mean.shape)        # (256,):训练期累积的全局统计,推理时用它

bn2d = torch.nn.BatchNorm2d(64)     # CNN 版: 输入 (B,64,H,W),沿 (B,H,W) 统计、每通道一组参数
model.eval()                        # ⚠ 切换: 推理改用 running 统计且不再更新;忘记切 = 结果随机漂移

代码解读

  • 构造函数参数 = 通道/特征数 (不是 batch 大小);每个特征独享一对 ( γ , β ) (\gamma,\beta) (γ,β);
  • 训练/推理双模式是 BN 最大的坑:训练用"当前 batch 统计"(有噪声但自适应),推理用"滑动平均统计"(确定性强),靠 train()/eval() 切换;
  • batch=1 时训练态方差无意义 → BN 失效,小 batch 请换 LayerNorm 或 GroupNorm。

示意图

复制代码
BatchNorm 的统计方向(对 (B,256) 张量):
  样本1 [ x  x  x ... ]      每一"列"= 同一特征在不同样本上的取值
  样本2 [ x  x  x ... ]  →   μ,σ 按列计算 → 得到 256 对统计量
  ...                        再逐列归一化: x̂=(x−μ)/√(σ²+ε)
  样本32[ x  x  x ... ]      最后 y = γx̂+β(可学习,恢复表达能力)

配图见 formula_norm.png(蓝色高亮列)与 graph_norm_hist.png(归一化前后直方图)

图形 :见 formula_norm.png(蓝色列 = BN 的统计方向);归一化前后分布见附录 B.3

2.2 LayerNorm(层归一化)

公式

μ = 1 d ∑ j = 1 d x j , σ 2 = 1 d ∑ j = 1 d ( x j − μ ) 2 , y = γ σ 2 + ϵ   ( x − μ ) + β \mu = \frac{1}{d}\sum_{j=1}^{d} x_j, \qquad \sigma^{2} = \frac{1}{d}\sum_{j=1}^{d} (x_j - \mu)^{2}, \qquad y = \frac{\gamma}{\sqrt{\sigma^{2} + \epsilon}}\,(x - \mu) + \beta μ=d1j=1∑dxj,σ2=d1j=1∑d(xj−μ)2,y=σ2+ϵ γ(x−μ)+β

符号说明 :统计维度从"跨 batch"换成"单样本的 d d d 个特征",因此与 batch 大小无关。

应用场景:Transformer 标配(每个 token 独立归一化);RNN;batch 很小的场景。

PyTorch

python 复制代码
ln = torch.nn.LayerNorm(512)        # 参数 = 归一化形状: 对最后一维(512 个特征)算 μ,σ
x = torch.randn(32, 10, 512)        # (B, T, d): 32 条序列 × 10 个 token × 512 维
h = ln(x)                           # 每个 (b,t) 行独立归一化 → 320 组统计量互不干扰
# 与 BN 的关键差异: 无 running 统计、无 train/eval 差异 → 不存在"忘记切模式"的坑

代码解读

  • 构造参数可以是元组做多维归一化,如 LayerNorm((10, 512)) 对最后两维一起统计;
  • 统计只依赖单样本 → batch=1、变长序列、推理部署全都行为一致,这是 Transformer 选它的根本原因;
  • 参数量:仅 2 d 2d 2d 个( γ , β \gamma,\beta γ,β 各 512),与 batch、序列长无关。

示意图 :统计方向对比见 formula_norm.png(橙色高亮行 = LN 方向)

图形


第三章 损失函数

3.1 MSE(均方误差)

公式

L M S E = 1 n ∑ i = 1 n ( y ^ i − y i ) 2 \mathcal{L}{\mathrm{MSE}} = \frac{1}{n}\sum{i=1}^{n} (\hat{y}_i - y_i)^{2} LMSE=n1i=1∑n(y^i−yi)2

符号说明:误差平方 → 大误差被放大惩罚,对离群点敏感;等价于高斯噪声假设下的负对数似然。

应用场景:回归任务(房价、轨迹预测、图像重建的像素级损失)。

PyTorch

python 复制代码
pred, target = torch.randn(32, 3), torch.randn(32, 3)   # 32 样本 × 3 维回归输出
loss = torch.nn.functional.mse_loss(pred, target)       # 默认 reduction='mean': 先平方再对全部元素平均
# = ((pred − target)**2).mean()  → 标量,可直接 backward()
loss_sum = torch.nn.functional.mse_loss(pred, target, reduction='sum')
# reduction='sum': 不求平均 → 损失值随 batch 增大而增大(VAE 重建项惯例用 sum)
loss_none = torch.nn.functional.mse_loss(pred, target, reduction='none')
# reduction='none': 保留逐元素误差 (32,3),用于自定义加权(如按样本重要性加权)

代码解读

  • 三个 reduction 模式决定了"平方之后怎么聚合":mean(默认)/ sum / none(逐元素);
  • MSE 对离群样本的梯度是 2 e 2e 2e,误差越大梯度越大 → 一个坏样本可能主导整个 batch,必要时换 Huber;
  • 输入形状任意且 pred/target 需同形(或可广播)。

示意图 :抛物线形状见 formula_losses.png 左图蓝线

图形 :见 formula_losses.png(左图蓝色抛物线)

3.2 L1 / Huber(SmoothL1)

公式

L L 1 = 1 n ∑ i = 1 n ∣ y ^ i − y i ∣ , L H u b e r = { 1 2 e 2 , ∣ e ∣ ≤ δ δ ( ∣ e ∣ − 1 2 δ ) , ∣ e ∣ > δ ( e = y ^ − y ) \mathcal{L}{\mathrm{L1}} = \frac{1}{n}\sum{i=1}^{n} \lvert \hat{y}i - y_i \rvert, \qquad \mathcal{L}{\mathrm{Huber}} = \begin{cases} \tfrac{1}{2}e^{2}, & \lvert e \rvert \le \delta \\4pt \delta\left(\lvert e \rvert - \tfrac{1}{2}\delta\right), & \lvert e \rvert > \delta \end{cases} \quad (e = \hat{y} - y) LL1=n1i=1∑n∣y^i−yi∣,LHuber={21e2,δ(∣e∣−21δ),∣e∣≤δ∣e∣>δ(e=y^−y)

符号说明 :L1 梯度恒定 → 对离群点鲁棒但零点不可导;Huber 在小误差区取平方(平滑可导)、大误差区取线性(鲁棒),两者兼得; δ \delta δ 为切换点。

应用场景:含离群点的回归;目标检测的边框回归(Faster R-CNN 用 SmoothL1)。

PyTorch

python 复制代码
loss = torch.nn.functional.l1_loss(pred, target)                  # |e| 的平均:梯度恒 ±1,离群点不爆炸
loss = torch.nn.functional.smooth_l1_loss(pred, target, beta=1.0) # beta 即公式中的 δ(切换点)
# beta=1.0 时: |e|≤1 用 0.5e²(原点平滑可导); |e|>1 用 |e|−0.5(线性鲁棒)
# 目标检测框回归常用 beta=0.1~1.0:小误差精细调、大误差不过度惩罚

代码解读

  • L1 的梯度大小与误差无关(恒为 1)→ 收敛末期会在最优解附近"匀速震荡",不如 MSE 精细;Huber 兼得两者;
  • smooth_l1_loss 即 Faster R-CNN 论文里的 SmoothL1;新版别名 huber_loss(delta 参数同义);
  • 选 beta 的经验:设为"典型误差量级",如框回归用 IoU 误差的量级。

示意图 :三段形状对比见 formula_losses.png 左图(绿=L1、橙=Huber)

图形 :见 formula_losses.png(左图绿/橙曲线)

3.3 BCE(二元交叉熵)

公式

L B C E = − 1 n ∑ i = 1 n y i log ⁡ p i + ( 1 − y i ) log ⁡ ( 1 − p i ) , p = σ ( y ^ ) \mathcal{L}{\mathrm{BCE}} = -\frac{1}{n}\sum{i=1}^{n} \Big y_i \\log p_i + (1 - y_i)\\log(1 - p_i) \\Big, \qquad p = \sigma(\hat{y}) LBCE=−n1i=1∑nyilogpi+(1−yi)log(1−pi),p=σ(y^)

符号说明 : y ∈ { 0 , 1 } y \in \{0, 1\} y∈{0,1} 为真标签, p p p 为预测概率; y = 1 y=1 y=1 时只剩 − log ⁡ p -\log p −logp → p → 1 p \to 1 p→1 损失趋于 0。

应用场景:二分类;多标签分类(每个标签独立 BCE 再求和,如图像 tagging)。

PyTorch

python 复制代码
logits = torch.randn(32)                    # 二分类原始得分 (B,),未经 sigmoid
y = torch.randint(0, 2, (32,)).float()      # 真标签 0/1,需 float 类型

loss = torch.nn.functional.binary_cross_entropy_with_logits(logits, y)
# 内部 = mean( max(z,0) − z·y + log(1+e^{−|z|}) ),即 log-sigmoid 的稳定实现

# 多标签分类(一张图多个标签): 每个标签独立做 BCE 再求和/平均
loss = torch.nn.functional.binary_cross_entropy_with_logits(logits_20, y_20)  # (B,20) 对 (B,20)
# 此时 y 是 0/1 矩阵而非类别索引;与 cross_entropy(单标签互斥)严格区分

代码解读

  • 单标签二分类用本公式;多标签 (标签不互斥)也用本公式但逐标签独立;多类互斥 才用 cross_entropy
  • pos_weight 参数可给正类加权(正负样本不平衡时,如正:负=1:50 设 pos_weight=50);
  • y 必须是 float(0.0/1.0),传 Long 会报类型错。

示意图 : − log ⁡ p -\log p −logp 曲线见 formula_losses.png 右图蓝线

图形 :见 formula_losses.png(右图蓝色 − log ⁡ p -\log p −logp 曲线)

3.4 交叉熵(多分类)

公式

L C E = − 1 n ∑ i = 1 n ∑ c = 1 C y i c   log ⁡ y ^ i c , y ^ = s o f t m a x ( z ) = e z c ∑ c ′ e z c ′ \mathcal{L}{\mathrm{CE}} = -\frac{1}{n}\sum{i=1}^{n}\sum_{c=1}^{C} y_{ic}\, \log \hat{y}{ic}, \qquad \hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{z}) = \frac{e^{z_c}}{\sum{c'} e^{z_{c'}}} LCE=−n1i=1∑nc=1∑Cyiclogy^ic,y^=softmax(z)=∑c′ezc′ezc

符号说明 : y i c y_{ic} yic 为 one-hot 标签(代码中用类别索引表示);等价于 − log ⁡ P ( 正确类 ) -\log P(\text{正确类}) −logP(正确类);与 softmax 是天然组合。

应用场景:一切单标签多分类:图像分类、语言模型下一个词、语音识别。

PyTorch

python 复制代码
logits = torch.randn(32, 10)              # (B=32, C=10) 原始得分,⚠ 不要先 softmax!
labels = torch.randint(0, 10, (32,))      # (B,) 类别索引 Long 型,无需 one-hot

loss = torch.nn.functional.cross_entropy(logits, labels)
# 等价展开: logp = log_softmax(logits, dim=-1)      # 沿类维度归一化+取log(稳定)
#           loss  = -logp[arange(32), labels].mean() # 取"真类"的 log 概率,取负,按 batch 平均
print(loss.shape)                          # torch.Size([]) 标量 → 直接 .backward()

loss_ls = torch.nn.functional.cross_entropy(logits, labels, label_smoothing=0.1)
# 标签平滑: 把 one-hot 的 1 换成 0.9、0 换成 0.1/C → 防止模型过度自信,提升泛化

代码解读

  • 传概率进去 = 二次归一化,梯度全错------这是新手最高频错误;
  • logp[arange(B), labels] 这步"按索引取真类"就是公式里 ∑ c y i c log ⁡ y ^ i c \sum_c y_{ic}\log\hat y_{ic} ∑cyiclogy^ic 的稀疏实现(one-hot 求和只剩一项);
  • 类别不平衡可传 weight=每类权重张量;想逐样本损失用 reduction='none'

示意图

复制代码
logits(32,10) ─▶ log_softmax(沿类维) ─▶ 按 labels 索引取真类 ─▶ 取负 ─▶ mean ─▶ 标量 loss
      ▲                                                                    (即 −log P(真类) 的 batch 平均)
      └── ⚠ 入口必须是原始得分

配图见 fig4_ce_pipeline.png

图形

3.5 Focal Loss

公式

L F o c a l = − ( 1 − p t ) γ log ⁡ p t , p t = { p , y = 1 1 − p , y = 0 \mathcal{L}_{\mathrm{Focal}} = -(1 - p_t)^{\gamma} \log p_t, \qquad p_t = \begin{cases} p, & y = 1 \\ 1 - p, & y = 0 \end{cases} LFocal=−(1−pt)γlogpt,pt={p,1−p,y=1y=0

符号说明 : ( 1 − p t ) γ (1-p_t)^{\gamma} (1−pt)γ 为调制因子( γ = 2 \gamma=2 γ=2)------易分样本( p t → 1 p_t \to 1 pt→1)权重趋近 0,难分样本主导梯度。

应用场景 :极端类别不平衡:目标检测(RetinaNet,前景:背景 ≈ 1 : 1000 \approx 1:1000 ≈1:1000)。

PyTorch

python 复制代码
p = torch.sigmoid(logits)                    # 预测概率 (B,)
pt = torch.where(y == 1, p, 1 - p)           # p_t: "真类"的概率(y=1 取 p,y=0 取 1−p)
loss = (-(1 - pt) ** 2 * torch.log(pt + 1e-8)).mean()
#        └── 调制因子(γ=2) ──┘   └── 普通 BCE ──┘
# 易分样本 pt→1: (1−pt)²→0 → 该项损失被"静音";难分样本 pt→0: 因子→1 → 主导梯度

代码解读

  • torch.where(cond, a, b) 是逐元素三元选择,这里把二分类统一成"真类概率"一个量 p t p_t pt;
  • +1e-8 防 log ⁡ 0 \log 0 log0;γ 越大对易分样本压得越狠(检测常用 γ=2、α=0.25 再平衡正负类);
  • torchvision 的 sigmoid_focal_loss 是官方优化实现,实战可直接用。

示意图 :Focal 与 BCE 曲线对比见 formula_losses.png 右图(红虚线被压低的部分=易分样本)

图形 :见 formula_losses.png(右图红色虚线:压低易分样本)

3.6 KL 散度

公式

D K L ( q   ∥   p ) = ∑ x q ( x ) log ⁡ q ( x ) p ( x ) = E x ∼ q log ⁡ q ( x ) − log ⁡ p ( x ) D_{\mathrm{KL}}(q \,\|\, p) = \sum_{x} q(x) \log \frac{q(x)}{p(x)} = \mathbb{E}_{x \sim q}\big\\log q(x) - \\log p(x)\\big DKL(q∥p)=x∑q(x)logp(x)q(x)=Ex∼qlogq(x)−logp(x)

符号说明 :衡量分布 q q q 相对 p p p 的"额外编码长度"; q = p q=p q=p 时为 0;非对称 : D K L ( q ∥ p ) ≠ D K L ( p ∥ q ) D_{\mathrm{KL}}(q\|p) \ne D_{\mathrm{KL}}(p\|q) DKL(q∥p)=DKL(p∥q);两高斯之间有解析解。

应用场景:知识蒸馏(学生匹配教师的软分布);VAE 正则项;PPO 约束新旧策略距离。

PyTorch

python 复制代码
from torch.distributions import Normal, kl_divergence

# 场景1 VAE: q=N(μ,σ²) 与先验 N(0,1) 的 KL,有解析解 → 直接调用,无需采样
kl = kl_divergence(Normal(mu, std), Normal(0., 1.))     # 逐元素 KL,形状同 mu

# 场景2 知识蒸馏: F.kl_div 的入参约定很特殊 ------
loss = torch.nn.functional.kl_div(
    F.log_softmax(student / T, dim=-1),   # 第1参: 学生的 log 概率(必须先取 log!)
    F.softmax(teacher / T, dim=-1),       # 第2参: 教师的概率(target)
    log_target=False,                     # target 不是 log 形式
    reduction='batchmean',                # 除以 B(而不是元素总数)→ 与 batch 大小解耦
) * T * T                                 # ×T²: 补偿软化导致的梯度缩小(Hinton 原文约定)

代码解读

  • kl_div 的参数顺序/形式是著名陷阱:input 要 log 概率、target 要概率(与直觉相反);
  • reduction='batchmean' 才是论文里的"按样本平均";默认 'mean' 会再除以类别数,量纲错;
  • 温度 T 软化教师分布:T 越大"暗知识"(类间相似度)占比越高; × T 2 \times T^2 ×T2 让梯度量级与 T=1 可比。

示意图 :KL 的几何含义(两分布阴影差)见 formula_kl.png

图形


第四章 优化器与学习率调度

4.1 SGD(随机梯度下降)

公式

θ ← θ − η   ∇ θ L \theta \leftarrow \theta - \eta\, \nabla_{\theta} \mathcal{L} θ←θ−η∇θL

符号说明 : η \eta η 学习率是唯一超参;"随机"指每个 mini-batch 梯度是全体梯度的无偏估计。

应用场景:理论基线;CV 大模型精调时配合 momentum + 调度器仍是最优选择之一。

PyTorch

python 复制代码
opt = torch.optim.SGD(model.parameters(),   # 第1参: 要更新的参数(可训练张量的迭代器/列表)
                      lr=0.01)              # η: 唯一必需超参,步长
# 训练循环三件套:
#   loss.backward()   → 填充每个参数的 .grad(即 ∇θL)
#   opt.step()        → 执行 θ ← θ − η·∇θL(对所有参数并行)
#   opt.zero_grad()   → 清零 .grad(PyTorch 默认累加,不清就叠加上一步)

代码解读

  • model.parameters() 返回生成器,优化器据此为每个参数维护状态(SGD 无额外状态);
  • 只训部分层:传过滤后的列表,如 [p for p in model.parameters() if p.requires_grad]
  • lr 量级经验:SGD 用 0.01~0.1(比 Adam 大 10~100 倍才跑得动)。

示意图 :单步更新在损失面上的移动见 fig1_hyperparams.png;轨迹见 formula_optimizers.png 灰线

4.2 Momentum(动量法)

公式

v t ← μ   v t − 1 + ∇ θ L , θ ← θ − η   v t ( μ = 0.9 ) v_t \leftarrow \mu\, v_{t-1} + \nabla_{\theta} \mathcal{L}, \qquad \theta \leftarrow \theta - \eta\, v_t \qquad (\mu = 0.9) vt←μvt−1+∇θL,θ←θ−ηvt(μ=0.9)

符号说明 : v v v 是梯度的指数滑动平均(物理类比:小球带惯性下坡);一致方向被放大、震荡方向相互抵消 → 加速穿过窄谷、平滑 mini-batch 噪声。

应用场景:SGD 的默认增强;CNN 训练标配(SGD + momentum 0.9)。

PyTorch

python 复制代码
opt = torch.optim.SGD(model.parameters(), lr=0.01,
                      momentum=0.9,        # μ: 动量衰减率,v ← 0.9v + g(历史梯度指数加权)
                      nesterov=True)       # Nesterov 变体: 在"预判位置"θ−ημv 处算梯度,更提前纠错
# 优化器为每个参数多存一份 v(与参数同形)→ 显存开销 ×2

代码解读

  • 动量的效果 = 把一致方向的梯度累加放大(稳态增益 1 / ( 1 − μ ) = 10 1/(1-\mu)=10 1/(1−μ)=10 倍),震荡方向互相抵消;
  • 因此加了 momentum 后 lr 通常要调小(等效步长变大),否则过冲;
  • nesterov=True 是"先迈一步再回头看梯度",收敛末期更稳,CV 里常用。

示意图 :动量如何冲过窄谷见 formula_optimizers.png 绿线

4.3 Adam

公式

m t = β 1 m t − 1 + ( 1 − β 1 )   g t 一阶矩(梯度均值) v t = β 2 v t − 1 + ( 1 − β 2 )   g t 2 二阶矩(梯度平方均值) m ^ t = m t 1 − β 1 t , v ^ t = v t 1 − β 2 t 偏差修正 θ ← θ − α   m ^ t v ^ t + ϵ 自适应步长更新 \begin{aligned} m_t &= \beta_1 m_{t-1} + (1 - \beta_1)\, g_t & &\text{一阶矩(梯度均值)} \\ v_t &= \beta_2 v_{t-1} + (1 - \beta_2)\, g_t^{2} & &\text{二阶矩(梯度平方均值)} \\ \hat{m}_t &= \frac{m_t}{1 - \beta_1^{t}}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^{t}} & &\text{偏差修正} \\ \theta &\leftarrow \theta - \alpha\, \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} & &\text{自适应步长更新} \end{aligned} mtvtm^tθ=β1mt−1+(1−β1)gt=β2vt−1+(1−β2)gt2=1−β1tmt,v^t=1−β2tvt←θ−αv^t +ϵm^t一阶矩(梯度均值)二阶矩(梯度平方均值)偏差修正自适应步长更新

符号说明 : g t = ∇ θ L g_t = \nabla_\theta \mathcal{L} gt=∇θL;默认 β 1 = 0.9 \beta_1 = 0.9 β1=0.9、 β 2 = 0.999 \beta_2 = 0.999 β2=0.999、 ϵ = 10 − 8 \epsilon = 10^{-8} ϵ=10−8; v ^ t \sqrt{\hat v_t} v^t 把各维度梯度缩放到相近量级 → 对稀疏梯度与不同量纲特征友好;偏差修正消除 m , v m, v m,v 从零初始化带来的前期偏小。

应用场景:默认首选:Transformer/NLP、GAN、RL;不细调超参时的安全选择。

PyTorch

python 复制代码
opt = torch.optim.Adam(model.parameters(),
                       lr=1e-3,              # α: 注意比 SGD 小 1~2 个量级
                       betas=(0.9, 0.999),   # (β₁, β₂): 一阶/二阶矩衰减率
                       eps=1e-8)             # ε: 防除零;梯度极稀疏时可提到 1e-4
# 优化器内部为每个参数维护 m, v 两份缓冲(对应公式的 m_t, v_t)→ 显存 ×3

opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
# AdamW = Adam + 解耦权重衰减: 收缩项直接作用于 θ,不被自适应步长缩放
# (普通 Adam 的 weight_decay 会被 1/√v̂ 扭曲 → 正则效果失真;Transformer 训练标配 AdamW)

代码解读

  • 自适应步长的直觉:某参数历史梯度小 → v ^ \sqrt{\hat v} v^ 小 → 实际步长被放大,反之压缩 → 各参数"齐头并进";
  • 偏差修正 m ^ , v ^ \hat m, \hat v m^,v^ 只在前几十步起作用( 1 − β t → 1 1-\beta^t \to 1 1−βt→1 后自动消失),无需关心;
  • 经验值:NLP/Transformer 用 lr=1e-4~3e-4 + AdamW(wd=0.01);GAN 常用 β₂=0.999→0.9 更稳。

示意图

复制代码
Adam 的内部缓冲(每个参数 θᵢ 配套):
  mᵢ ← 0.9mᵢ + 0.1gᵢ        梯度方向的滑动平均("往哪走")
  vᵢ ← 0.999vᵢ + 0.001gᵢ²   梯度平方的滑动平均("走多大")
  更新: θᵢ −= α·m̂/(√v̂ᵢ+ε)  → 每个参数拥有自己的等效学习率

配图见 formula_optimizers.png(红线:近似直线直达谷底)

图形(4.1--4.3 对比)

4.4 学习率调度:余弦退火 / Warmup

公式

η t = η min ⁡ + 1 2 ( η max ⁡ − η min ⁡ ) ( 1 + cos ⁡ π t T ) , Warmup: η t = η max ⁡ ⋅ t W ( t < W ) \eta_t = \eta_{\min} + \frac{1}{2}\left(\eta_{\max} - \eta_{\min}\right)\left(1 + \cos \frac{\pi t}{T}\right), \qquad \text{Warmup: } \eta_t = \eta_{\max}\cdot \frac{t}{W}\ (t < W) ηt=ηmin+21(ηmax−ηmin)(1+cosTπt),Warmup: ηt=ηmax⋅Wt (t<W)

符号说明 : T T T 为总步数、 W W W 为 warmup 步数;warmup 让模型在梯度噪声最大的初期用小步长稳定下来(Transformer 训练必备)。

应用场景:几乎所有现代训练:BERT/GPT 的 warmup+cosine;CV 的 StepLR(每 30 epoch 减半)。

PyTorch

python 复制代码
opt = torch.optim.AdamW(model.parameters(), lr=1e-3)          # lr 这里当作 η_max
warm = torch.optim.lr_scheduler.LinearLR(opt, start_factor=0.01, total_iters=500)
# 前 500 步: lr 从 1e-3×0.01 线性升到 1e-3(让小步长先稳住早期大梯度)
cos  = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=19500)
# 第 500 步后: 按余弦从 1e-3 平滑降到 η_min(默认0),T_max = 剩余总步数
sched = torch.optim.lr_scheduler.SequentialLR(opt, [warm, cos], milestones=[500])
# SequentialLR: 步数 <500 用 warm,≥500 切到 cos(milestone 是"切换点"而非长度)

# 每个训练步的顺序(顺序错了 lr 会错拍):
#   loss.backward(); opt.step(); sched.step()
print(sched.get_last_lr())     # 查看当前实际 lr,调试调度必备

代码解读

  • 调度器改的是优化器参数组里的 lr:opt.param_groups[0]['lr'],可用 print 验证曲线;
  • warmup 步数经验 = 总步数的 1%~5%(或前 1~2 个 epoch);
  • 按 epoch 调度(CV 常见)就在 epoch 末尾调 sched.step(),按 step 调度(NLP)就放在每步 opt.step() 后。

示意图 :三种调度曲线(阶梯/余弦/warmup+余弦)见 formula_lrschedule.png

图形

4.5 梯度裁剪

公式

g ← c ∥ g ∥ 2   g if ∥ g ∥ 2 > c g \leftarrow \frac{c}{\lVert g \rVert_2}\, g \quad \text{if} \quad \lVert g \rVert_2 > c g←∥g∥2cgif∥g∥2>c

符号说明 : c c c 为阈值(常取 1.0 1.0 1.0 或 5.0 5.0 5.0);把全局梯度范数缩放到 c c c,只改长度不改方向;防"梯度爆炸"而非消失。

应用场景:RNN/LSTM 训练必备;Transformer、RL(策略梯度方差大)常规配置。

PyTorch

python 复制代码
loss.backward()                                            # 先算出原始梯度 g
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 内部: total = sqrt(Σ‖g‖²)(全局范数); 若 total > 1.0 → 所有 gᵢ 统一乘 1.0/total
# 名字带 _ 后缀 = 原地修改 .grad;返回值是裁剪前的范数(可收集监控)
opt.step()                                                 # 用"裁剪后"的梯度更新
norm = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)   # 返回值 = 裁剪前 ‖g‖
# 实战技巧: 把 norm 记到日志/曲线,突增 = 训练即将失稳的预警信号

代码解读

  • 必须夹在 backward()step() 之间------它改的是 .grad,不是参数;
  • "全局范数"把所有参数拼成一个大向量算 ∥ g ∥ 2 \lVert g\rVert_2 ∥g∥2,保持各参数梯度的相对比例(方向不变);
  • 按元素夹断用 clip_grad_value_(params, 1.0)(改方向,少用);RNN 必加,Transformer 常设 1.0。

示意图 :梯度来自反向传播链见 fig3_autograd.png;五步循环中的位置见 fig9_training_loop.png

图形 :梯度 ∥ g ∥ \lVert g \rVert ∥g∥ 即反向传播产物,见 fig3_autograd.png


第五章 正则化

5.1 L2 正则 / 权重衰减

公式

L ′ = L + λ ∥ θ ∥ 2 2 ⟺ θ ← θ − η   ∇ θ L − 2 η λ   θ \mathcal{L}' = \mathcal{L} + \lambda \lVert \theta \rVert_2^{2} \qquad \Longleftrightarrow \qquad \theta \leftarrow \theta - \eta\, \nabla_\theta \mathcal{L} - 2\eta\lambda\, \theta L′=L+λ∥θ∥22⟺θ←θ−η∇θL−2ηλθ

符号说明 : λ \lambda λ 控制收缩强度;等价更新式显示每步把权重额外往 0 收缩 2 η λ 2\eta\lambda 2ηλ 倍 → 模型更平滑、抗过拟合。(AdamW 中 weight_decay 与梯度解耦,效果更纯粹。)

应用场景 :几乎所有训练的默认配置( λ ∈ 10 − 4 , 10 − 2 \lambda \in 10\^{-4}, 10\^{-2} λ∈10−4,10−2);小数据集防过拟合首选。

PyTorch

python 复制代码
opt = torch.optim.AdamW(model.parameters(), lr=1e-3,
                        weight_decay=0.01)     # λ: 每步把 θ 额外收缩 η·λ·θ(与梯度无关)
# ⚠ 惯例: 偏置 b 和归一化的 γ/β 不加衰减(正则它们没意义还会伤拟合):
decay, no_decay = [], []
for n, p in model.named_parameters():
    (no_decay if (p.ndim == 1 or 'bias' in n) else decay).append(p)
opt = torch.optim.AdamW([{"params": decay, "weight_decay": 0.01},
                         {"params": no_decay, "weight_decay": 0.0}], lr=1e-3)
# 参数分组: 不同组可用不同 lr / wd,是微调大模型的常规操作

代码解读

  • weight_decay 就是公式里的 λ;AdamW 中它与自适应步长解耦 (直接乘性收缩),Adam 中则被 1 / v ^ 1/\sqrt{\hat v} 1/v^ 扭曲;
  • p.ndim == 1 筛出偏置/LN 参数(都是一维),这是 HuggingFace 训练脚本的标准写法;
  • λ 过大会欠拟合(权重被压死);先跑 wd=0 基线再 grid 搜 1e-4~1e-1。

示意图 :权重指数收缩曲线 θ t = θ 0 ( 1 − η λ ) t \theta_t=\theta_0(1-\eta\lambda)^t θt=θ0(1−ηλ)t 见 graph_weight_decay.png

图形 :权重指数收缩曲线见附录 B.4; λ \lambda λ 在损失中的位置见 fig1_hyperparams.png

5.2 Dropout

公式

a ′ = a ⊙ m 1 − p , m j ∼ B e r n o u l l i ( 1 − p ) i.i.d. 推理: a ′ = a \mathbf{a}' = \frac{\mathbf{a} \odot \mathbf{m}}{1 - p}, \quad m_j \sim \mathrm{Bernoulli}(1 - p) \ \text{i.i.d.} \qquad \text{推理: } \mathbf{a}' = \mathbf{a} a′=1−pa⊙m,mj∼Bernoulli(1−p) i.i.d.推理: a′=a

符号说明 : p p p 为失活概率(隐藏层常 0.1 ∼ 0.5 0.1 \sim 0.5 0.1∼0.5); ⊙ \odot ⊙ 为逐元素乘;除以 ( 1 − p ) (1-p) (1−p) 保持激活期望不变(inverted dropout),故推理期无需缩放。

应用场景 :全连接层防共适应;Transformer 中注意力与 FFN 后常用 p = 0.1 p=0.1 p=0.1;CNN 中逐渐被 BN+数据增强取代。

PyTorch

python 复制代码
drop = torch.nn.Dropout(p=0.1)      # p = 置零概率(注意是"丢掉"的概率,不是保留)
h = drop(a)                         # 训练态: 每个元素独立以 0.1 概率置 0,存活者 ×1/(1−0.1) 放大
# 同一 forward 内 mask 固定;每次 forward 重新采样 → 每步都是"不同子网络"
model.eval()                        # 切推理态: 模块自动变恒等映射(不置零不缩放)
h2 = torch.nn.functional.dropout(a, p=0.1, training=True)   # 函数式需手动传 training 标志

代码解读

  • ×1/(1−p) 的缩放保证训练/推理时期望一致(inverted dropout),所以推理无需任何补偿;
  • 函数式 F.dropouttraining 参数默认 True------推理时忘了传 False 会引入随机性,推荐用模块式 让它跟随 model.eval()
  • p 的选择:输入层小(0.1)、宽隐藏层大(0.5);Transformer 常用 0.1。

示意图 :节点失活结构见 formula_dropout.png(红叉=被置零,虚线边=断开)

图形

5.3 早停(Early Stopping)

公式(准则)

每个 epoch 评估: L v a l ( e ) ≥ min ⁡ e ′ < e L v a l ( e ′ ) 连续 K 次 ⟹ 停止并回滚最优权重 \text{每个 epoch 评估: } \quad \mathcal{L}{\mathrm{val}}^{(e)} \ge \min{e' < e} \mathcal{L}_{\mathrm{val}}^{(e')} \ \text{连续 } K \text{ 次} \quad \Longrightarrow \quad \text{停止并回滚最优权重} 每个 epoch 评估: Lval(e)≥e′<eminLval(e′) 连续 K 次⟹停止并回滚最优权重

符号说明 : K K K 为 patience;本质是"用验证集选择迭代次数"这一超参。

应用场景:训练时间长/数据小的场景;与权重衰减并列为最廉价的正则手段。

PyTorch

python 复制代码
best, patience, bad = 1e9, 5, 0
for epoch in range(100):
    train_one_epoch()                    # 正常训练一个 epoch
    val = evaluate()                     # 验证集损失(no_grad 下算,别用训练损失判断!)
    if val < best:                       # 创新高 → 存档
        best, bad = val, 0
        torch.save(model.state_dict(), "best.pt")   # 只存权重(state_dict),不存整个模型对象
    else:
        bad += 1                         # 连续不改善计数
        if bad >= patience: break        # 耐心耗尽 → 停(此时 best.pt 就是最优点)
model.load_state_dict(torch.load("best.pt"))        # 训练结束后回滚到验证最优权重

代码解读

  • state_dict()(纯权重字典)而非 pickle 整个模型:跨版本可加载、文件小;
  • 判据必须用验证集损失:训练损失在过拟合开始后仍会下降,用它早停永远停不下来;
  • patience 太小会过早停(噪声波动误判),太大浪费时间;5~10 个 epoch 是常见起点。

示意图 :训练/验证损失曲线的"拐点"即早停位置(train↓ val↑ 分叉处)------与 graph_weight_decay.png 同属"防过拟合三件套"


第六章 卷积与池化

6.1 卷积运算与输出尺寸

公式

y i , j = ∑ m ∑ n w m , n   x i + S m − P ,    j + S n − P + b , O = ⌊ W − K + 2 P S ⌋ + 1 yi, j = \sum_{m}\sum_{n} wm, n\, xi + S m - P,\\; j + S n - P + b, \qquad O = \left\lfloor \frac{W - K + 2P}{S} \right\rfloor + 1 yi,j=m∑n∑wm,nxi+Sm−P,j+Sn−P+b,O=⌊SW−K+2P⌋+1

符号说明 : W W W 输入尺寸、 K K K 核大小、 P P P 填充、 S S S 步幅(工程实现为互相关,统称卷积);参数量 = K 2 ⋅ C i n ⋅ C o u t = K^2 \cdot C_{\mathrm{in}} \cdot C_{\mathrm{out}} =K2⋅Cin⋅Cout,与输入尺寸无关(权重共享)。

应用场景 :CNN 架构设计时推算特征图尺寸;感受野计算;"same 卷积"即取 P = ( K − 1 ) / 2 P=(K-1)/2 P=(K−1)/2 使 O = W O=W O=W。

PyTorch

python 复制代码
conv = torch.nn.Conv2d(in_channels=3,      # 输入通道: RGB=3(必须与 x 的第2维一致)
                       out_channels=64,    # 卷积核个数 = 输出通道 = 64 种"模板"
                       kernel_size=3,      # K=3(传元组 (3,5) 可非方形)
                       stride=1, padding=1)  # P=(K−1)/2=1 → 尺寸不变(same 卷积)
x = torch.randn(8, 3, 224, 224)            # (B, C_in, H, W) 四维固定约定
out = conv(x)                              # → (8, 64, 224, 224)
# 尺寸公式验证: O = (224 − 3 + 2×1)/1 + 1 = 224 ✓
print(conv.weight.shape)                   # (64, 3, 3, 3) = (C_out, C_in, K, K)
# 参数量 = 3×3×3×64 + 64(偏置) = 1792,与输入 224×224 无关(权重共享的威力)

代码解读

  • 张量约定永远是 (B, C, H, W):通道维在第 2 位,BatchNorm2d 的参数也对应这一维;
  • weight 形状 (C_out, C_in, K, K):每个输出通道是一张"对所有输入通道求和"的 3D 模板;
  • 下采样用 stride=2(O 减半);groups=C_in 变深度可分离卷积(MobileNet 省参数量)。

示意图

复制代码
尺寸速算(套公式 O = ⌊(W−K+2P)/S⌋+1):
  224 ──K3,P1,S1──▶ 224 ──K3,P1,S2──▶ 111 ──K3,P1,S2──▶ 55 ...
        same卷积        stride2下采样

滑动过程见 formula_conv.png;1D 信号版见 graph_conv1d.png

图形

6.2 最大池化

公式

y i , j = max ⁡ ( m , n ) ∈ R i j x m , n , R i j = 以 ( i S , j S ) 为起点的 K × K 窗口 yi, j = \max_{(m, n) \in \mathcal{R}{ij}} xm, n, \qquad \mathcal{R}{ij} = \text{以 } (iS, jS) \text{ 为起点的 } K \times K \text{ 窗口} yi,j=(m,n)∈Rijmaxxm,n,Rij=以 (iS,jS) 为起点的 K×K 窗口

符号说明 :无参数操作;保留窗口内最强响应 → 小幅平移不改变输出(平移不变性); K = 2 , S = 2 K=2, S=2 K=2,S=2 时尺寸减半。

应用场景 :CNN 下采样降计算量;扩大感受野;经典 VGG 每两个卷积接一次 2 × 2 2\times 2 2×2 池化。

PyTorch

python 复制代码
pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)   # 2×2 窗口、步长2 → H,W 各减半
out = pool(x)                                        # (B,64,224,224) → (B,64,112,112)
# 无参数、无训练/推理差异:纯函数式下采样
avg = torch.nn.AvgPool2d(2)                          # 均值版:背景/纹理类任务更平滑
gap = torch.nn.AdaptiveAvgPool2d(1)                  # 全局平均池化: 任意 H,W → (B,64,1,1)
# Adaptive 版不用算尺寸:直接指定"输出想要多大",分类头前标配

代码解读

  • MaxPool 保留窗口内最大响应 → 对微小平移/形变不敏感(平移不变性的来源);
  • 现代 CNN(ResNet 之后)倾向用 stride=2 卷积做下采样:可学习、信息损失少;池化留给收尾(GAP);
  • AdaptiveAvgPool2d(1) 把任意尺寸特征图压成每通道一个数 → 接分类头,免去尺寸对齐烦恼。

示意图 :2×2 取 max 的过程见 formula_pool.png(红底=被选中的最大值)

图形


第七章 序列模型与注意力

7.1 RNN 递推

公式

h t = tanh ⁡ ( W h h   h t − 1 + W x h   x t + b ) , y ^ t = W h y   h t + b y h_t = \tanh\left(W_{hh}\, h_{t-1} + W_{xh}\, x_t + b\right), \qquad \hat{y}t = W{hy}\, h_t + b_y ht=tanh(Whhht−1+Wxhxt+b),y^t=Whyht+by

符号说明 : h t h_t ht 是"到 t t t 为止的摘要";参数在所有时间步共享;反向传播时 Jacobian 连乘导致长程梯度消失/爆炸。

应用场景:短序列基线;教学与理解序列建模的起点(实战多用 LSTM/Transformer)。

PyTorch

python 复制代码
rnn = torch.nn.RNN(input_size=128,      # 每个时间步输入 x_t 的维度
                   hidden_size=256,     # 隐藏态 h_t 的维度
                   batch_first=True)    # ⚠ 默认 False! 设为 True 才能用直观的 (B,T,·) 顺序
x = torch.randn(32, 10, 128)            # (B=32 条序列, T=10 步, 每步 128 维)
h_all, h_last = rnn(x)                  # h_all: (32,10,256) 每步的 h_t; h_last: (1,32,256) 末步
# h_last 的层维=1(单层); num_layers=2 时 h_all 不变、h_last 变 (2,32,256)

代码解读

  • batch_first 是 PyTorch RNN 系第一坑:默认 (T,B,·) 是历史遗留,忘了设就要到处 transpose;
  • 两个返回值的分工:序列标注任务用 h_all(每步都要预测),整句分类用 h_last(末步摘要);
  • 变长序列需配 pack_padded_sequence 防止 padding 位置污染隐藏态(进阶)。

示意图

复制代码
x₀ ─▶ [RNN] ─▶ x₁ ─▶ [RNN] ─▶ x₂ ─▶ [RNN]     同一个 rnn 模块被"展开"复用 T 次
        │            │            │            (参数共享 = 公式中的 W 不随 t 变)
        ▼            ▼            ▼
       h₀ ────────▶ h₁ ────────▶ h₂            h_all = [h₀,h₁,h₂]  h_last = h₂

图形

复制代码
x₀ ─▶ [RNN] ─▶ x₁ ─▶ [RNN] ─▶ x₂ ─▶ [RNN] ─▶ ...
        │            │            │
        ▼            ▼            ▼
       h₀ ────────▶ h₁ ────────▶ h₂        h_t 携带"至今为止"的信息(参数共享)

7.2 LSTM

公式

f t = σ ( W f h t − 1 , x t + b f ) 遗忘门 i t = σ ( W i h t − 1 , x t + b i ) 输入门 c ~ t = tanh ⁡ ( W c h t − 1 , x t + b c ) 候选记忆 o t = σ ( W o h t − 1 , x t + b o ) 输出门 c t = f t ⊙ c t − 1 + i t ⊙ c ~ t 细胞状态更新 h t = o t ⊙ tanh ⁡ ( c t ) 隐藏态输出 \begin{aligned} f_t &= \sigma(W_f h_{t-1}, x_t + b_f) & &\text{遗忘门} \\ i_t &= \sigma(W_i h_{t-1}, x_t + b_i) & &\text{输入门} \\ \tilde{c}t &= \tanh(W_c h_{t-1}, x_t + b_c) & &\text{候选记忆} \\ o_t &= \sigma(W_o h_{t-1}, x_t + b_o) & &\text{输出门} \\ c_t &= f_t \odot c{t-1} + i_t \odot \tilde{c}_t & &\text{细胞状态更新} \\ h_t &= o_t \odot \tanh(c_t) & &\text{隐藏态输出} \end{aligned} ftitc~totctht=σ(Wfht−1,xt+bf)=σ(Wiht−1,xt+bi)=tanh(Wcht−1,xt+bc)=σ(Woht−1,xt+bo)=ft⊙ct−1+it⊙c~t=ot⊙tanh(ct)遗忘门输入门候选记忆输出门细胞状态更新隐藏态输出

符号说明 ⋅ , ⋅ \\cdot,\\cdot ⋅,⋅ 为向量拼接; c t c_t ct 只经历逐元素加法与门控乘法 → 梯度可长距离无衰减流动("梯度高速公路"),解决 RNN 长程依赖问题。

应用场景:语音、时序预测、机器翻译(前 Transformer 时代主力);仍广泛用于轻量时序任务。

PyTorch

python 复制代码
lstm = torch.nn.LSTM(input_size=128, hidden_size=256,
                     num_layers=2,          # 堆叠 2 层: 第2层的输入是第1层的 out 序列
                     batch_first=True)
x = torch.randn(32, 10, 128)
out, (h_n, c_n) = lstm(x)
# out : (32,10,256)  = 最后一层每步的 h_t
# h_n : (2,32,256)   = 每层的末步 h_T(层维在前!)
# c_n : (2,32,256)   = 每层的末步细胞状态 c_T(LSTM 独有,RNN 没有第二个返回值)
# 四组门 (f,i,g,o) 的 W,b 全部封装在 lstm.parameters() 里,共 4×2 层份

代码解读

  • 解包 (h_n, c_n) 是 LSTM 的签名特征:细胞状态 c 是"长期记忆线",h 是"对外输出";
  • 多层时 out 只来自最顶层;取中间层状态用 h_n[0](第 1 层末步);
  • 双向 LSTM 加 bidirectional=True:out 的隐藏维翻倍(正反向拼接),h_n 层维也翻倍。

示意图 :门控信息流(f 门遗忘 / i⊙g 写入 / o 门读出)见 formula_lstm.png

图形

7.3 缩放点积注意力

公式

A t t e n t i o n ( Q , K , V ) = s o f t m a x  ⁣ ( Q K ⊤ d k ) V , M u l t i H e a d = C o n c a t ( h e a d 1 , ... , h e a d h )   W O \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{Q K^{\top}}{\sqrt{d_k}}\right) V, \qquad \mathrm{MultiHead} = \mathrm{Concat}(\mathrm{head}_1, \dots, \mathrm{head}_h)\, W^{O} Attention(Q,K,V)=softmax(dk QK⊤)V,MultiHead=Concat(head1,...,headh)WO

符号说明 : h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) \mathrm{head}_i = \mathrm{Attention}(Q W_i^{Q}, K W_i^{K}, V W_i^{V}) headi=Attention(QWiQ,KWiK,VWiV);自注意力中 Q , K , V Q, K, V Q,K,V 由同一输入线性投影; d k \sqrt{d_k} dk 缩放防点积过大使 softmax 饱和;softmax 按行归一化 → 每个查询得到一组和为 1 的权重。

应用场景 :Transformer 全部信息交换:编码器自注意力、解码器因果注意力、交叉注意力;复杂度 O ( T 2 d ) O(T^2 d) O(T2d)。

PyTorch

python 复制代码
import torch.nn.functional as F
# q,k,v: (B, h, T, d_k) ------ 已由 x @ W_q / W_k / W_v 投影得到
out = F.scaled_dot_product_attention(q, k, v, is_causal=True)
# 官方融合内核: 自动选 FlashAttention/mem-efficient 后端
# is_causal=True: 内部加下三角 mask,第 i 个 token 只能看 ≤i(GPT 式自回归)
# 不显式存 T×T 的 scores → 显存 O(T²) 降到 O(T)

# 手写等价版(理解公式用):
scores = q @ k.transpose(-2, -1) / (q.size(-1) ** 0.5)   # QKᵀ/√d_k → (B,h,T,T)
attn = scores.softmax(dim=-1)                            # 每行归一化 = 注意力权重
out = attn @ V                                           # 加权求和 V → (B,h,T,d_v)
# 显式 mask(如 padding): F.scaled_dot_product_attention(q,k,v, attn_mask=mask)

代码解读

  • q.size(-1)**0.5 就是公式的 d k \sqrt{d_k} dk :不除的话 d_k 大时点积方差大 → softmax 一步到位饱和、梯度消失;
  • is_causalattn_mask 不要同时传;mask 约定是"被遮位置加 −inf"(bool mask 则 True=保留,注意版本差异);
  • 多头 = 把 d_model 切成 h 份并行做本公式:nn.MultiheadAttention 或手动 view(B,T,h,d_k).transpose(1,2)

示意图 :完整形状变换链(QKᵀ→scores→weights→@V)见 fig5b_attention.png

图形 :见 fig5b_attention.png

7.4 正弦位置编码

公式

P E ( p o s ,   2 i ) = sin ⁡  ⁣ ( p o s 10000   2 i / d m o d e l ) , P E ( p o s ,   2 i + 1 ) = cos ⁡  ⁣ ( p o s 10000   2 i / d m o d e l ) PE_{(pos,\, 2i)} = \sin\!\left(\frac{pos}{10000^{\,2i/d_{\mathrm{model}}}}\right), \qquad PE_{(pos,\, 2i+1)} = \cos\!\left(\frac{pos}{10000^{\,2i/d_{\mathrm{model}}}}\right) PE(pos,2i)=sin(100002i/dmodelpos),PE(pos,2i+1)=cos(100002i/dmodelpos)

符号说明 : p o s pos pos 为时间步、 i i i 为维度索引;不同频率的正弦波叠加 → 每个位置有唯一"指纹",且任意相对位移可由线性变换表示。

应用场景:原始 Transformer;现代模型多用可学习位置嵌入或 RoPE(旋转位置编码)。

PyTorch

python 复制代码
pos = torch.arange(5000).unsqueeze(1).float()   # (5000,1) 位置列向量: 0..4999
i = torch.arange(0, 512, 2).float()             # (256,) 偶数维索引: 0,2,4,...,510
pe = torch.zeros(5000, 512)                     # 预生成整张位置编码表 (max_len, d_model)
# 广播: (5000,1)/(1,256) → (5000,256),正好填满偶/奇两组维度
pe[:, 0::2] = torch.sin(pos / 10000 ** (i / 512))   # 偶数维 = sin
pe[:, 1::2] = torch.cos(pos / 10000 ** (i / 512))   # 奇数维 = cos(同频率配对)
x = x + pe[:T]                # 与 token 嵌入"相加"(不是拼接): (T,512)+(T,512)
# pe 无梯度需求 → 注册为 buffer: model.register_buffer('pe', pe),随 .to(device) 迁移

代码解读

  • 10000**(i/512) 生成从 1 到 10000 的几何级数波长:低维高频、高维低频(热力图的条纹疏密);
  • 切片赋值 0::2 / 1::2 是"偶/奇交错"的向量化写法,避免循环;
  • 相加而非拼接:保持 d_model 不变,且让注意力能用线性变换感知相对位置(论文性质)。

示意图 :PE 矩阵热力图(左密右疏的正弦条纹)见 graph_pe_heatmap.png

图形:热力图见附录 B.6


第八章 强化学习

8.1 折扣回报

公式

G t = ∑ k = 0 ∞ γ k   r t + k + 1 = r t + 1 + γ   G t + 1 , γ ∈ 0 , 1 G_t = \sum_{k=0}^{\infty} \gamma^{k}\, r_{t+k+1} = r_{t+1} + \gamma\, G_{t+1}, \qquad \gamma \in 0, 1 Gt=k=0∑∞γkrt+k+1=rt+1+γGt+1,γ∈0,1

符号说明 : γ \gamma γ 权衡眼前与长远: γ → 0 \gamma \to 0 γ→0 贪心、 γ → 1 \gamma \to 1 γ→1 远视(常取 0.99 0.99 0.99);递推式是从轨迹反推回报的 O ( T ) O(T) O(T) 算法依据。

应用场景 :一切 RL 的目标定义;REINFORCE 直接用 G t G_t Gt 加权 log ⁡ π \log \pi logπ。

PyTorch

python 复制代码
# rewards: 一条轨迹的即时奖励列表 [r₀, r₁, ..., r_{T-1}]
G, out = 0.0, []                    # G 从轨迹末尾的 0 起步(终止后无未来奖励)
for r in reversed(rewards):         # 从后往前扫:因为 G_t 依赖 G_{t+1}
    G = r + gamma * G               # 递推 G_t = r_t + γ·G_{t+1}(公式右半,O(T) 一遍算完)
    out.insert(0, G)                # 倒着算的,插回表头恢复时间正序
returns = torch.tensor(out)         # (T,):每个时刻的"从此刻看总回报"
# 向量化替代(无循环): 用 torch.cumsum 配合 gamma 的幂,或 flip→cumsum→flip

代码解读

  • 递推方向是关键: G t G_t Gt 需要"未来"信息,所以必须从轨迹尾部倒着算;正序循环做不到 O(T);
  • insert(0,·) 是 O(n²),长轨迹可改 out.append(G)out[::-1] 一次反转;
  • 非终止任务(持续控制)没有自然终点,改用 bootstrapping: G t = r t + γ V ( s t ) G_t = r_t + \gamma V(s_t) Gt=rt+γV(st)(见 8.2)。

示意图

复制代码
时间 →    t=0     t=1     t=2     t=3(末)
r:        r₀      r₁      r₂      r₃
G:        G₀ ◀── G₁ ◀── G₂ ◀── G₃=r₃      反向递推: G_t = r_t + γG_{t+1}
          (γ 每往前一步把未来奖励"折价"一次)

γ k \gamma^k γk 折价曲线见 graph_discount.png

图形 : γ k \gamma^k γk 衰减曲线见附录 B.7

8.2 贝尔曼方程与 TD 误差

公式

V π ( s ) = E r + γ   V π ( s ′ ) , Q ∗ ( s , a ) = E r + γ max ⁡ a ′ Q ∗ ( s ′ , a ′ ) V^{\pi}(s) = \mathbb{E}\big r + \\gamma\\, V\^{\\pi}(s') \\big, \qquad Q^{*}(s, a) = \mathbb{E}\big r + \\gamma \\max_{a'} Q\^{\*}(s', a') \\big Vπ(s)=Er+γVπ(s′),Q∗(s,a)=Er+γa′maxQ∗(s′,a′)

δ t = r t + γ   V ( s t + 1 ) − V ( s t ) \delta_t = r_t + \gamma\, V(s_{t+1}) - V(s_t) δt=rt+γV(st+1)−V(st)

符号说明 : V / Q V/Q V/Q 满足"自洽"递归------当前估计应等于即时奖励加折现的下一步估计;TD 误差 δ t \delta_t δt 度量自洽被破坏的程度,驱动一切 TD 学习。

应用场景 :DQN(用 δ 2 \delta^2 δ2 作损失)、Actor-Critic( δ \delta δ 作优势估计)、GAE( δ \delta δ 的加权累积)。

PyTorch

python 复制代码
# v_net: Critic 网络,输入状态输出 V(s),形状 (B,1)
with torch.no_grad():                       # ⚠ 目标侧 r+γV(s') 必须切断梯度(半梯度 TD)
    target = r + gamma * v_net(s_next).squeeze(-1)   # (B,) 自洽目标 y
delta = target - v_net(s).squeeze(-1)       # (B,) TD 误差 δ_t = y − V(s)
loss = (delta ** 2).mean()                  # 最小化 δ² → 把 V(s) 拉向自洽目标
# 若不加 no_grad: 目标里也含 v_net 参数 → "自己追自己",训练发散(经典错误)

代码解读

  • "半梯度":只对 δ \delta δ 中的 V ( s t ) V(s_t) V(st) 求导,把 V ( s t + 1 ) V(s_{t+1}) V(st+1) 当常数------这是 TD 方法稳定性的数学前提;
  • squeeze(-1) 把 (B,1) 压成 (B,) 与 r 对齐;形状不齐是 RL 代码第二大 bug 源(第一是 no_grad);
  • DQN 只需把 v_net 换成 q_net(...).gather(1,a)、目标侧加 max,其余完全同构。

示意图 :δ 在交互循环与 GAE 时间轴中的位置见 fig6_rl_loop.png

8.3 策略梯度与 PPO-Clip

公式

∇ θ J ( θ ) = E ∇ θ log ⁡ π θ ( a ∣ s )   G t \nabla_{\theta} J(\theta) = \mathbb{E}\big \\nabla_{\\theta} \\log \\pi_{\\theta}(a \\mid s)\\, G_t \\big ∇θJ(θ)=E∇θlogπθ(a∣s)Gt

L C L I P ( θ ) = E min ⁡ ( ρ t A \^ t ,    c l i p ( ρ t ,   1 − ϵ ,   1 + ϵ )   A \^ t ) , ρ t = π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) L^{\mathrm{CLIP}}(\theta) = \mathbb{E}\Big \\min\\big( \\rho_t \\hat{A}_t,\\; \\mathrm{clip}(\\rho_t,\\, 1-\\epsilon,\\, 1+\\epsilon)\\, \\hat{A}_t \\big) \\Big, \qquad \rho_t = \frac{\pi_{\theta}(a_t \mid s_t)}{\pi_{\theta_{\mathrm{old}}}(a_t \mid s_t)} LCLIP(θ)=Emin(ρtA\^t,clip(ρt,1−ϵ,1+ϵ)A\^t),ρt=πθold(at∣st)πθ(at∣st)

符号说明 : A ^ \hat A A^ 为优势函数(常用 GAE 估计); ρ t \rho_t ρt 为新旧策略的重要性采样比;clip 把单次更新幅度限制在 ± ϵ \pm\epsilon ±ϵ(常 0.2 0.2 0.2)内 → 训练稳定。

应用场景:机器人控制、游戏 AI、RLHF(大模型对齐即用 PPO 变种)。

PyTorch

python 复制代码
# new_logp: 当前策略 π_θ 对"旧数据里动作"的 log 概率(本次 forward 算出,有梯度)
# old_logp: 收集数据时旧策略 π_θ_old 记录的 log 概率(常量,无梯度)
ratio = (new_logp - old_logp).exp()     # ρ = π_θ/π_θ_old = exp(log差)(比直接除概率数值稳)
surr1 = ratio * adv                     # 无约束目标: ρ·Â
surr2 = torch.clamp(ratio, 0.8, 1.2) * adv      # clip(ρ, 1−ε, 1+ε)·Â, ε=0.2
loss = -torch.min(surr1, surr2).mean()  # 取 min = 悲观下界;加负号因为优化器只做 minimize
# min 的作用: Â>0 时 ρ 超过 1.2 后梯度为0(不许再抬高好动作概率);Â<0 时对称

代码解读

  • 整个 PPO 的"信任域"就靠 clip 一行实现:无需二阶信息就限制了单步策略变化幅度;
  • adv 要先做标准化 (adv−mean)/std(批内),否则量纲漂移导致裁剪阈值失效;
  • exp 的输入是 log 概率之差,天然避免下溢;ratio 初始≈1,偏离 1 的程度 = 策略已走多远。

示意图 :策略更新在交互循环中的位置见 fig6_rl_loop.png(下半 GAE 时间轴产出 adv)

图形 :交互循环与 GAE 时间轴见 fig6_rl_loop.png;GAE 权重曲线见附录 B.8


第九章 生成模型

9.1 VAE:ELBO 与重参数化

公式

log ⁡ p ( x ) ≥ E q ( z ∣ x ) log ⁡ p ( x ∣ z ) − D K L ( q ( z ∣ x )   ∥   p ( z ) ) (ELBO) \log p(x) \ \ge\ \mathbb{E}{q(z \mid x)}\big \\log p(x \\mid z) \\big - D{\mathrm{KL}}\big( q(z \mid x) \,\|\, p(z) \big) \qquad \text{(ELBO)} logp(x) ≥ Eq(z∣x)logp(x∣z)−DKL(q(z∣x)∥p(z))(ELBO)

z = μ + σ ⊙ ε , ε ∼ N ( 0 , I ) (重参数化) z = \mu + \sigma \odot \varepsilon, \quad \varepsilon \sim \mathcal{N}(0, I) \qquad \text{(重参数化)} z=μ+σ⊙ε,ε∼N(0,I)(重参数化)

D K L ( N ( μ , σ 2 )   ∥   N ( 0 , 1 ) ) = − 1 2 ∑ ( 1 + log ⁡ σ 2 − μ 2 − σ 2 ) D_{\mathrm{KL}}\big(\mathcal{N}(\mu, \sigma^2) \,\|\, \mathcal{N}(0, 1)\big) = -\frac{1}{2}\sum \left( 1 + \log \sigma^{2} - \mu^{2} - \sigma^{2} \right) DKL(N(μ,σ2)∥N(0,1))=−21∑(1+logσ2−μ2−σ2)

符号说明 :ELBO 第一项是重建质量、第二项把后验拉回先验;重参数化把随机性移入 ε \varepsilon ε,使采样操作可导------VAE 能端到端训练的关键。

应用场景:图像生成与插值、表征学习、异常检测(重构误差大 = 异常)。

PyTorch

python 复制代码
mu, logvar = encoder(x)          # 编码器输出两个头: μ 和 log σ²(学 log 值域无约束、更稳)
std = (0.5 * logvar).exp()       # σ = exp(logvar/2)
z = mu + std * torch.randn_like(mu)     # 重参数化: z = μ + σ⊙ε, ε~N(0,I)
# 关键: 随机性全在 ε 里 → z 对 (μ,σ) 是可导函数 → 梯度能穿过"采样"这一步

x_hat = decoder(z)               # 解码器重建 x̂ = p(x|z) 的均值
recon = F.binary_cross_entropy(x_hat, x, reduction='sum')   # −E[log p(x|z)],按样本求和
kl = -0.5 * (1 + logvar - mu.pow(2) - logvar.exp()).sum()   # KL 解析解,无需采样
loss = recon + kl                # = −ELBO;两项量级要平衡(β-VAE 给 kl 乘 β 调权重)

代码解读

  • 若直接 z = Normal(mu,std).sample():采样节点不可导,encoder 收不到梯度------重参数化就是绕开它;
  • randn_like(mu) 保证 ε 与 μ 同形状同设备;每个 batch 重新采样(蒙特卡洛单样本估计已够用);
  • KL 用解析解而非采样估计:方差为零;公式即 − 1 2 ∑ ( 1 + log ⁡ σ 2 − μ 2 − σ 2 ) -\frac12\sum(1+\log\sigma^2-\mu^2-\sigma^2) −21∑(1+logσ2−μ2−σ2) 的逐元素实现;
  • reduction='sum' 与 KL 的 sum 配套(ELBO 是"每样本"的下界);用 mean 会让 KL 相对过强。

示意图

复制代码
x ─▶ encoder ─▶ μ,logσ² ─┬─▶ z = μ+σ⊙ε ─▶ decoder ─▶ x̂ ─▶ recon 损失
                          │      ▲ ε~N(0,I)(梯度走 μ,σ 这条路)
                          └──────────────▶ KL 解析解 ─▶ + ─▶ loss

配图见 fig7a_vae.png

图形 :见 fig7a_vae.png

9.2 GAN:极小极大博弈

公式

min ⁡ G max ⁡ D V ( D , G ) = E x ∼ p d a t a log ⁡ D ( x ) + E z ∼ p z log ⁡ ( 1 − D ( G ( z ) ) ) \min_{G} \max_{D}\ V(D, G) = \mathbb{E}{x \sim p{\mathrm{data}}}\big \\log D(x) \\big + \mathbb{E}{z \sim p{z}}\big \\log\\big(1 - D(G(z))\\big) \\big GminDmax V(D,G)=Ex∼pdatalogD(x)+Ez∼pzlog(1−D(G(z)))

符号说明 : D D D 想把真样本判 1、假样本判 0; G G G 想让 D ( G ( z ) ) → 1 D(G(z)) \to 1 D(G(z))→1;纳什均衡时 D D D 处处输出 0.5 0.5 0.5。

应用场景:高分辨率图像生成(StyleGAN)、图像翻译(pix2pix)、数据增强;训练需交替更新、注意模式崩溃。

PyTorch

python 复制代码
z = torch.randn(64, 100)                    # 64 个噪声向量 z ~ N(0,I)
one, zero = torch.ones(64,1), torch.zeros(64,1)   # 真/假标签

# ---- 第1步 训 D(冻结 G)----
fake = G(z).detach()        # ⚠ detach: 切断通往 G 的梯度,否则训 D 会顺带改 G
d_loss = F.binary_cross_entropy(D(real), one) + F.binary_cross_entropy(D(fake), zero)
opt_D.zero_grad(); d_loss.backward(); opt_D.step()

# ---- 第2步 训 G(冻结 D 的参数,但梯度要"穿过"D 流回 G)----
g_loss = F.binary_cross_entropy(D(G(z)), one)     # 非饱和变体: 让 D 把假图判成真
opt_G.zero_grad(); g_loss.backward(); opt_G.step()
# 注意这里不 detach: 梯度路径 G → D → loss 必须通畅(只是 opt_D 不更新而已)

代码解读

  • 两个优化器各管一家:opt_D 只含 D.parameters()opt_G 只含 G.parameters()
  • detach() 与"冻结"的区别:detach 断计算图(本次反向不流过),冻结是 requires_grad=False(永久);训 D 用 detach 更省事;
  • 原始极小极大里 G 的梯度是 log ⁡ ( 1 − D ( G ( z ) ) ) \log(1-D(G(z))) log(1−D(G(z))),训练初期 D 太强 → 梯度≈0(饱和);非饱和变体 − log ⁡ D ( G ( z ) ) -\log D(G(z)) −logD(G(z)) 梯度健康,是实战标准做法;
  • 交替比例通常 1:1;D 赢太多/输太多都崩,监控两边 loss 与生成样本图。

示意图 :对抗结构与梯度通路见 fig7b_gan.png(造假者 vs 验钞员)

图形 :见 fig7b_gan.png


附录 A:公式 → PyTorch API 速查表

公式 PyTorch API 常用超参
σ ( x ) \sigma(x) σ(x) sigmoid torch.sigmoid / F.binary_cross_entropy_with_logits ---
ReLU / GELU F.relu / F.gelu ---
BatchNorm nn.BatchNorm1d/2d eps=1e-5, momentum=0.1
LayerNorm nn.LayerNorm(d) eps=1e-5
MSE F.mse_loss reduction='mean'
交叉熵 F.cross_entropy(吃 logits) label_smoothing=0~0.1
KL F.kl_div / kl_divergence reduction='batchmean'
SGD+动量 optim.SGD(momentum=0.9) lr=0.01~0.1
Adam / AdamW optim.Adam / optim.AdamW lr=1e-4~1e-3, wd=0.01
余弦调度 lr_scheduler.CosineAnnealingLR T_max=总步数
梯度裁剪 clip_grad_norm_ max_norm=1.0
Dropout nn.Dropout(p) p=0.1~0.5
卷积 nn.Conv2d(k, s, p) same: p=(k−1)/2
池化 nn.MaxPool2d(2) stride=2
RNN / LSTM nn.RNN / nn.LSTM batch_first=True
注意力 F.scaled_dot_product_attention is_causal=True
重参数化 mu + std*eps(手写) ---

附录 B:公式图像集(把公式画成函数曲线/图形)

正文每条公式配有结构示意图 (数据怎么流);本附录补上图像视角 :把公式本身画成函数曲线/图形,看它的形状如何随参数变化。

生成脚本:make_formula_graphs.py

公式 → 图像总览

编号 公式(正文章节) 图像文件 图像告诉你什么
G1 激活函数及导数(1.1--1.3) graph_act_deriv / formula_activations σ ′ \sigma' σ′ 峰值仅 0.25 且两端趋于 0 → 梯度消失;ReLU 正区间导数恒 1
G2 softmax 温度(1.1、7.3) graph_softmax_temp τ↓ 分布尖锐自信,τ↑ 平坦随机
G3 BN/LN 标准化(2.1--2.2) graph_norm_hist 任意分布被拉成 μ=0、σ=1
G4 损失函数族(3.1--3.5) formula_losses MSE 放大大误差;Huber 折中;Focal 压低易分样本
G5 KL 散度(3.6) formula_kl 两分布间阴影面积 ≈ 差异程度(非对称)
G6 权重衰减(5.1) graph_weight_decay 权重按 ( 1 − η λ ) t (1-\eta\lambda)^t (1−ηλ)t 指数收缩向 0
G7 优化器更新式(4.1--4.3) formula_optimizers 窄谷中三条轨迹:SGD 爬行 / 动量加速 / Adam 直达
G8 学习率调度(4.4) formula_lrschedule α 随步数的形状:阶梯 / 余弦 / warmup
G9 卷积(6.1) graph_conv1d / formula_conv 卷积 = 滑动加权求和;差分核提取"变化率"
G10 位置编码(7.4) graph_pe_heatmap 低频→高频正弦条纹 = 每个位置的唯一指纹
G11 折扣回报(8.1) graph_discount γ k \gamma^k γk 衰减:k 步后奖励的"现价",γ=0.99 半衰期≈69 步
G12 GAE 优势估计(8.3) graph_gae_weights ( γ λ ) l (\gamma\lambda)^l (γλ)l 权重:λ 是偏差←→方差的旋钮
G13 Dropout(5.2) formula_dropout 节点随机失活的结构图
G14 LSTM 门控(7.2) formula_lstm 三个门如何控制细胞状态的信息流

B.1 激活函数的导数(公式 1.1--1.3)

读图 :反向传播时梯度要连乘各层导数。 σ ′ ( x ) = σ ( 1 − σ ) \sigma'(x) = \sigma(1-\sigma) σ′(x)=σ(1−σ) 最大只有 0.25 0.25 0.25、 ∣ x ∣ > 5 \lvert x \rvert > 5 ∣x∣>5 后几乎为 0------连乘 10 层就剩 10 − 6 10^{-6} 10−6,这就是梯度消失;ReLU 正区间导数恒为 1,连乘不衰减,因此成为隐藏层默认。

B.2 softmax 的温度 τ(公式 1.1、7.3)

读图 :同一组 logits, τ = 0.5 \tau=0.5 τ=0.5 时最大类概率 0.83(尖锐), τ = 2 \tau=2 τ=2 时降到 0.37(平坦)。知识蒸馏用大 τ 让教师"说出"类间相似度;LLM 采样调 τ 控制创造性。

B.3 归一化前后的分布(公式 2.1--2.2)

读图 :左为某层原始激活( μ = 2 \mu=2 μ=2、 σ = 3 \sigma=3 σ=3),右为 x ^ = ( x − μ ) / σ 2 + ϵ \hat x = (x-\mu)/\sqrt{\sigma^2+\epsilon} x^=(x−μ)/σ2+ϵ 之后------任何分布都被标准化为 μ = 0 \mu=0 μ=0、 σ = 1 \sigma=1 σ=1,各层输入尺度稳定,训练才敢用大学习率。 γ , β \gamma, \beta γ,β 再把它平移缩放回网络需要的形状。

B.4 权重衰减 = 权重指数收缩(公式 5.1)

读图 :更新式 θ ← θ − η ∇ L − η λ θ \theta \leftarrow \theta - \eta\nabla\mathcal{L} - \eta\lambda\theta θ←θ−η∇L−ηλθ 中,即使梯度为 0,权重也每步乘 ( 1 − η λ ) (1-\eta\lambda) (1−ηλ)。 λ = 10 − 2 \lambda=10^{-2} λ=10−2 时 2 万步后权重只剩 13%------大 λ 强行压缩模型容量,小 λ( 10 − 4 10^{-4} 10−4)只做轻微约束。

B.5 一维卷积:滑动加权求和(公式 6.1)

读图 :上为带噪输入信号,下为与差分核 − 0.5 , 0 , 0.5 -0.5, 0, 0.5 −0.5,0,0.5 卷积的输出------正弦变成余弦状(导数),噪声被差分放大。卷积核就是"模板",输出 y i = ∑ m k m   x i + m yi = \sum_m km\,xi+m yi=∑mkmxi+m 即"哪里有这个模式"。

B.6 正弦位置编码热力图(公式 7.4)

读图:行是位置、列是维度。左侧维度频率高(条纹密)、右侧频率低(条纹疏),类似二进制计数尺------任意两行的图案差异随位置距离单调变化,网络据此感知顺序与相对距离。

B.7 折扣因子 γᵏ:未来奖励的"现价"(公式 8.1)

读图 : γ = 0.5 \gamma=0.5 γ=0.5 时 10 步后的奖励只值 0.001(极度短视); γ = 0.99 \gamma=0.99 γ=0.99 时 69 步后仍值一半(远视)。选 γ 就是选"智能体关心多远的未来",episodic 短任务用小 γ,持续控制用 0.99+。

B.8 GAE 的 (γλ)ˡ 权重(公式 8.3)

读图 : A ^ t = ∑ l ( γ λ ) l δ t + l \hat A_t = \sum_l (\gamma\lambda)^l \delta_{t+l} A^t=∑l(γλ)lδt+l 是未来各步 TD 误差的加权和,权重按 ( γ λ ) l (\gamma\lambda)^l (γλ)l 衰减。 λ = 0 \lambda=0 λ=0 只信当前一步 δ(方差小但有偏); λ = 1 \lambda=1 λ=1 把全部未来误差等权累加(无偏但方差大);0.95 是经验甜点位。