
第七课标准答案先把上一课的 9 个问题完整收掉然后直接进入第 8 课。今天的主线会继续沿着同一条训练链往后走\[ \text{Gradient} \rightarrow \text{Learning Rate} \rightarrow \text{Optimizer} \rightarrow \text{Parameter Update} \]1. 什么叫计算图为什么 Forward 时要保留运算关系计算图可以理解成记录“一个结果是由哪些变量经过哪些运算得到的”这条关系链。例如\[ zwx \]\[ Lz^2 \]可以画成w ──┐ × → z → square → L x ──┘Forward 时当然要先算\[ w,x\rightarrow z\rightarrow L \]但训练还需要知道最终的 \(L\) 到底和前面的 \(w\) 有什么关系只有保留这条关系Backward 才能从\[ L \]一路反向找到\[ w \]并计算\[ \frac{\partial L}{\partial w} \]所以计算图不是为了“画图好看”而是为了让自动微分知道梯度应该沿哪条计算路径往回传。2. 为什么\[ \frac{\partial L}{\partial w} \frac{\partial L}{\partial z} \frac{\partial z}{\partial w} \]因为 \(w\) 没有直接决定 \(L\)。它先影响\[ z \]而 \(z\) 再影响\[ L \]也就是\[ w\rightarrow z\rightarrow L \]所以 \(w\) 对 \(L\) 的影响需要把中间两段影响连起来\[ w\rightarrow z \]这一段\[ \frac{\partial z}{\partial w} \]以及\[ z\rightarrow L \]这一段\[ \frac{\partial L}{\partial z} \]最终\[ \frac{\partial L}{\partial w} \frac{\partial L}{\partial z} \frac{\partial z}{\partial w} \]这就是 Chain Rule链式法则。3. 已知\[ x2,\qquad w3 \]\[ zwx,\qquad Lz^2 \]求\[ \frac{\partial L}{\partial w} \]先 Forward\[ z3\times26 \]然后\[ L6^236 \]现在反向。因为\[ Lz^2 \]所以\[ \frac{\partial L}{\partial z}2z12 \]又因为\[ zwx \]所以\[ \frac{\partial z}{\partial w}x2 \]于是\[ \frac{\partial L}{\partial w} 12\times2 \]得到\[ \boxed{24} \]4. ReLU 在正数和负数区域的梯度分别是什么ReLU\[ ReLU(x)\max(0,x) \]所以\[ x0 \]时\[ ReLU(x)x \]导数\[ \frac{dReLU}{dx}1 \]而\[ x0 \]时\[ ReLU(x)0 \]导数\[ \frac{dReLU}{dx}0 \]所以\[ \boxed{ ReLU(x) \begin{cases} 0,x0\\ 1,x0 \end{cases} } \]这意味着如果 Forward 时某个值小于 0那么这一位置在 Backward 时梯度会被截断为 0。5.loss.backward()后是weight变了还是weight.grad有了结果是\[ \boxed{weight.grad} \]有了结果。例如loss.backward()以后layer.weight参数本身还没有因为这句话而更新。但layer.weight.grad里面已经存着\[ \frac{\partial L}{\partial W} \]真正修改 Weight 的是optimizer.step()所以必须记住\[ \boxed{ backward 算梯度 } \]\[ \boxed{ step 改参数 } \]6. 为什么nn.Linear的 Weight 不需要手动设置requires_gradTrue因为nn.Linear(...)内部的weight bias不是普通 Tensor而是被 PyTorch 注册为Parameter这些可训练参数默认就会参与梯度计算。所以你通常会看到for name, param in model.named_parameters(): print(name, param.requires_grad)输出类似fc1.weight True fc1.bias True fc2.weight True fc2.bias True7.nn.Parameter和普通 Tensor 最核心的区别是什么普通 Tensorx torch.tensor(...)本质上只是数据。而nn.Parameter可以理解成被nn.Module识别、注册和管理的可训练 Tensor。所以它可以被model.parameters()找到。Optimizer 才能进一步拿到它optimizer Adam(model.parameters())然后训练时更新它。8. 为什么 Observation 一般不需要梯度而模型 Parameter 需要因为我们训练的目标不是修改 Observation。例如输入\[ o_t \]它只是数据。我们真正想改变的是\[ \theta \]也就是\[ W,b,\ldots \]所以训练关心\[ \frac{\partial L}{\partial W} \]\[ \frac{\partial L}{\partial b} \]而通常不需要\[ \frac{\partial L}{\partial o_t} \]因此 Robot Observation 通常作为普通 Tensor 使用而 Parameter 要保存梯度。9. 五行训练代码完整因果关系是什么optimizer.zero_grad()pred model(obs)loss loss_fn(pred, action)loss.backward()optimizer.step()完整关系是清空旧梯度 ↓ 当前参数做 Forward ↓ 得到预测 Action ↓ 和 Expert Action 比较 ↓ 得到 Loss ↓ Backward ↓ 沿计算图计算每个 Parameter 的 Gradient ↓ Optimizer读取 Gradient ↓ 修改 Parameter数学上就是\[ O \rightarrow \pi_\theta \rightarrow \hat A \]然后\[ L(\hat A,A) \]接着\[ \nabla_\theta L \]最后\[ \theta_{\text{new}} \theta_{\text{old}} \text{某种更新量} \]到底这个“更新量”怎么算就是下一课。VLA 系统学习第 8 课Optimizer 到底在干什么——从 Learning Rate 到 SGD、Momentum 和 Adam上一课我们已经知道\[ loss.backward() \]会得到\[ \nabla_\theta L \]也就是每个参数对应的梯度。但梯度只回答往哪个方向改Loss 会变大或变小它还没有完整回答每次到底应该改多少这就是 Optimizer 要解决的问题。最基础的更新公式\[ \theta_{\text{new}} \theta_{\text{old}} - \eta\nabla_\theta L \]这里新出现的\[ \eta \]就是Learning Rate学习率。一、先只看一个参数理解 Learning Rate假设模型只有一个参数\[ w \]当前\[ w1 \]Backward 算出\[ \frac{\partial L}{\partial w}-16 \]梯度是负数。说明如果 \(w\) 增大Loss 会下降。最基础更新\[ w_{\text{new}} w_{\text{old}} - \eta \frac{\partial L}{\partial w} \]假设\[ \eta0.01 \]那么\[ w_{\text{new}} 1-0.01\times(-16) \]得到\[ w_{\text{new}}1.16 \]所以参数往正确方向增加了一点。这里\[ \eta \]控制的就是这一步迈多大。二、Learning Rate 为什么不能随便设这可以用“下山”来理解。假设 Loss 是山的高度。我们的目标\[ \min_\theta L(\theta) \]就是从山上走到最低处。Gradient 告诉你哪边是上坡最快的方向。因此负 Gradient\[ -\nabla L \]告诉你哪边大致是下坡方向。但是知道方向之后还需要决定一步迈多大这就是 Learning Rate。三、Learning Rate 太小会发生什么假设正确方向已经知道但\[ \eta0.0000001 \]那么每一步\[ \Delta w -\eta\frac{\partial L}{\partial w} \]都非常小。模型虽然在学习但可能像1.000000 → 1.000002 → 1.000004 → 1.000006 → ...训练会非常慢。所以可能出现Loss 在下降 但是下降得极其缓慢这通常意味着Learning Rate 可能过小或者还有其他优化问题。四、Learning Rate 太大又会怎样假设最低点在\[ w3 \]当前\[ w1 \]本来应该慢慢1 → 1.4 → 1.8 → 2.2 → 2.6 → 2.9 → 3但如果步子特别大1 → 5 → 0 → 7 → -2 → ...会不断跨过最低点。于是 Loss 可能下降 ↓ 突然上升 ↓ 再下降 ↓ 剧烈振荡更严重时数值直接爆掉可能看到loss nan所以\[ \boxed{ Learning\ Rate太小 \rightarrow 学得太慢 } \]\[ \boxed{ Learning\ Rate太大 \rightarrow 震荡甚至发散 } \]五、最基础的 OptimizerGradient Descent最简单更新规则就是\[ \theta_{t1} \theta_t - \eta\nabla_\theta L \]意思是算当前 Gradient乘 Learning Rate沿负梯度方向更新。但真实训练通常不是用整个 Dataset 一次性算梯度而是使用一个 Batch。因此我们经常说Stochastic Gradient Descent也就是SGDPyTorchoptimizer torch.optim.SGD( model.parameters(), lr0.01)这里model.parameters()告诉 Optimizer更新哪些参数。而lr0.01告诉它每次更新的基础步长有多大。六、为什么叫 Stochastic理想情况下可以用整个 Dataset 计算真正的平均梯度\[ \nabla_\theta L_{\text{all data}} \]但假设 Dataset 有\[ 10^7 \]个样本。每更新一次参数都把全部数据算一遍成本太高。所以实际中通常每次只拿一个 Batch\[ B32 \]或者\[ B64 \]根据当前 Batch 估计\[ \nabla_\theta L \]因此这个梯度带有一定随机性和噪声。这就是 Stochastic 的来源。实际上深度学习中常见的所谓 SGD很多时候更准确地说是Mini-batch SGD七、Batch 不同Gradient 为什么也不同假设 Dataset 中有Batch 1 一组比较简单的数据 Batch 2 一组偏左的数据 Batch 3 一组偏右的数据每个 Batch 对 Loss 的贡献不同。所以算出来\[ \nabla_\theta L_1 \]\[ \nabla_\theta L_2 \]\[ \nabla_\theta L_3 \]不一定完全相同。于是参数更新可能这一批稍微往左改 ↓ 下一批稍微往右改 ↓ 再下一批又往另一个方向改这也是训练曲线不会像一条完美平滑直线下降的原因之一。八、为什么纯 SGD 有时会抖想象一个狭长山谷。最低点在谷底。但是梯度每次都有一点不同。可能出现左 → 右 → 左 → 右 → 左虽然整体正在向谷底走但横向一直来回震荡。这时可以引入Momentum动量。九、Momentum 的直觉是什么想象一个球从山坡上滚下来。它不会每一步都完全只看当前坡度。它还有之前运动留下来的惯性。如果连续几步都在向同一个方向走那么这个方向的速度会逐渐积累。这就是 Momentum 的直觉。十、Momentum 的基本数学思想最基础的一种写法\[ v_t \beta v_{t-1} \nabla_\theta L_t \]然后\[ \theta_{t1} \theta_t - \eta v_t \]这里\[ v_t \]可以理解为累积的更新趋势。\[ \beta \]控制之前的方向保留多少。例如\[ \beta0.9 \]意味着过去的运动趋势会被较强保留。所以如果过去连续很多次都往右是正确方向那么即使这一批数据梯度稍微抖一下整体也不会马上掉头。十一、Momentum 为什么能减少震荡假设横向梯度1 -1 1 -1不断来回变。长期来看互相抵消。而真正朝谷底的方向一直是-2 -2 -2 -2那么 Momentum 会逐渐积累稳定方向。结果就是横向抖动减弱 ↓ 主方向速度增强 ↓ 更快走向低 Loss 区域所以 Momentum 主要解决SGD 更新方向容易受当前 Batch 噪声影响的问题。十二、PyTorch 里的 SGD Momentum可以写optimizer torch.optim.SGD( model.parameters(), lr0.01, momentum0.9)现在 Optimizer 不再只看\[ 当前Gradient \]而是同时利用\[ 当前Gradient 过去更新趋势 \]十三、那为什么现代深度学习经常用 Adam接下来是你以后会极其频繁看到的torch.optim.Adam(...)甚至torch.optim.AdamW(...)先不讲 AdamW先把 Adam 的核心直觉搞清楚。SGD 有一个比较简单的问题所有参数基本共享同一个全局 Learning Rate。但是神经网络不同参数的梯度尺度可能差别非常大。例如\[ w_1.grad0.0001 \]而\[ w_2.grad100 \]如果都乘\[ \eta0.01 \]那么两个参数的实际更新幅度差异会非常巨大。Adam 想做的事情之一就是根据每个参数自身过去的梯度情况自适应地调整它的有效更新尺度。十四、Adam 同时记住两件事Adam 会维护两类统计量。第一类梯度最近大概往哪个方向走。可以写成一阶矩\[ m_t \]第二类梯度最近大概有多大。可以写成二阶矩\[ v_t \]先不要被“矩”这个词吓到。现在只需要理解\[ m_t \]大致像带 Momentum 的平均方向。而\[ v_t \]大致反映梯度大小的历史信息。十五、Adam 的一阶矩核心思想类似\[ m_t \beta_1m_{t-1} (1-\beta_1)g_t \]这里\[ g_t\nabla_\theta L_t \]也就是当前 Gradient。如果\[ \beta_10.9 \]那么\[ m_t \]会保留过去的方向趋势。所以这一部分和 Momentum 很像。十六、Adam 的二阶矩Adam 还会记录\[ v_t \beta_2v_{t-1} (1-\beta_2)g_t^2 \]注意\[ g_t^2 \]表示梯度平方。因此 \(v_t\) 大致反映最近这个参数的梯度通常有多大。如果某个参数梯度长期特别大Adam 会相应缩小它的有效步长。如果某个参数梯度长期比较小Adam 可以相对给它更合适的更新尺度。十七、Adam 最终怎么更新先忽略 bias correction 等细节直觉上可以看成\[ \theta_{t1} \theta_t - \eta \frac{m_t}{\sqrt{v_t}\epsilon} \]这里\[ m_t \]提供稳定的更新方向。\[ \sqrt{v_t} \]提供对梯度尺度的调节。\[ \epsilon \]是一个很小的数用于数值稳定避免除零等问题。所以 Adam 的核心可以先记成\[ \boxed{ Momentum思想 每个参数自己的自适应步长 } \]十八、为什么 Adam 经常比最基础 SGD 更容易直接用因为它对不同 Parameter 的梯度尺度进行了自适应调整。现实网络里Vision Encoder Transformer MLP Action Head不同层的梯度尺度可能很不一样。Adam 往往能让训练初期更容易稳定起来。所以你以后会大量看到optimizer torch.optim.Adam( model.parameters(), lr1e-4)或者optimizer torch.optim.AdamW(...)尤其在 Transformer、VLM、VLA 中非常常见。十九、Adam 是不是就不需要 Learning Rate 了不是。这是非常容易误解的地方。即使 Adam 会自适应不同参数的更新尺度仍然需要\[ \eta \]也就是基础 Learning Rate。例如optimizer torch.optim.Adam( model.parameters(), lr1e-4)这里\[ 10^{-4} \]仍然非常重要。Adam 不是自动把 Learning Rate 完全解决了。而是在给定基础 Learning Rate 的情况下对每个参数的更新进一步进行自适应调整。二十、为什么不同 Optimizer 的step()行为不同现在你终于可以真正理解optimizer.step()并不是固定的一种数学操作。如果 Optimizer 是 SGDtorch.optim.SGD(...)它按照 SGD 的规则更新。如果是 SGD Momentumtorch.optim.SGD(..., momentum0.9)它会利用历史速度。如果是 Adamtorch.optim.Adam(...)它会利用一阶矩和二阶矩。所以optimizer.step()只是一套统一接口。真正采用什么更新公式取决于optimizer这个对象具体是哪一种 Optimizer。这和前面学习的面向对象思路又连起来了。二十一、把代码真正看懂现在optimizer torch.optim.Adam( model.parameters(), lr1e-3)可以完整翻译成创建一个 Adam Optimizer 对象让它管理model.parameters()中所有可训练参数并使用基础学习率 \(10^{-3}\)。训练optimizer.zero_grad()pred model(obs)loss loss_fn(pred, action)loss.backward()optimizer.step()现在最后一句真正意味着Adam 读取每个 Parameter 当前的.grad结合自己保存的历史梯度统计根据 Adam 更新规则修改模型参数。二十二、Optimizer 自己也有“状态”这是一个很重要但很自然的新认识。模型有参数W b而 Adam 自己也会保存m v step count ...这些东西不是模型本身的预测参数但训练恢复时非常重要。因此 Checkpoint 往往不仅保存model.state_dict()还会保存optimizer.state_dict()为什么因为如果你训练到第\[ 50000 \]步突然停掉只加载 Model ParameterAdam 过去积累的\[ m_t,\quad v_t \]就全部丢了。恢复训练可能和真正连续训练不完全一样。所以以后看到checkpoint { model: model.state_dict(), optimizer: optimizer.state_dict()}你应该知道一个保存模型本身一个保存 Optimizer 的训练状态。二十三、为什么训练 Loss 会抖动而不是一直下降现在我们已经能解释很多真实训练现象。每个 Batch 不一样所以\[ g_t \]不一样。参数更新\[ \theta_t\rightarrow\theta_{t1} \]之后下一个 Batch 又可能比较难。因此 Loss 可能0.80 ↓ 0.61 ↓ 0.65 ↓ 0.52 ↓ 0.55 ↓ 0.41虽然不是每一步都下降但整体趋势下降。这通常并不奇怪。特别是 Mini-batch Training 中Gradient 本身就是带噪声的估计。所以判断训练是否正常不能只看某一步 Loss 有没有变大。要看一段训练过程中的总体趋势。二十四、Learning Rate 太大时你可能实际看到什么可能看到Loss突然剧烈震荡或者Loss持续增大甚至nan inf原因之一就是参数一步更新太大\[ \Delta\theta \]过大。于是当前参数还算合理 ↓ 一次step ↓ 参数跑到很差的区域 ↓ 输出变得极端 ↓ Loss变大 ↓ 梯度进一步异常最后训练数值崩溃。当然nan不一定只由 Learning Rate 导致但它是常见排查方向之一。二十五、Learning Rate 太小时你可能看到什么例如Epoch 1 Loss 0.800 Epoch 2 Loss 0.799 Epoch 3 Loss 0.798 Epoch 4 Loss 0.797整个训练非常慢。原因可能是\[ \Delta\theta -\eta g \]中\[ \eta \]太小。即使 Gradient 方向正确参数每次也只动一点点。二十六、Learning Rate 和 Batch Size 有没有关系有但现在先建立直觉不急着上复杂经验公式。Batch Size 越小Gradient 通常噪声更大。Batch Size 越大Gradient 通常更稳定更接近整个 Dataset 的平均趋势。例如\[ B1 \]每次只看一个样本。这个样本如果很特殊Gradient 就可能偏得比较厉害。而\[ B256 \]把很多样本平均起来梯度往往更稳定。但 Batch 越大显存需求越高每个 Step 成本更高优化性质也会变化。后面讲完整训练配置时再系统分析。二十七、现在可以第一次理解 Training Configuration以后看到训练配置batch_size: 64 learning_rate: 1e-4 optimizer: adam epochs: 100它们不是四个互不相关的参数。而是在描述同一件事一次拿多少数据 → batch_size 算出梯度后基础步子多大 → learning_rate 用什么规则更新参数 → optimizer 整个Dataset反复学习多少轮 → epochs这就是训练系统。二十八、把前几课全部接起来现在我们已经从 Robot Dataset 一直走到真正参数更新。完整链\[ \boxed{ Demonstration \rightarrow Dataset \rightarrow Batch \rightarrow Forward \rightarrow Predicted\ Action \rightarrow Loss \rightarrow Backward \rightarrow Gradient \rightarrow Optimizer \rightarrow Parameter\ Update } \]Optimizer 这一段又可以展开\[ Gradient \rightarrow Learning\ Rate \rightarrow Update\ Rule \rightarrow \theta_{\text{new}} \]如果是 SGD\[ \theta_{t1} \theta_t-\eta g_t \]如果加入 Momentum\[ \text{Current Gradient} \text{Previous Direction} \]如果是 Adam\[ \text{Direction History} \text{Gradient Scale History} \text{Base Learning Rate} \]共同决定下一步更新。第八课自测Gradient 和 Learning Rate 分别回答什么问题如果\[ w2 \]梯度\[ \frac{\partial L}{\partial w}5 \]学习率\[ \eta0.1 \]按照最基础 Gradient Descent新的 \(w\) 是多少为什么 Learning Rate 太大可能导致 Loss 震荡甚至发散为什么 Learning Rate 太小会导致训练非常慢Mini-batch SGD 中为什么每一个 Batch 算出来的 Gradient 不一定相同Momentum 相比最基础 SGD 多使用了什么信息Adam 中\[ m_t \]和\[ v_t \]可以分别先怎样理解Adam 会自动消除 Learning Rate 这个超参数吗为什么为什么恢复 Adam 训练时除了model.state_dict()还经常需要恢复optimizer.state_dict()现在解释optimizer.step()时为什么不能简单说成“执行梯度下降”因为不同 Optimizer 的具体更新规则并不相同。