优化器与学习率
梯度下降只指出哪个方向是下坡。走多远、要不要参考过去那一百步,由优化器和学习率决定。以及为什么 SGD 让位给了 Adam。
梯度下降告诉你哪个方向能降低损失。它不告诉你步子该迈多大,也不说该不该信这一步——考虑到前面那一百步。这些决定属于优化器和它最重要的那个旋钮——学习率。而这两样设错,浪费掉的训练比任何其他单一原因都多。
学习率:最重要的那个旋钮
学习率给每次更新定尺度。梯度说「往这边走」,学习率说「走这么远」。
失效模式分布在它的两侧:
- 太高。 步子会冲过最小值,在山谷里来回弹跳甚至彻底发散——损失飙上去,变成一团垃圾。这是最常见的训练失败。
- 太低。 训练爬得极慢。最终也能到,但你要为此烧掉算力,而且它可能在还远没到好解的时候,就卡在某个浅浅的地方不动了。
没有普遍正确的取值,因为它取决于损失地形、模型和数据。这就是为什么学习率是任何人第一个动手调的超参数,也是为什么微调需要的值比预训练小得多——你是在轻推一个本来就不错的模型,不是在从零凿一个出来,步子大了会把它已有的知识毁掉。
调度:让步子随时间变化
固定学习率很少是最优的。前期你想要大步子尽快铺开,后期你想要小步子精准落位。所以学习率要按调度变化。
预热。 从极小开始,在最开始的若干步里逐步升上去。早期的梯度噪声很大,一开始就迈一大步,可能把一个还没训练的模型甩到再也回不来的地方。预热避免的就是这个。
衰减。 预热之后,逐步缩小学习率——线性缩,或者按余弦曲线缩——让模型在接近好区域时步子更细。
这条曲线的形状本身也是要调的。要记住的直觉:大步探索,小步收敛。
动量:记住你原本要去哪
朴素的梯度下降没有记忆——每一步只用当前的梯度。这让它抖得厉害。来自小批量的梯度带噪声,于是它走成 Z 字形。
动量通过累积过去梯度的滑动平均来解决这个问题,就像一颗滚下坡的球:方向一致时不断加速,遇到小坑洼能直接滑过去。一直指向同一方向的分量被加速,来回翻面的分量相互抵消。结果是沿真正坡向的进展更快、更顺。
从 SGD 到 Adam
这条演进线索值得搞懂,因为它解释了某个优化器为什么会成为默认选择。
SGD(随机梯度下降,stochastic gradient descent)。 基线做法:沿梯度方向走,步长由学习率缩放,每次用一小批随机样本。加上动量以后它表现不错——但它对所有参数只用同一个学习率,而且非常依赖这个学习率调得准。
问题在于:不同的参数需要不同的步长。一个很少收到强梯度的参数,在终于收到时应该迈得大一些;一个一直被猛推的参数则应该迈小一些。单一的全局学习率做不到两头兼顾。
自适应方法给每个参数各自的有效步长,依据是它见过的梯度历史。梯度大而频繁的参数被压下来,安静的参数被放大。
Adam 把两个好想法合在一起:动量(梯度的滑动平均)加上逐参数的自适应缩放(梯度幅值的滑动平均)。回报很实际——它在很多问题上都还算好用,所需的学习率调优远少于 SGD。正是这种稳健性让它成为训练大模型的默认选择,因为在那里,每一次失败的训练都极其昂贵。
Adam 并不是严格意义上更好——调得好的带动量 SGD 在某些任务上泛化一样好甚至更好。Adam 赢在不需要被调到完美,而这恰恰在你负担不起多次尝试的时候最关键。
读懂训练曲线
优化器和学习率的问题都会在损失曲线上显形,学会读它能省下不少次训练:
- 损失爆炸或变成 NaN → 学习率太高,调小
- 损失几乎不动 → 太低,或者梯度上游有 bug
- 损失反复尖峰又恢复 → 学习率略高,或者批次太小、噪声太大
- 损失很早就进入平台期 → 可能需要改调度,也可能你已经撞到模型或数据的上限
调参的大部分工作是从这条曲线做诊断,而不是讲理论。
要点回顾
- 学习率给每一步定尺度——太高会发散,太低会爬行,是第一个要调的
- 调度(先预热后衰减)让前期有大步探索,临近收敛时用细步
- 动量对过去梯度做平均,抹平噪声,加速方向一致的分量
- Adam 把动量和逐参数的自适应步长结合起来;它赢在所需调优少得多,而这在训练昂贵时很要紧
- 从损失曲线做诊断——爆炸说明学习率太高,纹丝不动说明太低或有 bug
下一篇: 正则化