首页 / PyTorch 入门教程 / 训练循环:前向、反向、更新一步到位

PyTorch 入门教程

训练循环:前向、反向、更新一步到位

本教程共 60 篇 · 第 16 篇 · 更新于 2026-08-17 · 约 3 分钟阅读

PyTorch训练循环前向传播反向传播深度学习入门

本节目标:把模型、损失函数、优化器串起来,写出标准训练循环,并搞懂每一步为什么不能省。

训练循环:一切的核心

前面几章我们分别学了模型、损失函数、优化器。把它们组装起来的那段代码,就是训练循环(training loop)。

无论多复杂的训练代码,剥开外壳都长这样:

  1. 清空梯度
  2. 前向传播:模型算预测
  3. 计算损失:损失函数打分
  4. 反向传播:算出每个参数的梯度
  5. 更新参数:优化器迈一步

这五步反复执行,模型就一点点变聪明。先记住骨架,后面所有变体都是它的加料。

最小可运行的训练示例

光说不练假把式。下面是一段完整的、可以直接跑的训练代码。我们随机造一批数据,让一个小网络去学「输入三个数,输出它们的和」这个简单映射:

import torch
import torch.nn as nn
import torch.optim as optim

# 1. 造数据:输入 3 个数,标签是它们的和
x = torch.randn(500, 3)
y = x.sum(dim=1, keepdim=True)

# 2. 定义模型
model = nn.Sequential(
    nn.Linear(3, 16),
    nn.ReLU(),
    nn.Linear(16, 1),
)

# 3. 损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

# 4. 训练循环
for epoch in range(200):
    optimizer.zero_grad()        # 清梯度
    pred = model(x)              # 前向
    loss = criterion(pred, y)    # 算损失
    loss.backward()              # 反向
    optimizer.step()             # 更新

    if epoch % 40 == 0:
        print(f"epoch {epoch:3d}, loss = {loss.item():.4f}")

跑起来你会看到 loss 一路下降,最后趋近于 0。说明网络真的学会了求和。

五步逐一拆解

为什么先清梯度?

第 7 章讲过,backward() 算出的梯度是累加到 .grad 上的。不清空的话,这一步的梯度会混进上一步的旧账,方向就偏了。

optimizer.zero_grad(set_to_none=True)   # 推荐:梯度设为 None,省显存
Note

一个 batch 更新一次,zero_grad() 就放在 batch 循环的开头。千万别放到 epoch 循环外面,那是经典 bug。

前向与损失:算分

model(x) 就是前向传播(forward pass):数据流过各层,得到预测。再交给损失函数打分。这步没什么花样,注意输入形状对上就行。

反向:传播梯度

loss.backward() 是反向传播(backward pass)。PyTorch 的自动求导引擎从损失出发,沿计算图往回走,算出每个参数的梯度。你一行导数公式都不用写,这是框架最大的便利。

更新:迈出一步

optimizer.step() 拿着刚算好的梯度,按优化器自己的算法(SGD、Adam……)更新参数。到这里,一轮学习才真正完成。

train 和 eval 模式

模型身上有个隐藏开关:model.train()model.eval()。平时不用管,但涉及 Dropout、BatchNorm 这类层时必须切换:

# 训练阶段
model.train()
for inputs, labels in train_loader:
    ...

# 验证阶段
model.eval()
with torch.no_grad():
    for inputs, labels in val_loader:
        ...

原因:Dropout 训练时随机丢弃神经元防过拟合,验证时要全部启用;BatchNorm 训练时用本批数据统计,验证时用训练积累的全局统计。不切 eval(),验证分数会莫名其妙变差。

torch.no_grad() 也别忘。验证不需要梯度,关掉它既省显存又跑得快。

日志与监控

训练不是黑箱,得盯着损失看。两个小细节:

  • loss.item() 把张量转成 Python 数字,避免计算图堆积
  • 顺手算个准确率,比光看 loss 直观
running_loss = 0.0
for i, (inputs, labels) in enumerate(train_loader):
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()

    running_loss += loss.item()
    if i % 100 == 99:                     # 每 100 个 batch 报一次
        print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f}')
        running_loss = 0.0

epoch 和 iteration 是什么

两个词别搞混:

  • iteration(迭代):处理一个 batch 算一次
  • epoch(轮):把整个训练集完整过一遍

训练集 50000 张图,batch 大小 64,那一个 epoch 大约有 782 个 iteration。调度器通常按 epoch 更新学习率,就是这个道理。

排查清单:训练不正常怎么办

  • loss 变成 NaN:学习率太大,或某层输出爆炸。先降学习率,加梯度裁剪
  • loss 不下降:学习率太小;数据没打乱;损失函数选错
  • loss 在降但准确率不动:正常,可能还在早期,先看趋势
  • 显存溢出:减小 batch 大小;用 zero_grad(set_to_none=True) 省显存

后续还能加什么料

这个五步骨架是地基,往上能盖不少楼。显存不够就加梯度累积,训练不稳就加梯度裁剪,想提速就上混合精度或 torch.compile,想调收敛节奏就加学习率调度器。这些后面章节会逐个展开,现在你只要知道:一切变体都围绕这五步转。

Tip

训练循环是深度学习的万金油模板,以后你会写几百遍。这个五步骨架先背下来,再谈技巧。