2.5 前向反向与梯度下降


2.5 前向反向与梯度下降

本节摘要:一次学习步骤可以写成五步:取一批数据、前向算出预测、用损失比较标签、反向得到梯度、优化器更新参数。Keras 的 fit 把五步收进函数;PyTorch 把五步写在循环里,并多出显式的梯度清零。梯度下降的直觉是「沿损失下降最快的方向挪参数」,Adam 只是这个方向上带自适应步长的变体。评估时关掉训练态与求导,只做前向。读完本节,你应能把任何一侧的训练代码还原成这五步。

本节导读

阅读完本节,你应当能够:

  1. 画出从批次到参数更新的流程图,并标出两侧 API 对应点
  2. 解释前向传播为何必须与反向使用同一套激活与形状
  3. 说明学习率、批次大小如何进入这一步,而不是进入「另一个魔法模块」
  4. 区分训练循环与评估循环:Dropout、BN、梯度、指标归约

把「训练」从黑话还原成步骤

原文 2.4 把流程写成:前向、损失、反向、梯度下降,再套进按 epoch、按批次的循环。评估单独一段。这和引言里的总流程(加载 → 构建 → 定义损失优化器 → 拟合 → 评估 → 预测)是同一件事的不同放大级别。对照驱动要求我们把放大级别固定在「一个批次」:这是 Keras 与 PyTorch 写法分叉最清楚的地方。

前向传播:数据张量穿过每一层,得到 logits 或概率。它必须是纯函数式的张量运算,不能在这里偷偷改权重(除了 BN 那种统计更新,入门 MLP 可忽略)。反向传播:从损失标量出发,沿 2.2 节的轨迹把梯度送到每个参数。梯度下降:参数减去学习率乘梯度(SGD 原话);Adam 会用历史梯度修正这个减法。没有新的物理定律,只有这三句话的工程包装。

原文 PyTorch 训练循环图与上面几乎同构,只是多了「梯度清零」插在前向之前。那不是第五点半物理,而是 2.2 说的累加行为。把清零画进图,PT 代码才能一行行对上;讲原理时可以把它标注为「PT 必做、Keras 隐含」。

epoch 的定义:把训练集完整过一遍。原文示例 10 个 epoch。批次数 = 样本数除以 batch_size(最后一批可能不足)。Fashion MNIST 训练 60000 张、batch 64,约 938 个批次。PT 示例每 100 步打印一次损失,就是为了在一个 epoch 里看到多次更新,而不只看 epoch 末一个数字。Keras fit 默认的进度条做了类似的事。对照时不要拿「Keras 一行日志」对「PT 每 100 步一行」说框架更吵,那是日志策略。

💡 关键直觉:fit 不是另一种算法,是把五步加上进度条和回调的封装。你会写五步,就会读 fit;你会读 fit,也应该能把 PT 循环里的五步指认出来。

梯度下降在这一步里到底改了什么

参数空间维度等于权重个数,784×128+128+128×10+10 已经上万。损失是这个空间上的一个曲面。梯度是局部斜率。学习率是沿斜率走多远。太大则越过谷底,损失爆炸;太小则 10 个 epoch 走出很短一截。原文让你「尝试不同的 epochs」和「尝试不同的 lr」,就是在调这两柄。

批次梯度是对 64 个样本平均后的方向,不是全数据集的真梯度。所以曲线会抖。抖不是实现错了。全量梯度在 60000 张上每步太贵,随机单样本又太噪,小批量是折中。64 来自原文 DataLoader,不是理论最优。显存不够就降批次,并意识到 Adam 的默认超参对批次变化并不完全免疫。

取批次 → 清梯度(PT) → 前向 → 损失 → 反向 → 更新 → 记录 ↑ 评估分支:无清梯度、无更新、eval+no_grad

评估循环只走前向与损失/指标。原文 PT 评估:model.eval()torch.no_grad(),累加正确数与总数,最后除。Keras:model.evaluate(test_images, test_labels, verbose=2) 返回 test_loss, test_acc。预测再短一截:只要前向。Keras predict 得到每类概率(因为入门模型带 Softmax);PT torch.max(outputs, 1) 在 logits 上取最大索引,与 Softmax 后取最大等价。打印第一张图的概率分布,是原文 TF 预测段的具体动作,用来建立「输出是 10 个数」的直觉。

训练与评估混用同一循环、却忘记切 eval,Dropout 会让验证准确率偏低且抖。反过来,训练时误留在 eval,Dropout 关闭,你以为正则在起作用其实没有。Keras 的 fit/evaluate 会切换学习阶段;自己写 train_on_batch 时要小心。对照检查:每一段代码开头是否明示当前阶段。

⚠️ 常见坑:PyTorch 循环里把 zero_grad 写在 backward 之后、step 之前,或干脆漏写。另一坑:评估时用训练损失的运行平均冒充测试成绩。运行平均只反映训练集。

从原理五步对照到工程五步

把两侧 API 钉在同一张表上,第 4 章就可以少费口舌。表里的「隐含」不是能力缺失,是封装。

原理步骤 Keras fit 内部 PyTorch 手写
取批次 数组切片或 tf.data DataLoader 迭代
清梯度 隐含 optimizer.zero_grad()
前向 模型 __call__ outputs = model(inputs)
损失 compile 时绑定 criterion(outputs, labels)
反向 Tape 隐含 loss.backward()
更新 优化器 apply 隐含 optimizer.step()
设备 通常自动 inputs.to(device)

过拟合在五步层面的表现:训练损失继续下降,验证损失上升。算法仍在正确执行五步,只是执行的目标(训练损失)与你关心的目标(新数据)分家了。不要因此怀疑反向传播写错。5.3 节用曲线形状诊断,前提是你相信五步在跑。若训练损失也不降,才回到:学习率、断图、标签错位、忘记 step

早停是评估循环的政策,不是新的传播算法:验证指标若干 epoch 不升就停,避免在过拟合区继续五步。Keras 有回调;PT 自己记最佳 state_dict。原理节只要求你知道「停」是停止更新,不是停止前向。

预测阶段的 argmax 在 Keras 原文用 np.argmax(predictions[0]),因为 predict 返回概率数组;PT 用 torch.max(outputs.data, 1)outputs.data 是老写法里避开梯度历史的方式,与 no_grad 可以同时存在。对照时认「取最大索引」这一动作,不抠属性名。

最后把「第一个 AI 模型」的验收再次落到五步:能完整跑完至少一个 epoch 的五步,能在测试集只前向地报告准确率,能把更新后的权重留下(第 4.4 节)。缺一步都不能说搭好了。Fashion MNIST 的十类名字只在预测打印时出现,不进入五步本身——模型看见的只是整数 0 到 9。

把五步写进一天的实验节奏

早上空跑:随机张量当输入,随机标签当 y,只跑两个批次,确认无 shape 错误、损失是有限数、step 后参数确实变了(打印某一层权重的和)。中午接真数据一个 epoch,确认损失从高位下降而不是 NaN。晚上才拉满 10 epoch 并画验证。很多人反过来:一上来 10 epoch,NaN 了不知道第几步坏的。空跑成本接近零,却能把 2.1 到 2.4 的约定全测一遍。

「参数确实变了」这一条在 PT 上特别有用。忘记 step 时损失仍能打印(前向和损失不依赖更新),准确率随机。比较一步前后 fc1.weight.abs().mean() 能揭穿。Keras fit 若已 compile,参数一般会变;若你自定义循环漏了 apply_gradients,同样需要这一比较。把它写成习惯,比盯着准确率猜更省时间。

问题:能不能一个 epoch 只更新一次,用全数据集梯度?

能,叫批量梯度下降,Fashion 6 万张在内存里也算得动。曲线会更平滑,每步更慢,跳出差局部的能力更弱。小批量是实践默认,不是理论唯一。对照实验不要一侧全量一侧 64,那是在比较算法变体而不是框架。坚持原文的按批次更新,把「全量」留到你刻意做消融的时候。

五步里「取批次」与「更新」之间不要插入 Python 里的重计算,例如每次用 NumPy 把整网权重拉出来改一改再塞回去。那会断图(2.2)而且极慢。改权重的合法位置是优化器 step / apply_gradients。调试打印可以插,但打印要用 .item()tf.print,避免把带梯度的张量变成 NumPy 再参与下一步运算。Keras fit 内部不会让你轻易插入这种断图,所以 Keras 用户第一次写 Tape 循环时特别容易犯。对照课把断图当成自定义循环的入门税,用二次函数玩具先交税,再上 Fashion。

epoch 与 step 的换算要会:60000/64 不是整数,最后一批较小,步数是向上取整或保留余数取决于 drop_last。说「我训了 10 个 epoch」比说「我训了 9380 步」更适合对照 Keras,因为 Keras 日志按 epoch。两边都用 epoch 当时间单位,5.3 才有共同横坐标。

要点速记

  • 五步:取批次、前向、损失、反向、更新;PT 另加清梯度
  • fit 是封装不是别的算法:会展开才会对照
  • 评估只前向evaluate / eval+no_grad;Dropout 必须切阶段
  • 训练损失下降≠泛化:过拟合时五步仍正确,目标函数与关心对象分家
  • 批次与学习率:进入这一步的尺度,不在层定义里
  • 预测是 argmax:概率或 logits 上取最大索引,两侧打印方式不同

下一章离开「一个张量」进入「一串批次」:tf.dataDataset/DataLoader 如何把预处理、打乱、预取接到这五步的第一步上。

五步验收:空跑两步,损失为有限数,某层权重的绝对值均值在 step 之后发生变化。变了说明更新发生;不变说明漏了 step 或 apply_gradients 或优化器没拿到参数。Keras fit 通过 compile 绑定,漏 compile 会直接报错,比 PT 漏 step 更吵,也因此更好查。评估分支必须切断更新。过拟合时五步仍正确,不要拆循环,去 5.3 开处方。学习率与批次进入这一步的尺度,不在 Dense 的 units 里。把尺度写进实验卡片,把层宽写进另一栏,两栏不要混成「我改了模型」。

空跑用假标签时,交叉熵仍应是有限数。NaN 说明 lr 爆炸或输入未归一化或出现了对数零。假标签要落在 0 到 9。假输入范围应接近真数据,不要用 0 到 255 的随机整数空跑再上 0 到 1 的真数据,两段实验不可比。权重变化检查选一层的 mean abs,不要比整个 state_dict 是否相等,因为即使没更新,浮点对象也是新的。变了才算 step 发生。评估循环禁止 step。过拟合不拆五步。尺度与宽度分栏。epoch 当共同时间单位。把这些写成空跑检查单,4.3 的模板只是把检查单换成 API。

把空跑检查单执行成动作:假输入范围接近真数据,假标签 0 到 9,两步损失有限,权重 mean abs 变化,评估无 step。NaN 停。不变权重则查六漏里的 backward 与 step。过拟合不拆五步。时间单位用 epoch。尺度与宽度分栏。检查单过了,4.3 只换名字。检查单不过,Fashion 上的随机准确率不能用来骂框架。骂框架是对照课最贵的错误,因为它会让你停止查图。

把空跑检查单执行到打勾。勾齐进 4.3。勾不齐禁止骂框架。骂框架会停止查图,是最贵错误。五步正确与泛化好坏是两件事。两件事分栏。分栏了,5.3 的诊断树才能接上。接不上,是因为你把过拟合当成 backward 写错。写错时训练损失也不降。降了还过拟合,backward 是对的。

审查权重变化用同一层同一统计量,不要有时用 mean 有时用 sum。统计量换了,变化可正可负,你会误判 step 没发生。固定一种。固定了,空跑才是测试而不是占卜。占卜出来的五步,4.3 无法翻译。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U