调试方法 / 6 分钟
把 Traceback 读成下一步
先找到真正失败的一行,再把环境、输入和调用链逐层缩小。报错不是最后一句话
模型训练失败时,Traceback 往往很长。我的第一步不是从顶端逐行阅读,而是先看最后一个异常类型和消息,再向上寻找第一个属于项目代码的调用位置。
这一步只回答一个问题:最先能够被当前证据确认的失败点在哪里。它不会立刻解释整个项目,也不要求马上修改代码。
把问题分成三个层次
在 PyTorch 项目里,同一个表面错误可能来自不同层次:
- 环境层:依赖导入、动态库、CUDA 或解释器环境。
- 数据层:文件路径、批次内容、张量形状和数据类型。
- 模型层:模块属性、前向调用、设备位置和输出维度。
先判断错误属于哪一层,可以减少无关修改。例如依赖尚未成功导入时,继续改模型结构不会提供新的证据。
用最小前向传播代替完整训练
完整训练包含数据加载、多个批次、反向传播和日志,很难看出错误最早在哪里出现。更小的检查是只取一个批次并运行一次前向传播:
images, labels = next(iter(train_loader))
images = images.to(device)
output = model(images)
print("input:", images.shape)
print("output:", output.shape)
如果这里已经失败,问题与训练循环后半段无关。如果这里通过,再逐步加入损失计算、反向传播和优化器更新。
一次修改对应一个判断
调试最容易失去证据的时刻,是同时改路径、形状和模型代码,然后看到错误消失。此时并不知道哪一个修改真正起作用,也不知道是否引入了新的问题。
更稳妥的流程是:
复现错误 -> 提出一个判断 -> 做最小修改 -> 重新运行 -> 记录结果
路径是否存在可以单独检查,张量维度可以单独打印,模块属性可以在构造后立即确认。每次运行都让下一步更具体。
修复后重新读取最新 Traceback
一个错误消失后,程序可能继续运行到下一个原本被遮住的问题。这不是“修复无效”,而是执行路径变长了。此时需要丢开旧截图,重新读取最新 Traceback,并再次定位第一个属于项目的失败位置。
调试的目标不是让红色文字消失,而是建立一条可以解释的证据链:原始失败是什么,哪项判断被验证,修改后程序前进到了哪里。