代码改变世界

深度学习调试记录 - 详解

2025-10-15 19:18  tlnshuju  阅读(9)  评论(0)    收藏  举报

今天编写了一个轨迹编码解码的神经网络,但是训练的时候loss下降的非常慢,最后在0.2附件徘徊。所以,记录一下轨迹编码的网络的训练过程:
看起来是长期在 ~0.25 左右平台期。这种情况先别盲改“层数/学习率”,按下面顺序快速定位——从“能不能在极简场景上学到接近 0 的损失”开始。如果连极简都不行,多半是数据/损失/对齐问题;如果极简能,才去调容量/学习率。

A. 10 分钟定位流程(强烈建议照做)

  1. 极小样本过拟合测试(必须过!)

    • 取 1–4 条轨迹,循环训练直到训练 loss≈1e-4(或明显下降到接近 0)。
    • 过不了 ⇒ 高概率是:数据未标准化 / 角度处理错误 / 目标对齐错位 / 损失维度权重失衡 / 代码 bug。
    subset = torch.utils.data.Subset(train_dataset, list(range(4)))
    loader = DataLoader(subset, batch_size=4, shuffle=True)
    for p in model.parameters(): p.requires_grad_(True)
    model.train()
    opt = torch.optim.Adam(model.parameters(), lr=1e-3)
    for step in range(3000):
    x = next(iter(loader)).to(device)          # x: (B,T,3) -> x,y,yaw
    y_hat = model(x)                            # 与你的前向一致
    loss = loss_fn(y_hat, x)                    # 见下文改进的loss
    opt.zero_grad(); loss.backward(); opt.step()
    if step % 100 == 0: print(step, float(loss))
    • 如果在这个测试里 loss 依旧 ~0.25,先别动模型结构,先修“B/C/D”
  2. 检查“目标对齐/索引”

    • 自回归解码时,常见错位:把 t 的输出跟 t+1 的标签比;或训练时 teacher forcing/shift 处理不一致。
    • 快检:用同一个网络做**“恒等映射”**(encoder 直连 decoder / 或把输入直接喂给输出头)看损失是否瞬间接近 0;不接近 ⇒ 维度/顺序/mask/归一化错了。
  3. 看“训练集 vs 验证集 loss”

    • 都在 0.25 附近欠拟合/损失定义有问题/优化器设置不当
    • 训练低、验证高 ⇒ 过拟合(再谈正则/数据)。

B. 先把“损失/数据预处理”修正确

你的轨迹是 (x, y, yaw)(yaw 是角度)。直接用 MSE 处理角度会出错(π 与 -π 的误差会非常大),必须用圆形角度损失;同时 (x,y) 尺度若没标准化,也会让 loss 卡高位。

建议的损失:Huber(x,y) + 角度环形误差(并对分量做尺度平衡)

import torch
import torch.nn.functional as F
def angle_diff(a, b):
# 角度差规约到 (-pi, pi]
return torch.atan2(torch.sin(a-b), torch.cos(a-b))
def traj_loss(y_hat, y_true, xy_w=1.0, yaw_w=0.5, delta=1.0):
# y_*: (B,T,3) -> x,y,yaw
xy_hat, yaw_hat = y_hat[..., :2], y_hat[..., 2]
xy_true, yaw_true = y_true[..., :2], y_true[..., 2]
# Huber 对 (x,y),更稳
l_xy = F.smooth_l1_loss(xy_hat, xy_true, reduction='mean', beta=delta)
# 环形角度误差
d_yaw = angle_diff(yaw_hat, yaw_true)
l_yaw = torch.mean(d_yaw ** 2)               # 或者 smooth_l1 也行
return xy_w * l_xy + yaw_w * l_yaw

强烈建议做标准化/反标准化(至少对 x,y):

  • 训练前:x_norm = (x - μ) / σ(μ, σ 按训练集统计;yaw 不做 z-score,保持弧度制)
  • 模型预测后:反标准化再评估 ADE/FDE。
  • 如果你之前没做标准化,loss 卡在 0.2~0.4 非常常见。

可选:把 yaw 用 (sin, cos) 表示,预测两维再归一化到单位圆,误差用欧氏距离/余弦损失,很多轨迹任务更稳。

确保 mask 正确:如果有 padding,loss 一定要用 mask 只统计真实步长。


C. 优化器与学习率:先“找范围”,再调度

别拍脑袋改 lr,用 LR Range Test(几百步就能看出合适区间):

# 线性把 lr 从 1e-6 提到 1e-1,记录 loss vs lr
opt = torch.optim.Adam(model.parameters(), lr=1e-6)
gamma = (1e-1/1e-6) ** (1/1000)  # 1000 步内指数拉升
scheduler = torch.optim.lr_scheduler.ExponentialLR(opt, gamma)
lrs, losses = [], []
model.train()
it = iter(DataLoader(train_dataset, batch_size=64, shuffle=True))
for step in range(1000):
try: batch = next(it)
except StopIteration:
it = iter(DataLoader(train_dataset, batch_size=64, shuffle=True))
batch = next(it)
x = batch.to(device)
y_hat = model(x)
loss = traj_loss(y_hat, x)
opt.zero_grad(); loss.backward(); opt.step(); scheduler.step()
lrs.append(opt.param_groups[0]['lr']); losses.append(float(loss))
# 观察在哪些 lr 区间 loss 快速下降且未爆炸,选一个中间值作为起始 lr

常见起点:Adam lr=1e-3,配 ReduceLROnPlateauCosineAnnealingLR
网络不收敛:可尝试 1e-2 → 1e-4 范围扫描。
记得 gradient clipping(RNN/GRU/LSTM 强烈建议):

torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)

D. 自回归解码的“teacher forcing”与对齐

  • 训练时建议 teacher forcing 比例从 1.0 逐步退火(例如从 1.0 → 0.5 → 0.0),否则容易漂。
  • 检查你的 dec_in:是否用了上一步的真值(teacher forcing)还是上一步的预测(free running)。两者在训练-推理间要一致或做好 schedule。
  • 确保“时间维对齐”:out_t 对应的 target 是否同一时间步?

E. 判断“欠拟合 vs 过拟合”,再决定改模型还是正则

  • 欠拟合(训练/验证都 ~0.25):

    • 增大容量:hid_dim ↑num_layers ↑z_dim ↑(若是 VAE),适当加残差/LayerNorm;
    • 去掉/减小 dropout、weight_decay,先让网络学得动;
    • 检查是否强正则(过大 wd / 过强 dropout / 过小 hid)。
  • 过拟合(训练低、验证高):

    • 增加 dropout、weight decay、数据增强(噪声、抖动、旋转对齐等)、早停。

F. 更可解释的评估指标(仅看 loss 不直观)

同时输出这些指标能帮你判断瓶颈在哪:

  • ADE/FDE(Average/Final Displacement Error):(x,y) 的 L2 距离(注意反标准化)。
  • Angle ADE/FDE:角度环形误差的平均/最终步。
  • 把一条轨迹的输入 vs 重构画在同一图里(肉眼看偏差在拐弯还是直行段)。

G. 常见坑位 Checklist

  • 没有 model.train() / model.eval() 正确切换(BN/Dropout 会受影响)。
  • optimizer.zero_grad() 忘了;或多次 loss.backward()retain_graph=True
  • loss 归一化方式错误(同时对 B、T、C 除法导致过小或不稳定)。
  • 训练时用了归一化数据,计算 loss 却在反归一化后(或反之)——前后一致。
  • shuffle/seed 设置问题导致批间极不均匀。
  • 数据里的 yaw 单位混乱(度/弧度)。
  • 时间步错位(target 提前或滞后了一步)。
  • 使用 --filter=blob:none 拉数据而忘了把真实文件取回(与你之前的 git/LFS 问题类似,这里只是提醒数据加载是否正确)。

H. 如果你要具体建议(基于你常用设置的“保守配方”)

  • 数据x,y 标准化到均值 0 方差 1;yaw 用弧度,损失用环形;
  • 模型:GRU 2–3 层,hid_dim=128~256;解码自回归 + teacher forcing 退火;
  • 优化:Adam(lr=1e-3, betas=(0.9,0.999)),clip_grad_norm=1.0,CosineAnnealingLR 或 ReduceLROnPlateau;
  • 损失:Huber(x,y, beta=1.0)*1.0 + angle MSE *0.5;
  • 训练:先把4条样本过拟合到很低,再扩大数据量、打开正则。

如果你把一小段你的损失计算代码(前向输出到 loss 的那几行)贴出来,以及你的 输入张量形状(B,T,C)和是否标准化的信息,我可以直接指出哪一两行先改、以及建议的权重/学习率起点。