🏠 总目录📚 本教程 挑战C · 过拟合实验室
📑 本页目录(点开跳转)

挑战项目 C · 过拟合实验室

5–7 天 | 难度 ★★★★★ | 前置:第 5、10、11 章


🎯 为什么选这个项目

前两个挑战是「造东西」,这个是「造故障」

   常规学习:跟着教程做对 → 遇到真实故障时两眼一抹黑
   这个项目:故意做错 10 次 → 每种故障的"手感"刻进肌肉记忆

   你要亲手制造并诊断:
   💀 训练 loss 变 NaN
   💀 loss 完全不动
   💀 CV 分数 0.95,测试集 0.60
   💀 换个种子结果差 10%
   💀 验证 loss 比训练还低
   💀 GPU 利用率只有 20%
   ... 共 10 种

   然后为每一种写出【症状 → 诊断步骤 → 修复】

为什么这个能力最值钱:教程教你成功路径,但真实工作 80% 的时间在处理故障。 而故障诊断几乎无法从书上学——只能靠见过。这个项目让你在一周内"见过"十次。


🧪 十个必做实验

每个实验的流程都一样:制造故障 → 观察症状 → 用第 11 章流程诊断 → 修复 → 记录

A 组:训练不动(第 9、11 章)

B 组:虚假的高分(第 5 章)⭐ 最重要的一组

C 组:不可信的结论(第 5、11 章)


🔍 十种故障的「症状指纹」(做之前先别看,做完对照)

👀 点开对照(建议自己先做出来再看)
# 故障 一眼识别的特征
1 学习率过大 loss 前几步先降后暴涨,然后 NaN。梯度范数指数增长
2 梯度断流 loss 一动不动(不是降得慢,是逐位相同);p.gradNone 或全 0
3 zero_grad loss 前期正常,随后越来越震荡;梯度范数随 step 线性增长(梯度在累加)⭐
4 梯度消失 后几层梯度正常,前几层小 3~6 个数量级;前几层的权重几乎不变
5 预处理泄漏 CV 分数高但测试掉一大截;在纯噪声数据上仍能跑出 >60% ⭐ 这是决定性证据
6 时间泄漏 随机 CV 很高、按时间切分骤降;未来信息被用来预测过去
7 分组泄漏 KFold 远高于 GroupKFold;同一实体的记录分散在训练和验证里
8 目标泄漏 CV 突然飙到 0.99+;特征重要性里一个特征占 90% 以上
9 种子彩票 同配置多次运行分数分布很宽;"提升"落在这个宽度里就不是提升
10 测试集污染 你选的"最优配置"在全新测试集上明显掉分

💡 注意 #3 和 #1 的区别:都是 loss 变糟,但 忘 zero_grad 是"梯度范数随 step 线性增长"(累加), lr 过大是"指数增长"(正反馈)。打印梯度范数就能分开。


🧪 每个实验的统一记录格式(六个数字)

import torch
def log_step(model, loss, opt):
    gn = sum(p.grad.norm()**2 for p in model.parameters()
             if p.grad is not None) ** 0.5
    return {
        "loss":       loss.item(),
        "grad_norm":  gn.item(),                       # ⭐ 最有信息量的一个
        "param_norm": sum(p.norm()**2 for p in model.parameters()) ** 0.5,
        "lr":         opt.param_groups[0]["lr"],
        "n_dead":     sum((p.grad == 0).all().item()   # 有多少层完全没梯度
                          for p in model.parameters() if p.grad is not None),
        "has_nan":    bool(torch.isnan(loss)),
    }

🔑 grad_norm 是这个项目里最重要的一个数字。 十个故障里有五个能靠它一眼分辨(爆炸、消失、断流、累加、正常)。 养成每次训练都记它的习惯。


📓 产出:一份故障诊断手册

每个实验记录成这个格式:

## 故障 N:<名字>

### 症状(我观察到什么)
- loss 曲线:...
- 梯度范数:...
- 训练/验证差距:...

### 复现代码
```python
# 最小复现

诊断路径(我是怎么找到原因的)

  1. 先查了 X,排除
  2. 打印 Y,发现异常
  3. 定位到 Z

根因

修复

一眼识别的特征 ⭐ 最有价值的部分

「如果下次看到 ___,八成就是这个问题」

---

## 🧠 ADHD 任务切分

- [ ] **Day 1** 搭实验脚手架:一个能快速切换"故障开关"的训练脚本 + 统一的日志记录(第 11 章的六个数字)
- [ ] **Day 2** A 组实验 1–4(训练不动)
- [ ] **Day 3–4** B 组实验 5–8(虚假高分)⭐ 花最多时间在这
- [ ] **Day 5** C 组实验 9–10(不可信结论)
- [ ] **Day 6–7** 整理成诊断手册 + 写「我以前踩过但没意识到的坑」

### 脚手架建议

```python
BUGS = {                      # 故障开关,一行切换
    "none": lambda cfg: cfg,
    "huge_lr": lambda cfg: {**cfg, "lr": 10.0},
    "no_zero_grad": lambda cfg: {**cfg, "zero_grad": False},
    "leak_scaler": lambda cfg: {**cfg, "fit_scaler_on_all": True},
    # ... 其余变体照这个形状加
}

def run(bug="none"):
    cfg = BUGS[bug](DEFAULT_CFG)
    log = train(cfg)          # 返回每 epoch 的六个数字
    plot(log, title=bug)      # 存图
    return log

✅ 通关标准

  1. 10 个故障全部复现成功,每个都有最小复现代码
  2. 每个故障有完整诊断路径(不是直接写答案,是记录你怎么找到的)
  3. 每个故障有「一眼识别的特征」 —— 这是手册的核心价值
  4. 实验 9 得出了你的噪声基准,并说明它如何改变你对"提升"的判断
  5. 实验 10 量化了测试集污染的代价

🏆 加分


💭 做完之后

你会发现自己看别人的训练日志时,能一眼看出不对劲。 这个能力没法从教程学,只能靠"见过"。你用一周换来了通常要一年才能积累的故障直觉。

🔗 这和智能体工程挑战项目 B的精神一致: 优化的前提是能正确测量;而能正确测量的前提,是知道测量会怎么骗你。


✅ 检查点

  1. 这个项目和前两个挑战的根本区别是什么?为什么这个能力最值钱?
  2. 「忘了 zero_grad」和「学习率过大」都会让 loss 变糟,怎么一眼区分?
  3. 为什么说「在纯噪声数据上仍能跑出 >60%」是预处理泄漏的决定性证据?
  4. 目标泄漏在特征重要性上会留下什么痕迹?
  5. grad_norm 为什么是这个项目里最重要的数字?
  6. 实验 9(种子彩票)的产出是什么?它怎么改变你对"提升"的判断?
  7. 实验 10 在模拟什么真实工作中的错误?
  8. B 组为什么最重要?
👀 答案
  1. 前两个是造东西,这个是造故障。最值钱是因为:教程只教成功路径,但真实工作 80% 时间在处理故障,而故障诊断几乎无法从书上学,只能靠见过——这个项目让你一周内见过十次。
  2. 看梯度范数的增长方式:忘 zero_grad 是线性增长(梯度在累加);lr 过大是指数增长(正反馈)。
  3. 因为纯噪声数据的真实可分性是 50%(就是随机)。任何显著高于 50% 的分数只可能来自泄漏——没有别的解释。这是唯一能"证明"泄漏存在的实验设计。
  4. 特征重要性里一个特征占 90% 以上,且 CV 突然飙到 0.99+。这是排查泄漏最快的手段。
  5. 因为十个故障里有五个能靠它一眼分辨:爆炸(指数增长)、消失(前层小几个数量级)、断流(为 0 或 None)、累加(线性增长)、正常。
  6. 产出是你的噪声基准(同配置 20 个种子的分数极差)。之后任何"提升"如果落在这个宽度里,就不是提升,是随机波动
  7. 模拟反复在测试集上调参——每轮都看测试集分数并据此选择,测试集就慢慢变成了"第二个验证集",报告的分数是乐观的。这是真实工作中最普遍也最隐蔽的错误。
  8. 因为 A 组和 C 组的故障你能看见(loss 不动、NaN),而 B 组的故障看起来像成功——分数很高,你会高高兴兴上线,然后崩。看不见的错误才是最贵的。

🛑 可以停在这里

走神救援

故意造 10 种故障再诊断,把手感刻进肌肉记忆(前两个挑战是造东西,这个是造故障——故障诊断无法从书上学,只能靠见过)。A组训练不动(NaN/梯度断流/忘zero_grad/梯度消失);B组虚假高分(最重要——因为A、C组的故障你看得见,B组的故障看起来像成功):预处理泄漏/时间泄漏/分组泄漏/目标泄漏;C组不可信结论:种子彩票(跑20个种子得噪声基准,之后落在这个宽度里的"提升"都不算)、测试集慢性污染。⭐ grad_norm 是最重要的数字,十个故障有五个靠它一眼分辨:爆炸=指数增长、忘zero_grad=线性增长(在累加)、断流=0或None、消失=前层小几个数量级。纯噪声数据上跑出>60% 是泄漏的决定性证据(真实可分性只有50%);目标泄漏的痕迹=一个特征占重要性90%以上。产出=一份诊断手册,核心是每个故障的「一眼识别特征」。加分:让别人随机注入故障做盲测。


🎓 三个挑战都做完之后

这三个 + 第 17 章的三个基础项目,是一份能证明"基本功扎实"的作品集。

首页 看进度,或去全景导论第 16 章选下一个方向。

打卡记录保存在你的浏览器里,首页能看到总进度