P5-7.1 optimizer 的角色¶
Section ID:
P5-7.1Version:v2026.07.20
在 P5-6 章里,我们已经区分了学习循环、step/batch/epoch、学习(learning)与模型执行(inference),以及训练模式(training mode)与评估模式(evaluation mode)。走到这里,接下来就会留下一个非常直接的问题:既然已经把模型出错这件事算成了数字,那么模型内部的真实数字到底是在什么地方改变的?
损失也算了,gradient 也求了,那么到底是谁真正去改权重?
承担这个角色的,就是优化器(optimizer)。
优化器是接收反向传播算出的 gradient,并把参数真实更新到更可能减小损失方向上的规则。换句话说,前面的计算会告诉我们朝这边改,损失可能会下降,而 optimizer 则把这个结果继续变成那么这一 step 里就把权重这样改这样的真实调整。
如果后面又开始把 loss、gradient、update 的角色混在一起,更适合回到英文概念词汇表里的 optimizer 条目,重新按角色拆开。
如果把一次学习 step 非常粗略地说出来,模型会先做出预测,再计算这个预测错了多少,再计算这种错误与哪些权重有关,最后才真正改动权重数值。这里最后一个阶段,就是 optimizer 的位置。
读这条流程时,最好先抓住下面三句话。
- 损失会把错误变成数字。
- 反向传播会计算每个权重该往哪个方向改。
- 优化器会把这个信号变成真实更新。
optimizer 负责 update 的问题¶
- optimizer 在学习过程里处在什么位置?
- 损失函数、反向传播、优化器之间分别承担什么角色?
- 为什么只有
算出了 gradient还不够,还必须另外有更新规则? - 如果不把 optimizer 看成某个实现函数,而看成
真实改动参数的角色,应该先看什么?
这一节专注于闭合一个问题:到底是谁真正去改参数? 也就是说,这里先把计算错误、计算 gradient、把 gradient 变成真实 update这三个阶段分开来读。只有这条区分先清楚,下一节再看 learning rate 或 Adam 时,才不容易把不同层位的问题混成一团。
同时,这一节不会马上扩大的问题也要明确。同样的 gradient 在不同 learning rate 下为什么会出现不同步幅,会在下一节 P5-7.2 继续说明;Adam 这类自适应 optimizer 试图补什么,会在 P5-7.3 再说明;adaptive optimization 的收敛分析则会被单独放到 P5-7.4 补充学习。
gradient 与 update 规则的判断标准¶
- 能把 optimizer 解释成
把 gradient 变成真实 update 的规则。 - 能区分损失函数、反向传播、优化器分别结束的是哪一个阶段。
- 能解释为什么
算出了 gradient和参数真的变了不是同一句话。 - 能通过一个很小的 Python 例子确认:gradient、update、参数变化本来就是不同步骤。
optimizer 在学习过程里的什么位置¶
如果把 Part 5 前面的学习流程重新绑起来,深度学习训练通常会按下面顺序进行。
- 用前向传播(forward pass)计算预测。
- 用损失函数(loss function)把错误变成数字。
- 用反向传播(backpropagation)计算 gradient。
- 由优化器(optimizer)更新参数。
也就是说,optimizer 不是负责计算 gradient 的装置,而是看着已经算出来的 gradient,决定下一个参数该怎么改的装置。更直接地说,损失函数和反向传播负责计算怎样改会更好,而 optimizer 则把这个结果继续变成这一步到底实际改多少。
如果把这个流程画得非常简单,就是下面这样。
flowchart TD
A["forward pass<br/>用当前参数做预测"]
B["loss<br/>把误差变成数字"]
C["backpropagation<br/>计算梯度"]
D["optimizer<br/>把梯度变成 update"]
E["参数更新被应用<br/>权重真正发生变化"]
A --> B
B --> C
C --> D
D --> E
B -. "哪里出错了" .-> C
C -. "该朝哪个方向改" .-> D
D -. "把算出来的结果作为真实移动应用" .-> E
只有先抓住这个区分,读训练代码时才不容易把不同问题捏成一步。看损失函数时,先问的是哪里错了、错了多少;看反向传播时,先问的是这个错误会把怎样的方向信号传给各个参数;看 optimizer 时,先问的是这个信号有没有真的被变成 update,并应用到了参数上。
如果把这三段混在一起读,就很容易把loss 已经算出来了、gradient 已经出来了、模型已经更新了读成同一个意思。但真实训练里,它们承担的是三种不同角色。因此更安全的阅读标准,是先把它们分成:测错误、算责任、实际移动参数。
- 损失函数:把哪里错了表达成数字
- 反向传播:算出谁对这个错误负多少责任
- 优化器:决定这次到底实际改多少
这三句话,就是 Part 5 里学习计算流程的最小地图。与其把术语分开死记,不如先按错误 -> 责任 -> 真实修改这个顺序绑起来。只要这条顺序固定,之后在代码里看到 loss、backward、step 时,也更容易判断它们各自结束的是哪一段。
为什么只有 gradient 还不够¶
gradient 提供的是方向(direction)信息。它通常告诉我们:朝哪边走,损失更可能下降。 只靠这个信号,已经能说明学习不是瞎猜。但在真实 update 里,只知道方向仍然不够。
原因很简单。真正改参数,不只是看到了箭头,而是还要决定:沿着这个箭头走多远、以什么方式走。地图告诉你往这边下坡,并不等于你已经自动决定好每一步要迈多大。即便是同一个 gradient,有时你几乎不动,有时能用比较合适的步幅靠近目标,有时也可能一下走得太大,直接越过更好的位置。也就是说,gradient 只是下坡方向信号,而真实学习还需要一条规则,把这个方向信号变成真实移动。
因此还会留下下面这些问题。
- 一次到底要走多大?
- 要不要参考前一步移动时的方向?
- 不同坐标是不是应该用不同速度?
这些问题都说明:在知道方向和真实更新之间,还多隔了一层。也就是说,gradient 更像地图,optimizer 更像移动规则。
用一句话压缩就是:
如果 gradient 是方向标,optimizer 就是在决定:要以多快、用什么方式沿着这个方向真的走出去。
把它换成更直白的语言,就是下面三句。
- gradient 会告诉你
该朝哪边走 - optimizer 会决定
这个方向要怎样变成真实 update,以及这次到底走多大 - 所以即使 gradient 相同,只要 optimizer 规则不同,实际学习的样子也可能不同
读 optimizer 时,到底要把什么看成 update¶
初学者最常见的误解之一,就是把已经算出 gradient和模型已经变了读成同一句话。但真实流程里,中间还隔着一步。
- 先在当前参数位置上计算 gradient。
- optimizer 读这个 gradient,做出 update。
- 把这个 update 反映到参数上。
如果慢慢读这条顺序,就会看到:optimizer 的工作不是把 gradient 原样传过去,而是把 gradient 变成可以真实应用到参数上的移动量。 gradient 还是一个计算结果,它只是在说:哪边更像下降方向。update 则是在这个结果之上,再次变成:这一 step 到底真实移动多少。 之后参数反映,则是这个移动量真正加到或减到权重数值上,使模型内部数字发生变化的步骤。
如果把这三件事压成一句话,就是:方向信号、移动量计算、真实数字改变。
也就是说,gradient 仍然只是方向和强度的信号,而 update 才是真正会应用到参数上的移动量。 即使出现同样的 -16.0,也不能把它直接读成权重就会马上改 -16.0。它仍然必须经过 optimizer 的规则,被重新解释成这一步里的真实移动量。
如果忽略这条区分,读训练日志或代码时就会把阶段混在一起。gradient 算得很好只说明方向信号已经出来了,而update 已经应用才说明这个信号真的被变成参数变化。因此,看 optimizer 时不要停在有没有 gradient,而要继续看:这个 gradient 最后变成了什么 update 值,并且这个 update 是否真的被反映了。
这条区分还会影响我们怎么诊断训练慢或不稳定。因为有时 gradient 本身没问题,但 update 太保守;也有时方向是对的,但 update 过于激烈。像这种步幅问题,会在下一节 P5-7.2 里与 learning rate 一起更直接地说明。
案例与示例¶
案例. loss 和 gradient 都算出来了,但 update 还没真的应用¶
读训练代码时,经常会遇到这样一种时刻:模型已经把错了多少算成了数字,也已经算出这些错误该怎样传给各个权重,但权重数值本身还没有变。代码里最典型的场景,就是 loss.backward() 已经结束,但 optimizer.step() 还没被调用的时候。人看到这里时,常会觉得学习几乎已经做完了,但实际上,此时只完成了该怎样改的计算,还不能说真的改了。
用 optimizer 视角重读这个场景,问题会变得更直接。我们不再停在gradient 有没有算出来,而是继续问:这个 gradient 有没有真的被应用成 update? loss 和 gradient 都是计算结果,而 optimizer step 则是把这些结果继续变成模型内部数字变化的最后一步。这个案例里真正要确认的结果,不是做过 backward 了,而是有没有一直走到 optimizer step,并让参数真实变化。
如果把这个场景再拆得更直接一点,大致是下面这样。
| 现在已经算出来的东西 | 还没有发生的东西 |
|---|---|
| 已经有预测值了 | 权重数值真的改变了 |
| 已经算出损失了 | 下一 step 的起始参数已经确定了 |
| 已经算出 gradient 了 | update 已经真实反映到参数上 |
也就是说,有了 loss 和 gradient只说明学习已经算出该往哪里走;optimizer.step() 结束了,才说明这个结果真的变成了模型内部数字变化。本节的中心边界,就在这里。
| 人最容易先看的标准 | 重新用 optimizer 视角读出来的标准 |
|---|---|
| gradient 已经出来了,所以学习应该已经结束了 | gradient 计算和 update 应用是两个不同阶段 |
| loss 打印出来了,所以模型应该已经变好了 | loss 只是状态数字,真实参数变化要由 optimizer 去完成 |
| 只看 backward 就够了 | 参数若要改变,optimizer step 必须真的执行 |
如果把这个案例再压缩一次,最先该读的 optimizer 流程就是下面这样。
flowchart TD
A[反向传播算出 gradient] --> B[optimizer 应用 update 规则]
B --> C[参数真正移动]
C --> D[下一次 forward 与 loss 改变]
B --> E[update 太小]
B --> F[update 比较合适]
B --> G[update 太大]
E --> H[几乎没有移动]
F --> I[更接近目标]
G --> J[越过目标]
这张图不是为了重新讲一遍案例,而是为了把gradient 已经算出来和真实 update 已经应用这两件事再一次一口气分开抓住。至于同一个 gradient 在不同 learning rate 下为什么会变成不同步幅,会在下一节 P5-7.2 继续;Adam 一类方法为什么还会去看最近流向与按坐标调节,则会在 P5-7.3 继续。
练习与例子¶
这一节例子的目标,是把 gradient 计算与真实 update 应用分开来看。这里最重要的,不是马上比较 learning rate 的好坏,而是确认:先算出 gradient,optimizer 再做出 update,之后参数才会改变。 也就是说,这个例子不是在教你哪一个 learning rate 更好,而是在让你看清:代码与输出里,gradient、update、参数变化分别出现在不同位置。
输入:
- CSV 文件里的多条观测行
- 每一行的压力未恢复程度
pressure_unrecovered - 每一行的目标阻断分数
target_block_score - 当前风险权重
risk_weight - 固定学习率
learning_rate
输出:
- 预测得到的阻断分数
- 损失
- gradient
- optimizer 做出来的 update 值
- update 前后的风险权重与损失
问题场景:
- 仅仅算出 gradient,并不等于参数会自动改变
- 只有 optimizer 做出的 update 被真正应用时,模型内部数字才会变
需要确认的概念:
- gradient 是方向信号
- optimizer 会把这个信号变成 update 值
- 参数变化要等到 update 应用以后才会出现
在看代码之前,先把这个例子分成 CSV batch、gradient 计算后、optimizer step 后来读,会更容易。例子数据在 optimizer-step-role-log.csv。
| 区间 | 这里要确认什么 |
|---|---|
| CSV batch | 多个样本上当前参数平均错得有多大 |
| gradient 计算后 | 方向信号已经出来了,但参数是否仍然保持原样 |
| optimizer step 后 | optimizer 做出的移动量是否真的反映到了参数上 |
| 更新后状态 | 用同一个 CSV batch 再看时,平均损失怎样变化 |
这段输出最好按顺序来读。先看 [batch] 区间,就能确认:从 CSV 读出的 36 个样本在当前 risk_weight_before = 1.0 下,平均损失是 7.308。这个数字不是某一行的偶然结果,而是这一 step 里一起处理的样本束整体的当前状态。
接着看 [after gradient calculation] 区间的 gradient_risk_weight,可以看到这个 batch 上已经算出了权重要往哪个方向调整的信号。但紧接着的 parameters_changed = False 很重要。gradient 虽然已经算出来了,但 optimizer step 还没有应用,所以参数值仍然没有变化。
第一次真正发生参数变化的位置是 [after optimizer step]。这里生成了 optimizer_delta = 0.619,这个移动量被反映后,risk_weight_after_step = 1.619。再用同一个 CSV batch 看一遍时,平均损失也降到了 0.287。因此,这个输出里真正重要的阅读习惯,是把 loss_before、gradient_risk_weight、parameters_changed = False、optimizer_delta、risk_weight_after_step 连成一条线读。它们对应的顺序正是:计算错误 -> 计算方向信号 -> 尚未反映 -> 生成真实移动量 -> 反映到参数上。

这张图展示的是:原本 risk_weight_before = 1.0,在 optimizer 做出的移动量被应用之后,真实发生了变化。这里重要的,不只是算出 gradient 了,而是这个结果最终变成了权重数字变化。

这张图则说明:同一个 update 也会马上影响 CSV batch 的平均预测值。也就是说,optimizer 不只是改内部权重,它还改变了下一次预测可能使用的出发点。

最后一张图确认:CSV batch 的平均损失也会随之下降。把这条顺序用眼睛再读一遍,会更清楚地看到:在gradient 计算和loss 降低之间,确实存在optimizer 做出真实 update 并应用这个中间步骤。
所以这个例子里读者最该带走的是下面这些点。
gradient_risk_weight还不是参数本身optimizer_delta是 optimizer 把 gradient 变成的真实移动量- 参数变化要到
risk_weight_after_step才真正看得见 - 因此,
算出了 gradient和模型已经更新了并不是同一句话
这里真正要固定住的核心,就是:gradient 出来了与模型真的变了之间,确实隔着 optimizer 做出的中间步骤。至于同一个 gradient 在不同步幅下为什么会得到不同结果,会在下一节 P5-7.2 继续说明。
什么时候要先用 optimizer 视角来读¶
当一段说明只说已经算出了 gradient,但还无法闭合参数到底怎么真实移动时,就要先把这一节拿出来。
| 先出现的问题场景 | 为什么 optimizer 视角会先有用 | 紧接着要看的问题 |
|---|---|---|
| gradient 能看懂了,但真实移动规则和幅度还看不见 | 它会迫使我们把 update 当成单独规则来读 | 接下来要看同一个 gradient 在不同 learning rate 下如何改变步幅 |
| loss、反向传播、更新糊成一团 | 可以把错误 -> gradient -> 真实修改的角色差异重新拆开 | update 的步幅与自适应修正要到后面几节继续看 |
| 直觉上不明白为什么同样的 gradient 也可能得到不同结果 | 可以先固定:optimizer 会改变学习动力学本身 | 后面还要去看 P5-7.2、P5-7.3 的步幅与自适应 update |
检查清单¶
- 能说明 optimizer 不是
计算 gradient 的阶段,而是把已经算出的 gradient 变成真实参数更新的阶段吗? - 能把损失函数、反向传播、优化器分别说成
错误计算、方向信号计算、真实修改应用吗? - 能区分为什么
算出了 gradient和参数真的改变了是两句不同的话吗? - 能说明:只有 optimizer 做出的 update 真正被应用之后,参数和损失的变化才会出现吗?
- 能知道:下一节 P5-7.2 会继续说明 learning rate 怎样改变 update 步幅,P5-7.3 会继续说明 Adam 类方法还补了什么吗?
出处与参考资料¶
- Ian Goodfellow, Yoshua Bengio, Aaron Courville,
Deep Learning, MIT Press, 2016, 确认日期: 2026-06-29. https://www.deeplearningbook.org/ - Léon Bottou,
Large-Scale Machine Learning with Stochastic Gradient Descent, COMPSTAT, 2010, 确认日期: 2026-07-19. https://doi.org/10.1007/978-3-7908-2604-3_16 - Sebastian Ruder,
An overview of gradient descent optimization algorithms, arXiv, 2016, 确认日期: 2026-07-19. https://arxiv.org/abs/1609.04747