4. 训练损失震荡排查:学习率与批次大小的联合诊断
震荡现象与联合诊断思路
农业图像分类模型(例如番茄叶片病害四分类)训练时,若损失曲线出现“锯齿状大幅上下跳动、整体不下降或下降极慢”,通常不是单一超参数问题,而是学习率与批次大小不匹配共同作用的结果。诊断的核心是:把“震荡”拆成可观测的指标,再用对照实验逐项排除。
先明确两个关键关系:
- 学习率 η 过大:单步更新跨过损失谷底,损失在最优值两侧来回跳,表现为高频大幅震荡。
- 批次大小 B 过小:梯度估计噪声大,即使 η 合适,损失也会抖动;B 越小,噪声越大,可容忍的 η 上限越低。
- 二者耦合:线性缩放经验为 η ∝ B(大批次可配大学习率),但超过临界批次后收益递减,需配合 warmup 或调整优化器 β。
因此诊断顺序应为:先固定 B 扫 η,再固定 η 扫 B,最后检查优化器参数(β₁、β₂、weight decay)是否放大了震荡。
对照实验设计与执行
以番茄叶片病害四分类(健康、早疫病、晚疫病、叶霉病)为例,训练集约 4800 张,输入 224×224,骨干为 ResNet-18,优化器先用 AdamW。基线配置:B=32、η=1e-3、weight decay=1e-4、余弦退火 30 epoch。若基线已出现震荡,按下面矩阵做 12 组短程实验(每组 8 epoch,记录每 epoch 训练损失与验证损失)。
关键实现要点:
- 每组实验只改一个变量,其余固定,避免混淆。
- 记录损失标准差与相邻 epoch 损失差绝对值均值,作为“震荡强度”量化指标,比肉眼看曲线更可靠。
- 同时记录验证集准确率,防止把“训练损失平稳但过拟合”误判为稳定。
- 若出现 NaN,立即终止该组,标记为不可用配置。
import torch, torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms, models
def build_loader(batch_size, root="data/tomato"):
tf = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
ds = datasets.ImageFolder(root, transform=tf)
return DataLoader(ds, batch_size=batch_size, shuffle=True, num_workers=4)
def run_one(batch_size, lr, epochs=8, wd=1e-4):
loader = build_loader(batch_size)
model = models.resnet18(weights=None, num_classes=4).cuda()
opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=wd)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
crit = nn.CrossEntropyLoss()
losses = []
for ep in range(epochs):
model.train()
ep_loss = 0.0
for x, y in loader:
x, y = x.cuda(), y.cuda()
opt.zero_grad()
loss = crit(model(x), y)
loss.backward()
opt.step()
ep_loss += loss.item() * x.size(0)
ep_loss /= len(loader.dataset)
losses.append(ep_loss)
sched.step()
if not torch.isfinite(torch.tensor(ep_loss)):
return losses, float("nan")
return losses, float(torch.tensor(losses).std())
# 示例:固定 B=32,扫学习率
for lr in [1e-4, 1e-3, 5e-3, 1e-2]:
losses, std = run_one(batch_size=32, lr=lr)
print(f"B=32 lr={lr:.0e} std={std:.4f} losses={[round(v,3) for v in losses]}")
示例输出(教学示例,非实测):B=32、η=1e-3 时损失标准差约 0.02,曲线单调下降;η=5e-3 时标准差升到 0.09,出现明显锯齿;η=1e-2 时第 3 epoch 出现 NaN。据此可判断:在 B=32 下,η 的稳定上限在 1e-3 到 5e-3 之间。
根因定位与稳定配置确定
把 12 组结果按“震荡强度”和“验证准确率”两个维度排序,通常会出现三类典型模式:
| 现象 | 最可能根因 | 处置 |
|---|---|---|
| 小 B 下任何 η 都抖,大 B 下同 η 平稳 | 批次过小导致梯度噪声主导 | 增大 B 至 32/64,或启用梯度累积 |
| 同 B 下 η 稍大即锯齿,η 减半即平稳 | 学习率超过临界值 | 降 η,或加 warmup 前 3 epoch |
| 大 B 下 η 按线性缩放放大后仍抖 | 超过临界批次,AdamW 二阶矩估计不稳 | 加 warmup、调 β₂=0.99、或改用 SGD+momentum |
| 损失平稳但验证准确率不升 | 不是震荡问题,是过拟合或数据问题 | 转查正则化与数据划分 |
最终稳定配置的确定原则:在验证准确率不下降的前提下,选震荡强度最低且收敛速度可接受的组合。本例教学示例中,B=32、η=1e-3、warmup 3 epoch、余弦退火 30 epoch 为推荐配置;若显存允许,B=64、η=2e-3 加 warmup 也可作为备选,需复测验证集准确率确认无退化。
复测时保持随机种子一致,并至少跑 2 个种子取平均,避免把随机波动误判为配置差异。
不要仅凭训练损失判断稳定:训练损失平稳但验证损失上升,说明是过拟合而非震荡已解决,应转入正则化与数据增强排查。