神经网络训练循环
按清梯度、前向、损失、反向和更新的顺序重复优化参数。
学习目标
- 一个参数训练一步
- backward 为什么不能替代 optimizer.step?
- 说明:单步损失下降只证明这一步对当前构造目标有效,不证明所有批次都下降或模型已收敛。
为什么需要这个概念
一个基本训练步依次清理旧梯度、前向预测、计算标量损失、反向传播和更新参数。epoch 是重复这些批次步骤的外层数据遍历。
学习前需要掌握
背景与问题
定义网络只是定义了一族函数。训练循环把数据、目标、梯度和优化器接起来,让当前参数按选定损失发生变化。循环顺序错误时,代码可能运行却一直使用旧梯度。
概念定义
一个基本训练步依次清理旧梯度、前向预测、计算标量损失、反向传播和更新参数。epoch 是重复这些批次步骤的外层数据遍历。
直观理解
forward 给答案,loss 按规则评分,backward 计算每个参数应往哪边调,step 才真正转动参数旋钮。
核心原理
optimizer.zero_grad(set_to_none=True) 让下一次反向从新梯度开始;loss.backward 只累积导数。optimizer.step 使用优化器保存的参数引用和内部状态更新参数,因此优化器必须绑定实际参与训练的参数。
model.train 切换 Dropout、BatchNorm 的训练行为。验证时使用 model.eval 并关闭梯度跟踪,不应执行 backward 或 step。训练与验证的预处理也应按约定区分,验证结果用于选择模型而测试结果用于最终评估。
记录 epoch 损失时,均值损失要乘批内有效样本数再累计,最后除总数。梯度累积则需另行定义归一化与更新频率,不能把每次 backward 都当成一次 optimizer update。
数学表达
核心公式
一个基本训练步依次清理旧梯度、前向预测、计算标量损失、反向传播和更新参数。epoch 是重复这些批次步骤的外层数据遍历。
变量含义
θ模型参数η学习率L当前 batch 损失
计算步骤
- 1清梯度,前向得到 2,损失为 16。
- 2反向得到 w 的梯度 -16。
- 3SGD 更新得到 w=2.6,再前向损失约为 0.64。
代码对应位置
示例 1一个参数训练一步的最小验证:使用完整构造输入核对一个参数训练一步,输出只表示本例计算。
变量解释
| 变量 | 含义 |
|---|---|
θ | 模型参数 |
η | 学习率 |
L | 当前 batch 损失 |
完整数值示例
一个参数训练一步
已知条件
- 模型 y=wx,初始 w=1
- 输入 x=2,目标为 6,学习率 0.1
计算步骤
- 清梯度,前向得到 2,损失为 16。
- 反向得到 w 的梯度 -16。
- SGD 更新得到 w=2.6,再前向损失约为 0.64。
计算结果
一次更新使本构造样本上的损失从 16 变为 0.64。
处理前后对比
- 单步损失下降只证明这一步对当前构造目标有效,不证明所有批次都下降或模型已收敛。
案例推演与判断
这个例子把参数更新从框架代码中拆开:w−0.1×(-16)=2.6。再次前向得到 5.2,与目标差 -0.8,平方损失为 0.64。日志同时记录更新前后,便能确认 optimizer 的确使用了本次梯度。
这只有一条训练样本,没有泛化结论。更大的模型即便训练损失降低,也仍要检查独立验证样本和类间差异。
计算与实现步骤
- 清梯度,前向得到 2,损失为 16。
- 反向得到 w 的梯度 -16。
- SGD 更新得到 w=2.6,再前向损失约为 0.64。
代码实现
示例 1
一个参数训练一步的最小验证
example_01.py用途:使用完整构造输入核对一个参数训练一步,输出只表示本例计算。
import torch
from torch import nn
model = nn.Linear(1, 1, bias=False)
with torch.no_grad():
model.weight.fill_(1.)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
x, target = torch.tensor([[2.]]), torch.tensor([[6.]])
optimizer.zero_grad(set_to_none=True)
loss = (model(x) - target).square().mean()
loss.backward()
optimizer.step()
with torch.no_grad():
after = (model(x) - target).square().mean()
print(f"before={loss.item():.2f} weight={model.weight.item():.2f} after={after.item():.2f}")代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
使用完整构造输入核对一个参数训练一步,输出只表示本例计算。
model- 组合预处理和估计器的模型对象。
mean- 训练特征逐列均值。
loss- 当前预测与目标之间的损失值。
Step 1 · 1–14 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import torch
from torch import nn
model = nn.Linear(1, 1, bias=False)
with torch.no_grad():
model.weight.fill_(1.)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
x, target = torch.tensor([[2.]]), torch.tensor([[6.]])
optimizer.zero_grad(set_to_none=True)
loss = (model(x) - target).square().mean()
loss.backward()
optimizer.step()
with torch.no_grad():
after = (model(x) - target).square().mean()
print(f"before={loss.item():.2f} weight={model.weight.item():.2f} after={after.item():.2f}")代码与数学原理
减去训练均值并除以训练标准差,对应代码中的 mean/std 或 StandardScaler。
参数更新由当前梯度与学习率共同决定。
平方误差代码对应均方损失。
预期输出或运行结果
before=16.00 weight=2.60 after=0.64
常见错误 · 5 条
- 不要在测试集上调用 fit 或 fit_transform;测试集只能复用训练参数。
- 忘记清梯度
- backward 当更新
- 学习率越大越好
- 验证仍 step
实际应用
- 监督学习的小批量训练
- 定位模型参数始终不变的问题
常见错误
- 忘记清梯度
- backward 当更新
- 学习率越大越好
- 验证仍 step
常见问题
backward 为什么不能替代 optimizer.step?
backward 计算和累积导数;step 按 SGD、Adam 等规则读取这些导数并修改参数,两者是不同操作。
这个示例能说明什么边界?
单步损失下降只证明这一步对当前构造目标有效,不证明所有批次都下降或模型已收敛。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。