损失与稳定性 入门
每一篇深度学习论文都会用到的 4 件工具,但没人专门为它们写一段 —— 「softmax 你肯定知道吧?」—— 初学者不知道,熟手也常记不清细节。 分成两对很自然:softmax 把原始分数变成概率分布、cross-entropy 衡量这个分布有多错 —— 输出和损失。 然后 dropout 通过随机把神经元置零来做正则化、BatchNorm / LayerNorm 把激活的尺度卡住, 让非常深的网络仍然可训 —— 训练稳定性的两块。 这 4 件都长在 Transformer 里。Transformer 之前的最后一篇 primer。
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_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。
交叉熵损失
在几乎所有分类器里都跟 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.00→L = 0。完美预测;损失触底。p_c = 0.95→L ≈ 0.05。又自信又对;损失很小。p_c = 0.50→L ≈ 0.69。不确定;损失有界。p_c = 0.10→L ≈ 2.30。又自信又错;损失很大。p_c → 0→L → ∞。模型给真实答案的概率是 0 —— 无限处罚。 别这么干。
「自信地对」和「自信地错」之间的非对称,就是 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) —— 目标的熵 + 从 y 到 p 的 KL 散度。one-hot 标签里 H(y) = 0,所以最小化交叉熵就是最小化预测和答案之间的 KL 散度。(软标签 —— 蒸馏、label smoothing —— 里两项都要算, 你真正关心的是 KL。)同时,这也是为什么最小化交叉熵 等价于在 categorical 模型下做极大似然估计。
在 Transformer 里:一个语言模型的全部训练目标就是 「下一 token 的交叉熵,在每个序列里的每个位置上,在每个 batch 里的每个序列上, 在整个数据集上,全部加起来」。万亿个 token,每一个 token 贡献一项交叉熵。 GPT-3、GPT-4、Claude、Gemini、LLaMA —— 它们的损失函数,本质上就这一行。 架构再花哨,损失就是这么一句。
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。
这套看起来荒谬的做法为什么能正则:
- 打破共适应(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。
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 接管。
为什么这一招那么有用:
- 激活始终有界。无论训练中权重怎么漂,下一个归一化层都会把激活 拉回到一个受控的尺度上。深网络「信号爆炸」那种失败模式被直接消灭。
- 可以用更大的学习率。没有归一化时,一次激进的更新会冲过头, 把激活变成 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 块的第一行都是归一化层。