损失与稳定性 入门

每一篇深度学习论文都会用到的 4 件工具,但没人专门为它们写一段 —— 「softmax 你肯定知道吧?」—— 初学者不知道,熟手也常记不清细节。 分成两对很自然:softmax 把原始分数变成概率分布、cross-entropy 衡量这个分布有多错 —— 输出和损失。 然后 dropout 通过随机把神经元置零来做正则化、BatchNorm / LayerNorm 把激活的尺度卡住, 让非常深的网络仍然可训 —— 训练稳定性的两块。 这 4 件都长在 Transformer 里。Transformer 之前的最后一篇 primer。

01

Softmax

任意实数向量 → 一个干净的概率分布。

神经网络的输出层吐出的是一堆原始实数 —— 可以是负的、可以大于 1、量级随意。 要把它当成概率分布用(分类要、attention 要、下一个 token 预测要), 就得把它变成一个「全是正的、总和为 1」的向量。这件事的标准工具就是softmax

数学就一行:

softmax(z)_i = exp(z_i) / Σ_j exp(z_j)

每一项先 exp 一下让它变正,再除以总和让它加起来等于 1。配方就这些。

具体例子:分数 z = (2.0, 1.0, 0.5)

  exp(z)     =  (7.39,  2.72,  1.65)
  exp(z) 总和 =   11.76
  softmax(z) =  (0.628, 0.231, 0.141)   ← 总和 1.000
softmax(z)_i = exp(z_i) / Σ exp(z_j)分数 z2.0class 11.0class 20.5class 3三个原始分数 z = (2.0, 1.0, 0.5)。总和没限制。
1 / 3
Softmax:拿一个任意实数向量,变成一个干净的概率分布。越大的分数,指数级地分到越大的份额。

有三个性质让 softmax 成为「从向量到分布」类映射里的默认选择:

  • 保序。如果 z_a > z_b,那么 softmax(z)_a > softmax(z)_b。分数的 argmax 也是概率的 argmax —— softmax 是「可以反向传播的 argmax」。
  • 可微。纯 argmax 的梯度处处为 0 —— 反向传播完全用不了。 softmax 是光滑的,所以优化器能一点点把概率质量从一个类挪到另一个类。
  • 指数分离。分数越大,占的概率份额就指数级地越大。 比其它分数高 2 个单位的项,大概拿走 7 倍多的概率;高 5 个单位,大概 150 倍。 这是「赢家拿大头」,但不是「赢家通吃」。

softmax 还有一个大多数入门资料会跳过的旋钮:温度。 把 softmax(z) 换成 softmax(z / T),T > 0 是任意正数,分布的形状就会变。T → 0 把分布 锐化向 one-hot(最大值吃掉一切);T → ∞ 把分布拍平成均匀(每个类一样)。 默认 T = 1。在 LLM 采样里,温度就是那个「控制模型多有创意」的旋钮 —— 高温度给奇怪、出人意料的 token,低温度每次都给最显然的那个。

实战提醒:数值稳定性exp(z_i) 对中等大小的 z_i 就会溢出(float32 大概 700)。诀窍:在 exp 之前先把所有的 z_i 都减去 max(z)。结果完全一样(常数在比值里抵消了), 但不会有任何一项爆掉。所有真实框架都帮你这么做 —— 但如果你哪天要自己手写一个 softmax, 记得减一下。

在 Transformer 里:softmax 出现在两个地方,都很关键。Attention 权重:把 query 和 key 的原始点积分数变成一个「在所有位置上」的 分布,让模型把固定的注意力预算分配到整个序列上。输出端:最后一层线性层把隐藏状态映射到大约 5 万个原始分数(整个词表), softmax 把它们变成下一个 token 的概率。模型字面意义上, 在它认识的每一个 token 上,概率加起来等于 1。

02

交叉熵损失

在几乎所有分类器里都跟 softmax 配对的那个损失。

softmax 把分数变成分布。交叉熵把「你的分布 vs 正确答案」变成一个数 —— 损失。 两者一搭,就是几乎所有「带离散标签」的论文都在用的标准分类配方:ImageNet 分类、 语言模型、Transformer 里的下一个 token 预测。

公式,对一个真实类别为 c 的训练样本:

L = −log(p_c)

这里 p_c 是模型给正确类别打的 softmax 概率。整个损失就是「对一个概率取一次 log」。 通用形式 L = −Σ_i y_i log(p_i)(y 是 one-hot)化简到这同一个式子上 —— 因为除真实类别外所有项都被 0 抵消了。

从图上读 −log(p):

  • p_c = 1.00L = 0。完美预测;损失触底。
  • p_c = 0.95L ≈ 0.05。又自信又对;损失很小。
  • p_c = 0.50L ≈ 0.69。不确定;损失有界。
  • p_c = 0.10L ≈ 2.30。又自信又错;损失很大。
  • p_c → 0L → ∞。模型给真实答案的概率是 0 —— 无限处罚。 别这么干。
L = −log(p_target)损失 L给真实类别的概率 p01230.250.500.751.00交叉熵的形状:L = −log(p)。p = 1 → L = 0;p → 0 → L → ∞。
1 / 4
交叉熵就是 −log 曲线在「你给真实类别的概率」上的高度。「自信地错」要比「不确定」被罚得重得多。

「自信地对」「自信地错」之间的非对称,就是 log 存在的 全部理由。平方误差没这种非对称 —— 偏差 0.5,无论真实答案是 0 还是 1,都罚 0.25。 交叉熵非常在意你是「往哪一边错」,这给优化器一个清晰信号:模型「自信地错」的时候, 梯度恰好最大 —— 模型最迷糊的地方,正好是梯度最强的地方。 所以几乎所有分类器都用交叉熵、而不是对概率用 MSE。

一份数学上的额外礼物:把 softmax 和交叉熵复合起来,再对 softmax 之前的 logits 求导, 答案神奇地化简成 ∂L/∂z_i = p_i − y_i。预测减真值 —— 就这。 softmax 导数那一套复杂表达,加上 −log 那边的链式法则,全都互相抵消掉了。 所有深度学习框架都利用这一点,把两步合并成一个融合算子 ("softmax cross-entropy with logits"),数值上更稳、速度也大约快一倍 —— 比起「先算 softmax 再 −log」要好得多。

信息论上的来源。完整的 −Σ y_i log p_i 是目标分布 y 和 预测分布 p 之间的交叉熵 H(y, p)。它等于 H(y) + KL(y ‖ p) —— 目标的熵 + 从 yp 的 KL 散度。one-hot 标签里 H(y) = 0,所以最小化交叉熵就是最小化预测和答案之间的 KL 散度。(软标签 —— 蒸馏、label smoothing —— 里两项都要算, 你真正关心的是 KL。)同时,这也是为什么最小化交叉熵 等价于在 categorical 模型下做极大似然估计。

在 Transformer 里:一个语言模型的全部训练目标就是 「下一 token 的交叉熵,在每个序列里的每个位置上,在每个 batch 里的每个序列上, 在整个数据集上,全部加起来」。万亿个 token,每一个 token 贡献一项交叉熵。 GPT-3、GPT-4、Claude、Gemini、LLaMA —— 它们的损失函数,本质上就这一行。 架构再花哨,损失就是这么一句。

03

Dropout

一种简单粗暴的正则化:随机关掉一半神经元。

网络会过拟合,深的更会。监督学习 primer §5 讲过那些温柔的正则化(权重衰减、早停)。 这一节讲那个粗暴的。第一次听绝对觉得反直觉:训练时每一次前向, 随机把一层里的一半神经元关掉、假装它们不存在。推断时所有神经元照常用。 但这一招效果出奇地好。

机制:

训练时前向(inverted dropout,p = 保留概率):
  m   ~  Bernoulli(p)      ← 每个神经元独立采样
  a'  =  (a · m) / p       ← 丢掉,再把幸存者放大

推断时前向:
  a'  =  a                 ← 不丢、不放大

反向:
  ∂L/∂a  =  (∂L/∂a') · m / p     ← 梯度只从幸存的
                                    那些流回来

1/p 的缩放(「inverted dropout」)让期望激活值跟推断时一致 —— 这样推断时就什么都不用改了。常见的 p:全连接隐藏层一般 p = 0.5, 靠近输入的层 p = 0.9(轻度 dropout),输出层通常不用 dropout。

dropout —— Bernoulli mask + 按 1/keep_prob 放大推断模式 (p = 0)1.2h1 = 1.20.7h2 = 0.71.8h3 = 1.8-0.3h4 = -0.30.9h5 = 0.9推断模式。一个不丢 —— 5 个神经元都按原值开火。
1 / 4
训练时:每次前向丢一组不同的神经元。推断时:一个都不丢。同一个网络,两种模式。

这套看起来荒谬的做法为什么能正则:

  • 打破共适应(co-adaptation)。没有 dropout 时, 神经元 A 可能学会「只在神经元 B 也开火的时候开火」 —— 把特征当作一对去记。 随机把它们之一丢掉,这个策略就崩了。被 dropout 训练足够久之后, 每个神经元都得在「任意随机抽出的队友组合」下也能独立有用。
  • 集成效应。每个随机 mask 定义了一个不同的子网络。 用 dropout 训练,本质上就是在训练「一个指数级巨大的、共享权重的子网络集成」。 推断(不 dropout)是对这个集成的近似平均,这就是它效果好的原因。
  • 纯粹的噪声注入。给激活加随机噪声本身就是一种正则化 —— 阻止网络去精确地记住某些确切模式。就算你不接受「集成」那个解释, 至少这一层是肯定成立的。

一些「除非被坑过否则没人会跟你说」的细节。(1) Dropout 在训练时启用; 推断前忘了把模型切到 eval 模式,是经典 bug。(2) Dropout 打破了「给定输入, 激活是确定的」这个假设 —— 训练里同一个输入每次预测都会变。 (3) Dropout 跟 BatchNorm(§4)合不来 —— 一起用会带来训练/推断之间的 「方差偏移」。常用的补救:dropout 放在 BatchNorm 后面, 或者干脆有了 BatchNorm 就不用 dropout 了。

在现代非常大的模型里,dropout 略微失宠了。数据足够多、模型有别的正则化来源 (权重衰减、数据增强、规模本身),dropout 的边际收益就缩小。但 Transformer 代码里 它仍然到处都是 —— attention dropout、FFN dropout、residual dropout —— 只是概率小 (像 p = 0.1)。ImageNet 时代那种 50% 现在很少见了。

在 Transformer 里:通常有三个位置,概率都很小(0.0 – 0.1)。Attention dropout —— 加在 attention 的 softmax 输出上,在跟 value 相乘之前。FFN dropout —— 加在 FFN 子层的隐藏激活上。Residual dropout —— 加在每个子层输出上,在残差求和之前。dropout 率是最常被调的超参之一, 在不需要额外正则化的大模型里,常常一路调到 0。

04

BatchNorm 与 LayerNorm

把激活按均值 0、标准差 1 重置一次的那个层 —— 每一步、每一层。

反向传播 primer §3 讲过一件清楚的事:深度网络里,逐层局部导数相乘要么消失要么爆炸。 对付这种漂移最有力的武器,就是在每个「线性层」和下一个之间塞进一个归一化层。 BatchNorm(2015)是第一个被广泛使用的配方;LayerNorm(2016)是 Transformer 的最爱。 两者都是:算均值和方差、减均值、除以标准差、再乘一个可学的 scale、加一个可学的 bias。

同一个配方,只是看你沿哪个轴算:

给一个激活张量 x:
  μ      =  mean(x, axis)
  σ      =  std (x, axis)
  x_hat  =  (x − μ) / (σ + ε)
  y      =  γ · x_hat + β        ← γ、β 是可学的

人话版:
  1. 减均值
  2. 除以标准差
  3. 乘一个可学的 scale,加一个可学的 bias

BatchNorm 和 LayerNorm 的差别就一行:你沿哪根轴算 μσ

  • BatchNorm —— 沿 batch 维度,对每一个特征做归一化。 对形状为 [batch, features] 的激活,均值和方差是「沿每一列」算的。 每个特征在 batch 维度上得到均值 0、标准差 1。 需要一个有意义的 batch size 来估计统计量;诞生地:卷积网络。
  • LayerNorm —— 对每个样本,沿它自己的特征做归一化。 对 [batch, features],均值和方差是「沿每一行」算的。 每个样本在 它自己的特征维度上得到均值 0、标准差 1。 完全不依赖 batch size;诞生地:RNN,后来被 Transformer 接管。
BatchNorm:沿每一列。 LayerNorm:沿每一行。原始激活特征 →↓ batch2.00-1.500.503.000.501.002.50-0.50-1.003.001.001.501.50-0.50-1.002.004 样本 × 4 特征。数值没受控 —— 这就是问题。
1 / 3
同一份激活,两种归一化方向。BatchNorm 需要一个有意义的 batch(CNN 喜欢它);LayerNorm 不需要(所以 Transformer 用它)。

为什么这一招那么有用:

  • 激活始终有界。无论训练中权重怎么漂,下一个归一化层都会把激活 拉回到一个受控的尺度上。深网络「信号爆炸」那种失败模式被直接消灭。
  • 可以用更大的学习率。没有归一化时,一次激进的更新会冲过头, 把激活变成 NaN。有归一化时,下一个归一化层吸收掉这种破坏、再缩放回来。
  • 梯度流得更好。归一化把损失地形改造成一条更好走的下坡 —— 经验上观察得到、理论上仍有争论。(最初的解释 ——「降低 internal covariate shift」—— 学界有部分质疑,但经验上的胜利是没争议的。)
  • 轻度的正则化。BatchNorm 尤其会给每个样本的激活注入「依赖 batch 的 噪声」(因为它的 μ, σ 取决于这个 batch 里还有谁)。这点噪声虽小, 但是稳定的正则化效应。

踩坑提醒。BatchNorm 在训练时(用本 batch 的统计)和评估时(用训练时统计的滑动平均)行为不同。忘了切 eval 模式,可能会在 batch size = 1 的时候看到诡异的预测不稳定。 BatchNorm 还会被很小的 batch 难住(统计估计噪声大), 在 batch 维度被序列长度方差挤满的序列模型里也很尴尬。LayerNorm 完全绕开了这些 —— 它从不依赖 batch,所以训练和评估完全一致。

值得一提:RMSNorm。LayerNorm 的一种小型简化版, 干脆不减均值、只除以输入的均方根(RMS)。略快、参数略少(没有 β), 在 Transformer 上经验效果跟 LayerNorm 一样好。LLaMA、Mistral 以及大部分现代 开源权重的 LLM,用的都是 RMSNorm 而不是 LayerNorm。 原理完全一致,实现少了两个操作。

在 Transformer 里:每个子层外面都有一个归一化层 (LayerNorm 或 RMSNorm)。现代 Transformer 用「pre-LN」模式:y = x + Sublayer(Norm(x)) —— 先归一化、再做子层(attention 或 FFN)、 再加到残差上。这个模式让 50 层以上的非常深的网络也能从零训得起来, 不需要什么 exotic 的训练调度。最早的 2017 Transformer 用的是「post-LN」 (y = Norm(x + Sublayer(x)));几年内 pre-LN 就接管了, 因为它明显更稳。打开任何现代 LLM 的源码,每个 Transformer 块的第一行都是归一化层。