14 · 实战与挑战项目
⏱ 项目 1–2 天 / 挑战 4–6 天 | ⭐ 理论要靠动手才能变成直觉
🎯 一句话
这一章的项目和基础教程不同:它们的产出不是模型,而是「验证一个理论断言」的实验报告。
💡 为什么这种项目对 ADHD 特别友好: 每个任务都有一个明确的、二元的成功判据(数字对上了 / 没对上), 不像调模型那样"好像好了一点"——反馈清晰,多巴胺来得快。
🧰 开工前:一个通用的实验脚手架
import numpy as np, pandas as pd
rng = np.random.default_rng(0) # ⭐ 固定种子,否则结论不可复现
def run(setting_fn, n_repeat=20, **kw):
"""把一个实验重复 n 次,返回 均值±标准差。
⭐ 单次实验的结论不可信——理论断言是关于【期望】的。"""
rs = [setting_fn(rng, **kw) for _ in range(n_repeat)]
return np.mean(rs), np.std(rs)
⚠️ 本章最常见的失败原因:只跑一次就下结论。 理论说的是期望行为,单次实验的随机波动可能完全盖过效应。 所有实验都重复 ≥20 次再看均值。
🥉 项目一:把三个理论现象亲手做出来(1 天)
目标:本教程的三个核心断言,你自己跑数字验证一遍。
T1(60min)验证第 1 章:最小二乘 = 高斯 MLE
- [ ] 造带高斯噪声的数据
y = 3x + 2 + ε, ε~N(0, σ²) - [ ] 路线 A:
np.linalg.lstsq最小二乘 - [ ] 路线 B:用
scipy.optimize.minimize直接最大化高斯对数似然 - [ ] 对比两组系数 —— 应该到小数点后 6 位一致 ⭐
- [ ] 换成拉普拉斯噪声(重尾)+ 加 3 个离群点 → 对比 MSE 和 L1(
HuberRegressor/ 分位数回归)的拟合线
# 路线 B 的核心(写出来才有感觉)
import numpy as np
from scipy.optimize import minimize
def neg_log_lik(params, X, y):
w, b, log_s = params[:-2], params[-2], params[-1]
resid = y - (X @ w + b)
s2 = np.exp(2 * log_s)
return 0.5 * np.sum(resid**2) / s2 + len(y) * log_s # ⭐ 去掉常数项
🎯 该看到的现象:高斯噪声下两条线完全重合; 换成有离群点的数据后,MSE 的线被离群点拽歪,L1 的线纹丝不动 —— 这就是"损失函数 = 你对噪声的假设"的可视化证明。
T2(60min)验证第 2 章:L2 正则 = 高斯先验
- [ ] 手写 MAP 目标:
对数似然 + 高斯先验的对数 - [ ] 和
Ridge(alpha=λ)对比系数 —— 应该一模一样 ⭐ - [ ] ⭐ 验证 λ = σ²/τ²:固定先验方差 τ²,把噪声 σ² 从 0.1 扫到 10, 每次用 CV 找最优 λ → 画 λ_best vs σ² 的散点,应该接近一条过原点的直线
🎯 该看到的现象:噪声越大,最优正则越强。 这不是调参玄学,是 λ = σ²/τ² 这个公式在现实里的样子。
T3(45min)验证第 5 章:维度灾难
- [ ] 复现那四个数字实验(球体积占比、壳层集中、最近/最远距离比、样本需求)
- [ ] 加一个新的:在 d = 2/10/50/200 上跑 kNN 分类(保持样本数不变)
- [ ] 同时记录:准确率、以及"最近邻距离 / 平均距离"这个比值
🎯 该看到的现象:准确率下降的同时,那个比值趋近于 1 —— kNN 崩溃的直接原因是"最近邻不再更近",两条曲线应该同步。
📊 参考数字:d=500 时 (max−min)/min ≈ 0.19,d=20 的球体积占比 ≈ 0.0000025%
T4(45min)写报告
三个断言 + 你的数字 + 一句话结论。
✅ 通关标准
数值上吻合(不是"差不多",是小数点后几位一致),并能解释不一致的地方来自哪。
⚠️ 三个常见坑(卡住了再点)
- T1 系数对不上 → 检查你的负对数似然是否漏了
n·log σ项,或scipy优化没收敛(把tol调小、换method="L-BFGS-B")。 - T2 系数对不上 → sklearn 的
Ridge默认不惩罚截距,而且它的目标是‖y−Xw‖² + α‖w‖²(没有 1/2n)。手写时要对齐这两点 ⭐ - T3 kNN 准确率不降 → 你可能让"有用的特征数"也跟着增加了。正确做法是只加噪声维度,保持信号维度不变。
🥈 项目二:核方法与 SVM 实验(1–2 天)
- [ ] T1 (45min) 手动验证第 6 章:随机生成 a、b ∈ ℝ²,
验证
(a·b)²确实等于φ(a)·φ(b),其中 φ(x) = [x₁², √2·x₁x₂, x₂²] - [ ] T2 (60min) 在同心圆 / 月牙数据上:线性 SVM vs RBF 核 SVM,画决策边界
- [ ] T3 (60min) ⭐ C 与 γ 的网格实验:
C ∈ {0.1,1,10,100} × γ ∈ {0.01,0.1,1,10}, 画一张 4×4 的决策边界图,亲眼看到那条"对角线" - [ ] T4 (45min) 验证"只有支持向量决定模型":训练后删掉所有非支持向量, 用剩下的点重训,对比两条决策边界
- [ ] T5 (45min) 测 SVM 的 O(n²):n = 1000/5000/20000,记录训练时间,画增长曲线
# T4 的核心:三行就能验证一个理论断言 ⭐
import numpy as np
sv_idx = clf.support_
clf2 = SVC(kernel="rbf", C=C, gamma=g).fit(X[sv_idx], y[sv_idx])
print("决策函数最大差异:", np.abs(clf.decision_function(Xg)
- clf2.decision_function(Xg)).max())
✅ 通关标准
| 实验 | 该看到什么 |
|---|---|
| T3 网格 | 左上角欠拟合、右下角过拟合,好的模型在一条对角线上 —— 说明 C 和 γ 会互相补偿,必须联合调 ⭐ |
| T4 支持向量 | 删掉非支持向量后决策边界几乎不变(差异 < 1e-10) |
| T5 时间曲线 | 明显超线性 —— log-log 图上斜率约为 2 |
💡 T5 的意义:这条超线性曲线就是 SVM 输给深度学习的原因。 不是效果不好,是 n 一大就跑不动——神经网络是 O(n)。
🥇 挑战项目:泛化理论的实证检验(4–6 天)
难度 ★★★★★ | 这个项目做的是科研级别的事:用实验检验理论的适用边界。
本教程告诉你:VC 界说"真实误差 ≤ 训练误差 + O(√(d_VC/m))"
也告诉你:它解释不了深度学习
这个项目让你【亲手测出这个界在哪里开始失效】
阶段一:验证界在经典模型上成立(Day 1–2)
- [ ] T1 用线性分类器(d_VC = d+1,已知的精确值)
- [ ] T2 固定 d,扫 m(样本数从 20 到 10000,对数间隔): 画「训练误差 / 测试误差 / 两者差距」三条曲线
- [ ] T3 验证差距是否大致按 √(d/m) 衰减 ——
画
gapvs√(d/m)的散点,看是不是接近过原点的直线 - [ ] T4 验证经验法则:m = 10·d_VC 时差距有多大?
🎯 该看到的现象:散点接近直线(相关系数 > 0.9), 但斜率远小于 1 —— 因为 VC 界是最坏情况,实际比它乐观 10~100 倍。 "形状对、常数松"就是及格。
阶段二:找到界失效的地方(Day 3–4)
- [ ] T5 换成 MLP,逐步增加宽度(参数量从 ≪ m 到 ≫ m)
- [ ] T6 ⭐ 复现双下降:横轴参数量(对数刻度)、纵轴测试误差, 看能否画出那条"升—降—再降"的曲线
- [ ] T7 记录 VC 界的预测值 vs 实际测试误差,看界从哪里开始变得毫无意义
🔧 双下降跑不出来?四个必要条件(点开)
双下降不是随便跑就能看到的,需要:
- 必须有噪声 —— 干净数据上双下降很弱。给 10~20% 的标签加噪声 ⭐
- 参数量要真的跨过插值阈值 —— 要扫到"训练误差 = 0"的那个点两侧
- 不能有太强的正则 —— 关掉 weight decay 和 early stopping(它们会抹平第一个峰)⭐
- 训练要充分 —— 每个点都训到收敛,不然峰会被"没训够"掩盖
💡 峰值出现在参数量 ≈ 样本数的地方。这是找它的坐标。
阶段三:探究为什么(Day 5–6)
- [ ] T8 随机标签实验(Zhang et al. 2017 的经典实验): 把标签完全打乱,看网络能不能把训练集背下来
- 能背下来 → 说明它的容量足够记住任意标签 → VC 维确实巨大
- 但同一个网络在真实标签上泛化得很好 → 矛盾正在这里 ⭐
- [ ] T9 对比:真实标签 vs 随机标签的训练收敛速度 —— 真实标签快得多,说明数据的结构在帮忙
- [ ] T10 写报告:界在哪成立、在哪失效、你的解释
🔑 T8 是整个项目的高潮。它证明了一件让整个领域重新思考的事: "模型能记住任意标签"和"模型泛化得好"可以同时成立。 而经典 VC 理论认为前者应该排除后者 —— 理论在这里断了。
✅ 通关标准
- 阶段一的
gap ~ √(d/m)关系在图上可见(形状对即可,常数不必对) - 成功复现双下降(至少看到测试误差的二次下降趋势)
- 随机标签实验:网络能拟合随机标签(训练准确率 → 100%)
- 报告里有一段你自己的关于"为什么 VC 界失效"的分析
🏆 加分
| 加分项 | 你在验证什么假说 |
|---|---|
| 测量权重范数 / Hessian 特征值随训练的变化 | "有效复杂度" ≠ 参数量 |
| 对比 SGD 和全批量 GD 的泛化差距 | "SGD 的隐式正则"假说 ⭐ |
| 画不同宽度下的 loss landscape 切片 | "平坦极小值泛化更好"假说 |
💡 这个项目的价值:你会亲身体会到「理论有适用边界,而找到边界比记住结论更重要」。 这是研究者和调包侠的分水岭。
📝 报告模板
# 理论断言:____
## 断言的精确表述(公式)
## 实验设计(怎么造数据、控制什么变量、重复多少次)
## 结果(表 + 图,带误差棒)
## 断言成立吗?
- [ ] 完全成立
- [ ] 在 ___ 范围内成立,超出后失效
- [ ] 不成立,因为 ___
## 我原本以为 ___,实际上 ___ ← 最有价值的一节
💡 最后一节别跳过。记录"哪里和你预期不同",比记录结论有用十倍—— 那个差距就是你的直觉正在被修正的地方。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 上线之后 17 | ⚠️ 脚手架要先解决可复现:种子、版本、数据快照,否则结论不可信 |
| Kaggle 04 | 实验管理与调参的工程做法 |
| ML基础 18 | 把第 12 章的推导真正写成代码 —— 这两个项目是一对 |
✅ 检查点
- 为什么本章所有实验都要重复 ≥20 次?
- T1 里"MSE 被离群点拽歪、L1 纹丝不动"证明了什么?
- T2 里 λ_best vs σ² 应该是什么关系?它对应哪个公式?
- T3 里 kNN 准确率下降的直接原因是什么?该同时观察什么指标?
- C-γ 网格实验里那条"对角线"说明了什么调参原则?
- 为什么 SVM 的时间曲线是它输给深度学习的原因?
- 复现双下降的四个必要条件是什么?峰值在哪出现?
- 随机标签实验(T8)为什么是整个项目的高潮?
👀 答案
- 因为理论断言是关于期望行为的,单次实验的随机波动可能完全盖过效应。只跑一次就下结论是本章最常见的失败原因。
- 证明损失函数 = 你对噪声分布的假设。MSE 对应高斯(轻尾,对离群点敏感),L1 对应拉普拉斯(重尾,抗离群点)。
- 近似正比(过原点的直线),对应 λ = σ²/τ²。含义:噪声越大,最优正则越强。
- 直接原因是最近邻不再更近——"最近邻距离/平均距离"这个比值趋近于 1。应该和准确率曲线同步观察,两条线的同步是因果的证据。
- C 和 γ 会互相补偿(一个变强可以被另一个变弱抵消),所以必须联合调,不能一个一个单独调。
- 曲线明显超线性(log-log 斜率约 2,即 O(n²)),n 一大就跑不动;神经网络是 O(n)。不是效果问题,是可扩展性问题。
- ①必须有标签噪声(10~20%)②参数量要跨过插值阈值两侧 ③关掉 weight decay 和 early stopping ④每个点都训到收敛。峰值在参数量 ≈ 样本数处。
- 因为它证明了"能记住任意标签"和"泛化得好"可以同时成立,而经典 VC 理论认为前者应该排除后者——理论在这里断了。
🛑 可以停在这里
⚡ 走神救援
三个项目,产出是验证理论的实验报告不是模型。所有实验重复≥20次(理论说的是期望)。①亲手验证最小二乘=高斯MLE(离群点下 MSE 被拽歪、L1 不动 = 损失函数就是噪声假设)、L2=高斯先验(系数应逐位一致,注意 Ridge 默认不罚截距)、λ_best vs σ² 应是过原点直线(λ=σ²/τ²,噪声越大正则越强)、维度灾难(kNN崩溃的直接原因是"最近邻不再更近")②核技巧 (a·b)²=φ(a)·φ(b)、C-γ对角线说明必须联合调参、删掉非支持向量边界不变、SVM的O(n²)就是它输给深度学习的原因 ③⭐挑战:实测VC界在哪失效——线性模型上验证 gap~√(d/m)(形状对、常数松就及格),MLP上复现双下降(四条件:有噪声/跨过插值阈值/关正则/训到收敛,峰在参数量≈样本数处),随机标签实验是高潮——网络能背下随机标签却在真实标签上泛化好,经典理论认为这两者互斥,理论在这里断了。报告里"我原本以为实际上"这节最有价值。
下一节 👉 附录A-数学速查.md