📑 本页目录(点开跳转)
挑战项目 B · 四种解法对决
⏱ 4–6 天 | 难度 ★★★★☆ | 前置:第 2–6、15–16 章
🎯 为什么选这个项目
第 2 章说了「没有免费的午餐」,但那只是一句话。 这个项目让你亲手把它变成一张数据表——从此选型不再靠感觉。
同一批数据,四种解法正面对决:
① 线性模型(逻辑回归/岭回归)
② 树模型(LightGBM / GBDT)
③ 神经网络(MLP)
④ 预训练模型微调(如果数据是文本/图像)
× 在【三种不同规模的数据量】上各跑一遍
(100 行 / 1000 行 / 全量)
→ 你会得到一张【什么时候该用什么】的决策表
这张表比任何博客的"经验之谈"都可靠,因为是你自己测的
这个项目训练的是选型判断力 —— 工程师最值钱、也最难自学的能力。
🔑 它同时是一个理论的实证检验: 数学原理第 11 章 NFL 说 "算法的专长是守恒的"——一个模型在某类问题上变强,必然在另一类上变弱。 这个项目就是把那句话画成一张交叉的曲线图。
🧭 开工前:一个能省你两天的建议
❌ 常见的做法:先跑线性模型,跑完再跑树,跑完再跑NN……
→ 跑到第三天发现实验函数有 bug,前面全白干
✅ 正确的做法:Day 1 只做一件事——
让【4 个模型 × 1 个数据量 × 1 个数据集】跑通,哪怕分数很烂
→ 确认框架没问题,再去填满整个矩阵 ⭐
这就是"先跑通全流程"在实验项目上的版本。
📊 实验设计
数据集(选 2–3 个,覆盖不同类型)
| 类型 | 推荐数据集 | 获取 |
|---|---|---|
| 表格·分类 | Adult 收入预测 / Titanic | fetch_openml("adult") |
| 表格·回归 | California Housing | fetch_california_housing() |
| 文本·分类 | 20 Newsgroups | fetch_20newsgroups() |
| 图像·分类 | CIFAR-10 子集 | torchvision.datasets |
💡 至少要有一个表格数据集和一个非表格数据集——结论会完全不同,这正是重点。
实验矩阵
100 行 1000 行 全量
线性模型 ? ? ?
树模型 ? ? ?
神经网络 ? ? ?
预训练微调 ? ? ?
每格记录:CV 分数 ± 标准差 / 训练时间 / 调参耗时
🧠 ADHD 任务切分
第一阶段:搭实验框架(Day 1)⭐ 先把架子搭好,后面全是填空
- [ ] T1 (90min) 统一的实验函数:输入
(数据集, 模型, 数据量),输出(CV分数, 标准差, 训练耗时)。所有预处理进 Pipeline(第 5 章) - [ ] T2 (45min) 结果记录:每次实验追加一行到 CSV(模型/数据集/样本量/分数/标准差/耗时/超参)
- [ ] T3 (30min) ⭐ 先测噪声底线:同一配置跑 5 个种子,记下分数的标准差。这个数字决定后面所有比较是否有意义
第二阶段:跑满矩阵(Day 2–3)
- [ ] T4 (90min) 线性模型:记得标准化,调正则强度
- [ ] T5 (90min) 树模型:调
max_depth/learning_rate/n_estimators(第 4 章) - [ ] T6 (120min) 神经网络:用第 11 章的诊断流程(先过拟合 10 样本),调层数/宽度/dropout/lr
- [ ] T7 (90min) 预训练微调(文本/图像数据集上)
- [ ] T8 (45min) ⭐ 公平性检查:每个模型的调参预算是否相当?(给树模型调了 50 组、给 NN 只调了 5 组,那结论无效)
第三阶段:深挖差异(Day 4)—— 项目的精华
- [ ] T9 (60min) 画「数据量 vs 分数」曲线,四条线放一张图。交叉点在哪?
- [ ] T10 (60min) 分析为什么:
- 小数据上谁赢?为什么?(提示:归纳偏置,第 14 章)
- 表格 vs 文本,排名有变化吗?为什么?
- [ ] T11 (45min) 成本维度:把「训练时间」和「调参耗时」也画出来 —— 有时第二名才是正确选择
- [ ] T12 (45min) 融合实验:把最好的 2–3 个模型加权平均,能超过单个最好的吗?(第 4 章 Stacking)
第四阶段:产出决策表(Day 5–6)
- [ ] T13 (90min) 写出你自己的选型决策树(不是抄的)
- [ ] T14 (60min) README:实验矩阵表、四条曲线图、决策树、「和我预期不同的三个发现」
🔨 实验框架骨架
import time, numpy as np, pandas as pd
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.pipeline import Pipeline
RESULTS = []
def run_experiment(name, pipeline, X, y, n_samples=None, seeds=(0,1,2)):
"""统一实验入口 —— 所有对比都必须走这个函数,保证公平"""
if n_samples:
idx = np.random.RandomState(0).choice(len(X), n_samples, replace=False)
X, y = X[idx], y[idx]
scores, t0 = [], time.time()
for s in seeds:
cv = StratifiedKFold(5, shuffle=True, random_state=s)
scores.extend(cross_val_score(pipeline, X, y, cv=cv, scoring="roc_auc"))
rec = dict(model=name, n=len(X), mean=np.mean(scores),
std=np.std(scores), secs=time.time()-t0)
RESULTS.append(rec)
print(f"{name:<20} n={len(X):<7} {rec['mean']:.4f} ± {rec['std']:.4f}"
f" ({rec['secs']:.1f}s)")
return rec
# 用完导出
# pd.DataFrame(RESULTS).to_csv("results.csv", index=False)
🕳️ 专属坑
| 坑 | 说明 |
|---|---|
| 调参预算不公平 ⭐ | 最容易毁掉整个结论。给每个模型相同的调参次数或时间 |
| 忘了测噪声底线 | 没有基准方差,你无法判断 0.005 的差距是不是真的 |
| 小数据上只跑一次 | 100 行数据的方差极大,必须多种子多折 |
| 预处理没进 Pipeline | 第 5 章那个 76.5% 的教训 |
| 只比分数不比成本 | 训练慢 10 倍换 0.2% 提升,工程上往往不划算 |
| 数据集选得太单一 | 全是表格数据的话,你只会得出"树模型最强"这个片面结论 |
| 小数据用了不同的子集 | 100 行的那一格如果每个模型抽的是不同 100 行,比较无效。固定抽样种子 ⭐ |
| 文本/图像给树模型的输入不合理 | 把 TF-IDF 直接喂 LightGBM 可以,但别拿原始像素喂它再说"树模型不行" |
⚖️ 「调参预算公平」具体怎么保证
这是本项目最容易被质疑、也最值得写进 README 的一点。三种做法:
| 做法 | 说明 | 推荐度 |
|---|---|---|
| 等次数 | 每个模型都随机搜 30 组 | ⭐ 最简单,够用 |
| 等时间 | 每个模型都给 10 分钟调参预算 | 更公平,但结果依赖机器 |
| 都用默认参数 | 谁都不调 | 也是一个有效的对比维度 ⭐ |
from sklearn.model_selection import RandomizedSearchCV
BUDGET = 30 # ⭐ 所有模型共用这一个数字
search = RandomizedSearchCV(pipe, param_dist, n_iter=BUDGET,
cv=cv, scoring="roc_auc", random_state=0)
💡 "都用默认参数"这一列很有信息量: 它回答了「开箱即用哪个最强」——而在真实工作里, 你常常没有时间给每个模型调 30 组参数。
✅ 通关标准
- 完整的实验矩阵(4 模型 × 3 数据量 × 至少 2 个数据集),每格带 ± 标准差
- 测过噪声底线,并用它判断哪些差距是真实的
- 调参预算公平,且在 README 里说明了怎么保证的
- 画出「数据量 vs 分数」四条曲线,能指出交叉点并解释原因
- 产出你自己的选型决策树
- 记录了至少 3 个「和预期不同」的发现
🏆 加分
- 加一个维度:特征数量(10 / 50 / 全部),看高维时排名怎么变
- 测「加噪声标签」的鲁棒性:哪个模型最抗噪?
- 把结论和 Kaggle 第 21 章任务决策路径对照,哪里一致、哪里不一致?不一致的地方去想为什么
💡 预期你会发现的(做完再看,别剧透自己)
👀 点开有剧透
- 表格数据上,树模型在几乎所有数据量下都赢,且几乎不用调参
- 神经网络在小数据上明显更差(归纳偏置弱,第 14 章)
- 文本/图像上,预训练微调碾压一切,且数据越少差距越大
- 线性模型在极小数据上反而可能最好(第 1 章那个逻辑回归)
- 训练时间差距可能有 100 倍,而分数差距只有 1%
- 融合能提升,但提升量常常小于噪声
✅ 检查点
- 这个项目和哪条理论直接对应?它把那条理论变成了什么?
- Day 1 该做什么?为什么不是"先把线性模型跑完"?
- 为什么必须先测噪声底线?没测会怎样?
- 「调参预算公平」有哪三种做法?为什么"都用默认参数"这一列有价值?
- 小数据那一格最容易犯什么错?
- 为什么必须至少有一个非表格数据集?
- 「成本也是维度」是什么意思?举一个例子。
- 四条曲线的交叉点说明了什么?
👀 答案
- 对应 NFL「算法的专长是守恒的」——一个模型在某类问题上变强必然在另一类上变弱。这个项目把那句话画成了一张交叉的曲线图。
- 让 4 个模型 × 1 个数据量 × 1 个数据集先跑通,哪怕分数很烂。因为如果先跑完一整列才发现实验函数有 bug,前面全白干。
- 因为没有基准方差你无法判断 0.005 的差距是不是真的。同一配置跑 5 个种子记下标准差,之后所有比较都以它为尺子。
- 等次数(都搜 30 组,最简单够用)、等时间(更公平但依赖机器)、都用默认参数。第三种有价值是因为它回答了「开箱即用哪个最强」——真实工作里你常常没时间给每个模型调 30 组。
- 每个模型抽的是不同的 100 行——这样比较无效。必须固定抽样种子。而且 100 行的方差极大,必须多种子多折。
- 因为全是表格数据的话,你只会得出"树模型最强"这个片面结论。排名会随数据类型改变,这正是 NFL 的可见证据。
- 训练时间和调参耗时也要记进表。例子:训练慢 10 倍换 0.2% 提升,工程上往往不划算——有时第二名才是正确选择。
- 说明不同数据量下的最优模型不同——小数据上归纳偏置强的模型(线性/树)赢,大数据上偏置弱的(NN)赢。交叉点就是你的选型分界线。
🛑 可以停在这里
⚡ 走神救援
四种解法(线性/树/NN/预训练微调) × 三种数据量 × 至少两类数据集,跑满实验矩阵得出自己的选型决策表。⭐ 它是 NFL「算法的专长守恒」的实证版——把那句话画成一张交叉曲线图。Day1 只做一件事:让 4×1×1 先跑通(别先跑完一整列再发现框架有bug)。铁律:①统一实验函数保证公平②先测噪声底线(没有基准方差就判断不了0.005是不是真的)③调参预算必须相当——等次数/等时间/都用默认参数(这列回答"开箱即用哪个最强",很有价值) ④预处理进Pipeline ⑤小数据要固定抽样种子,否则各模型抽的不是同一批行 ⑥成本也是维度(慢10倍换0.2%不值,第二名常是正确答案) ⑦必须有非表格数据集否则只会得出"树模型最强"的片面结论。精华在第三阶段:画数据量vs分数四条曲线找交叉点——那就是你的选型分界线。
下一个挑战 👉 20-挑战项目C-过拟合实验室.md