跳到主要内容

训练过程:梯度、优化器与学习率

训练把样本和学习目标转化为参数更新。前向计算先给出预测,损失衡量预测与目标的差距,反向传播求出各参数的导数,优化器再决定怎样利用这些导数。四件事各有职责:把 Adam 换成 SGD,不会改变标签的含义,也修不好标错的数据。

《深度学习》的优化章节区分了两件事:把训练目标降下来,以及学到能泛化的预测器。训练损失负责指引更新;验证集负责判断这些更新对没见过的样本是否还有用。

手算一次更新

使用不带截距的模型 y^=wx\hat y=wx,构造两个样本 (1,2),(2,4)(1,2),(2,4)。把损失定义为均方误差的一半:

L(w)=12ni(wxiyi)2,g(w)=1ni(wxiyi)xi.L(w)=\frac{1}{2n}\sum_i(wx_i-y_i)^2, \qquad g(w)=\frac{1}{n}\sum_i(wx_i-y_i)x_i.

w=0w=0 开始,损失为 55,梯度为 5-5。学习率取 η=0.1\eta=0.1,按 wwηgw\leftarrow w-\eta g 更新后得到 w=0.5w=0.5,损失降为 2.81252.8125。负梯度说明在当前位置增大权重能减小误差,却没有保证任意大的增加都有用。

这组数据的损失还可以写成 L(w)=1.25(w2)2L(w)=1.25(w-2)^2。每次更新后,参数到最优点的距离满足:

wt+12=(12.5η)(wt2).w_{t+1}-2=(1-2.5\eta)(w_t-2).

因此,只有 0<η<0.80<\eta<0.8 时距离才会缩小。取 0.80.8 会在最优点两侧等幅振荡,继续增大会发散。这个阈值来自当前二次函数,不能直接当作神经网络的推荐学习率。

x, y = [1.0, 2.0], [2.0, 4.0]
w, learning_rate = 0.0, 0.1
for step in range(50):
gradient = sum((w * a - b) * a for a, b in zip(x, y)) / len(x)
w -= learning_rate * gradient
loss = sum((w * a - b) ** 2 for a, b in zip(x, y)) / (2 * len(x))
assert abs(w - 2.0) < 2e-6
print(round(w, 6), round(loss, 10))

这段标准库代码演示数值更新,没有划分测试集,不能据此判断预测能力。带截距的模型和可交互图示见线性回归

为什么分批读数据

全量梯度每走一步都要遍历所有训练样本。小批量训练只取一部分样本来估计梯度。在适当的随机抽样和平均损失定义下,这个估计指向全数据梯度;但每一步的参数已经变化,实际方向也带有噪声。《动手学深度学习》从这个角度解释随机梯度下降。

一次 step 指一次优化器更新,一次 epoch 指遍历一遍训练集。1,000 个样本、每批 100 个,通常一轮有 10 次更新。若累计四批梯度才更新一次,有效批量和更新次数都会改变。梯度的平均方式也要一致:四个已经取均值的损失直接相加、不再除以四,会把梯度放大四倍。存在依赖批量的层或随机操作时,累计小批量也未必等价于一次大批量。

增大批量有时能让设备更忙,但会增加激活内存,并减少每遍数据中的更新次数。比较训练方案时,只报 epoch 会掩盖这些差异,应同时看处理了多少样本或 token、更新次数、耗时和质量。

优化器记住了什么

SGD 使用当前梯度;动量额外保存过去更新方向的移动平均,减少来回摆动;Adam 保存梯度的一阶、二阶矩估计,并据此调整各参数的步幅。这些状态也占内存。因此,一个推理时放得下的模型,训练时加上梯度、优化器状态和反向传播要用的激活,可能就放不下了。

橙色动量更新轨迹穿过蓝色损失等高线,先上下摆动,再逐渐靠近原点处的最低点。查看清晰大图

蓝线连接损失相同的位置,橙点是每次更新后的参数。这个独立的二维例子使用 f(x₁,x₂) = 0.1x₁² + 2x₂²,学习率为 0.6,动量系数为 0.5。轨迹先沿竖直方向摆动,再逐渐靠近最低点 (0, 0);在同一目标上,普通梯度下降使用这个学习率会发散。图中用的是二次函数的确定性梯度,摆动不来自小批量噪声,也不是上文标量 w 的轨迹。

PyTorch 的 AdamW 定义把权重衰减与自适应梯度更新分开。因此,往损失中加 L2 惩罚,和设置 AdamW 的 weight decay 通常不能互换。比较正则强度前,先说明使用的定义,别直接搬用同一个系数。

优化器也不会自动找到普遍正确的学习率。输入尺度、初始化、批量组成和损失的平均方式都会影响合适的范围。Warmup 在训练初期逐渐提高学习率,衰减计划在后期逐渐降低。它们控制的是更新幅度,不能证明模型已经学会目标任务。

怎样读出训练失败

训练损失和验证损失都很高时,可以先尝试记住一个很小、标签干净的子集。如果连这也做不到,优先排查标签、梯度是否连通、损失定义和步长。小子集能拟合、全量数据不行,再考虑容量和优化预算。

训练持续变好、验证开始变差,说明新增更新正在拟合无法迁移的差异。调整正则化之前,先检查重复样本、同一用户或同一文档是否跨越了数据划分。用验证集选停止时机和超参数;每轮都看测试集,测试集实际上就成了另一份验证集。数据划分与泄漏进一步说明这条边界。

损失突然出现无穷或非数值时,要检查输入、除法、指数运算、梯度范数和数值精度。梯度裁剪能限制梯度范数,修不好无效标签或缺失值。损失不变也可能只是程序顺序写错:梯度清零太晚,或者意外跨 step 累积。

保存模型与恢复训练

只保存权重,足以再次运行预测。想接着同一条优化轨迹训练,还需要优化器、学习率调度器、更新计数,以及相关随机状态和数据读取位置。权重相同、动量重新置零,下一步仍可能不同。检查点也应附带预处理和标签定义,保证恢复后的模型读到相同的输入表示。

理解这套循环后,预训练与后训练解释目标从哪里来,LoRA 与蒸馏则解释让哪些参数、哪一个模型来学习。

探索关联打开关联网络