Skip to content

常见陷阱与反模式

本页速览 深度学习实践中最常翻车的场景,每个都给你症状、原因、解法:数据泄漏、评估泄漏、学习率错误、忘记归一化、类别不平衡、不固定种子、验证集过拟合、BatchNorm 陷阱、混合精度 bug、微调学习率过大、显存 OOM,以及"看起来对"的假象。

常见陷阱与反模式 ​

一句话定义:深度学习项目里 80% 的失败不是"理论不够",而是反复踩进同一批工程陷阱——本页把它们逐个解剖,给出症状、原因与解法,让你踩过一次就能识别。

这些陷阱跨越数据、训练、评估、部署全链路。排查方法论的完整流程见调试与诊断,预防它们的系统性原则见DL 设计原则。

一、数据泄漏:测试集信息混入训练 ​

  • 症状:离线指标惊人(99%+),线上效果大幅缩水;或模型在训练集上"过拟合得过于完美"。
  • 原因:归一化统计量、特征工程(如标准化用的均值/方差)、类别编码等只该由训练集计算的东西,混入了全量数据(含测试集)。
  • 解法:
    python
    # 错误:用全部数据的统计量
    mean, std = all_data.mean(), all_data.std()
    
    # 正确:只拟合训练集,测试集用同一套统计量
    mean, std = train_data.mean(), train_data.std()
    对时序数据还要防时间泄漏(用未来的信息预测过去);对用户数据要防身份泄漏(同一用户的样本不能同时进训练和测试)。完整清单见评估实践。

二、评估泄漏:在测试集上调参 ​

  • 症状:测试集准确率反复"涨"到离谱,但模型换数据后崩盘。
  • 原因:测试集被当成验证集用——每跑一次实验看一眼测试集分数,本质上等于把测试集"训练"进了模型选择。
  • 解法:严格遵守 train / val / test 三层划分。调参、选模型、早停、选 epoch 全部在验证集上;测试集只在最终验收时用一次。如果必须多次看测试集,至少记录每次结果并意识到指标已经被污染。

三、学习率错误:过大、过小、不调度 ​

症状原因解法
loss 一开始就 NaN/发散学习率过大范围测试法找到合理区间(见训练配方与调参)
loss 下降极慢、像"爬行"学习率过小提高 10 倍重试;仍不行检查数据/模型
训练集拟合了,验证集上不去学习率无调度、后期震荡加余弦退火或按验证集折半
Adam 不收敛忘了 Adam 也要调学习率(默认 1e-3 是起点不是终点)范围测试 + 调度

学习率是"最便宜、影响最大"的超参数,出错时优先查它。

四、忘记归一化 ​

  • 症状:相同配置下 loss 偏高、收敛慢或震荡;换初始化结果剧烈变化。
  • 原因:输入范围不对(0–255 或 0–1 混用),权重初始化的尺度与输入尺度不匹配(机理见初始化与归一化)。
  • 解法:训练前检查 x.min()/x.max(),统一归一化到 [0,1] 或标准化;图像任务直接用预训练模型时,必须用该模型配套的均值和标准差(如 ImageNet 的 (0.485, 0.456, 0.406))。

五、类别不平衡处理不当 ​

  • 症状:准确率看着不错(90%+),但小类别全是 0;F1 惨不忍睹。
  • 原因:默认的交叉熵损失被多数类主导,模型学会"全猜多数类"。
  • 解法(按性价比排序):
    1. 改评估指标:别用准确率,用 per-class precision/recall/F1(见评估实践)。
    2. 加权损失:CrossEntropyLoss(weight=class_weights),权重与类别频率成反比。
    3. 重采样:过采样少数类 / 欠采样多数类(注意别对测试集做)。
    4. 换损失:Focal Loss(难样本加权)用于极端不平衡的检测任务。
    python
    # 加权损失示例
    counts = torch.bincount(train_labels, minlength=n_classes).float()
    class_weights = 1.0 / counts
    class_weights = class_weights / class_weights.sum() * n_classes
    criterion = nn.CrossEntropyLoss(weight=class_weights.to(device))

六、不固定种子导致不可复现 ​

  • 症状:同一份代码两次运行结果不一样;"昨天 90%,今天 87%";无法向别人/自己解释成绩。
  • 原因:随机初始化、数据 shuffle、增强采样、cuDNN 算法选择都引入随机性,全都没被固定。
  • 解法:固定 torch/numpy/random 种子 + cudnn.deterministic(代码模板见训练配方与调参)。注意:固定种子是让"实验可复现",不代表模型更好——评估时反而应该用多个种子取均值(评估方法见评估实践)。

七、在验证集上过拟合 ​

  • 症状:验证集分数持续上涨直到 99%,换数据立刻暴跌。
  • 原因:验证集太小或用了太多次——每个 epoch 都"看"验证集选最佳 checkpoint,选多了验证集本身也被拟合。
  • 解法:
    • 验证集要足够大且固定(不小于数千样本)。
    • 早停、选 checkpoint 时不要反复在同一批验证样本上挑最好的;至少保留一个更"隔离"的最终测试集。
    • 用小验证集做高方差决策(如比较两个相近配置)时,必须用置信区间判断差异是否显著。

八、BatchNorm 陷阱 ​

两个高频翻车点:

1. batch size 太小

  • 症状:训练 loss 震荡、验证指标差且不稳定;batch 内统计量噪声大。
  • 原因:BatchNorm 用当前 batch的均值和方差,batch 太小(<16)时估计失真;推理时又用累计统计量,训练/推理行为撕裂。
  • 解法:batch 尽量 ≥32;小 batch 场景换 GroupNorm/LayerNorm,或增大 batch(可用梯度累积)。

2. 训练/推理行为不一致

  • 症状:训练集上指标正常,测试时整体偏差。
  • 原因:评估时忘记 model.eval(),BatchNorm 还在用 batch 统计量而不是累计统计量。
  • 解法:评估前调用 model.eval();反向传播前确认 model.train()。
python
# 评估必须这样写
model.eval()
with torch.no_grad():
    ...  # 推理

九、混合精度(AMP)bug ​

  • 症状:训练中途偶发 NaN;某些层输出全 0;CPU 与 GPU 结果不一致。
  • 原因:float16 动态范围小(上界约 65504),大激活、小梯度都会溢出/下溢;自定义算子没写 fp16 内核,被静默降级或出错。
  • 解法:
    • 用官方 GradScaler 而非手写缩放;
    • 对自定义损失/算子,检查其 fp16 行为,必要时强制该部分用 fp32;
    • 出现 NaN 时,先用纯 fp32 复现,确认问题是否与 AMP 相关;
    • 逐个排除:torch.autocast 的 enabled=False 快速对比。

十、预训练模型微调学习率过大 ​

  • 症状:微调前期 loss 剧烈升高后不再恢复,或精度反而不如冻结主干时。
  • 原因:预训练权重已经很接近好的解,用从头训练的默认学习率(如 1e-3)会把学好的表征"冲毁"。
  • 解法:微调学习率比训练新模型小 1–2 个量级(1e-4~1e-5);先冻结主干只训分类头,再解冻微调(配方见训练配方与调参,实例见渐进式教程:三版跑起来)。

十一、显存 OOM 处理 ​

  • 症状:CUDA out of memory;或训练到一半爆显存。
  • 原因:模型参数量 + 中间激活 + 优化器状态超出显存;或显存泄漏(每 batch 都在累积计算图)。
  • 解法(按性价比):
    1. 减小 batch(同时注意学习率联动)或梯度累积。
    2. 混合精度:显存立省 30–50%。
    3. 梯度检查点:空间换时间。
    4. 检查显存泄漏:loss.backward() 前是否 zero_grad();循环里是否保留了 .item() 之外的 Tensor 引用。
    5. 用 torch.cuda.max_memory_allocated() 定位峰值来源。

别忽略泄漏

"OOM 但减小 batch 没用"通常是显存泄漏——某处把计算图或 Tensor 引用保留在了循环里。用 max_memory_allocated 观察内存是否随 batch 单调增长来确诊。

十二、模型"看起来对"的假象 ​

  • 症状:指标合格、demo 演示正常,但换数据/换场景就崩;或 demo 里一直在"作弊"。
  • 原因:评估设计缺陷掩盖了真实问题——常见有:
    • 训练测试同分布:测试集与训练集来自同一批次,掩盖分布漂移。
    • 数据泄漏(见第一条)让指标虚高。
    • 在测试集上调参(见第二条)。
    • 只看平均值:平均指标被"好区段"掩盖了长尾灾难。
    • demo 特供:演示数据经过挑选,正好都是模型擅长的样本。
  • 解法:构建分布外测试集(不同时段/地域/人群的数据);按难度/置信度分桶看指标;把"一个模型只能在自己的测试分布上工作"当作默认假设,主动去验证它何时失效。可解释性工具能进一步揭示模型到底"靠什么"做对,见可解释性与公平性。

附:反模式速查表 ​

反模式一句话特征核心对策
数据泄漏指标好得不真实统计量只从训练集算
评估泄漏测试集反复看三层划分,测试只碰一次
学习率错误loss 不降或乱跳范围测试法
忘记归一化收敛慢且不稳检查输入范围
类别不平衡准确率高但小类全错加权损失/换指标
不固定种子结果不可复现固定全链路种子
验证集过拟合验证 99% 换数据崩固定大验证集+隔离测试集
BatchNorm 陷阱小 batch 震荡/训练推理不一致batch≥32、正确 eval()
AMP bug偶发 NaNGradScaler + 纯 fp32 对比
微调 lr 过大微调把模型训废学习率小 1–2 个量级
显存 OOM一爆再爆四件套:batch/AMP/checkpoint/查泄漏
假象看似都对其实不行分布外测试+分桶指标

延伸阅读 ​

参考资料 ​