第 23 章 · 四路横评:模型们其实差得不多
这一章要解决什么:把前面 17 章学过的模型放到同一张桌子上比一次。 这是全书的一个转折点 —— 你会得到一个尴尬但重要的结论。
最容易踩的坑:四路用了不同的划分或不同的预处理 → 数字放一起比毫无意义。横评的前提是"同一份划分、同一套指标、同一套特征"。
本章你需要完成的代码:demos/ch23-model-comparison.py
23.1 横评的前提:三个"同一"
先记住这一条,否则后面的表全是废的:
横评 = 同数据 + 同划分 + 同指标 + 同特征。少一个,比较就失效。
真实输出:
四路横评:同一份数据、同一套指标、同一份划分
训练 17,244 行 / 48 支设备
测试 3,995 行 / 12 支设备
两边设备有交集:False ← 必须是 False
提升模型:HistGB(替 XGBoost,300棵)四路用的是同一份 tr / te 索引:
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
tr, te = next(gss.split(X, y, groups=groups)) # ← 只划分一次
Xtr, Xte, ytr, yte = X.iloc[tr], X.iloc[te], y.iloc[tr], y.iloc[te]
# 四个模型都用这同一份 Xtr/Xte
for name, model in MODELS:
model.fit(Xtr, ytr)
pred = model.predict(Xte)
r2 = r2_score(yte, pred) # ← 都用这同一套 yte★ 为什么这件事是硬性要求
如果模型 A 用 random_state=42 划分、模型 B 用 random_state=7
↓
A 抽到 12 支"好预测"的设备,B 抽到 12 支"难预测"的
↓
A 的 R² 比 B 高 —— 但这个差距来自【抽签】,不是模型
↓
第 11 章已经量化过:换个 random_state,同一个模型的分差可达 0.036★ 而这里的陷阱更隐蔽:因为"更好的模型"赢了一点, 你会自然地归因于"模型更强",而不会想到是划分的差异。
点开:一个真实项目里的横评事故
场景:团队要选模型,三个人各跑一个,交上来三份结果。
小王:随机森林 R² = 0.913
小李:XGBoost R² = 0.928
小张:LSTM R² = 0.941结论:LSTM 最好,上 LSTM。
三个月后上线翻车。排查发现问题
| 人 | 数据划分 | 特征 | 预处理 |
|---|---|---|---|
| 小王 | 按时间切(正确) | 7 个 | 在 Pipeline 里缩放 |
| 小李 | 按时间切 | 12 个(多加了 5 个) | 全表缩放(泄漏) |
| 小张 | 按行随机(泄漏!) | 7 个 | 全表缩放(泄漏) |
三个人的数字根本不是一件事:
小张的 0.941 —— 按行随机划分,同一设备的相邻窗口跨集 → 虚高
小李的 0.928 —— 多加了 5 个特征(可能有用),但缩放泄漏
小王的 0.913 —— 唯一干净的那个★ 教训:
横评之前,先对齐"方法"再对齐"数字"。
一个可操作的检查清单(打印出来贴在工位上):
□ 四路用的是同一份划分吗?(同一组 tr/te 索引)
□ 四路用的是同一个 y 吗?(同一份测试标签)
□ 四路用的是同一套特征吗?(同一个 FEATS 列表)
□ 预处理都在 Pipeline 里吗?(防止逐路泄漏程度不同)
□ 都固定 random_state 了吗?
□ 指标是同一个函数吗?(别一个用 MAE 一个用 RMSE)这六条里任意一条没对齐,那张对比表就不能用。
23.2 四路横评的结果
真实输出:
模型 MAE RMSE R² 训练 单行
线性回归(+标准化) 0.00968 0.01217 0.95053 0.01s 0.000ms
HistGB(替 XGBoost,300棵) 0.00787 0.01034 0.96429 15.22s 0.063ms
随机森林(200棵) 0.00790 0.01023 0.96503 6.27s 0.022ms
高斯过程 GPR(600行,仅cycle) 0.01768 0.02288 0.82518 0.03s 0.005ms★ 先读前三个(这是一个公平的比较)
| 模型 | MAE | RMSE | R² | 训练耗时 |
|---|---|---|---|---|
| 线性回归(+标准化) | 0.00968 | 0.01217 | 0.95053 | 0.01 s |
| 梯度提升(HistGB 300 棵) | 0.00787 | 0.01034 | 0.96429 | 15.22 s |
| 随机森林(200 棵) | 0.00790 | 0.01023 | 0.96503 | 6.27 s |
★ 关键数字:
三个主流模型的 R² 极差 = 0.96503 - 0.95053 = 0.01449从最朴素的线性回归,到 200 棵树的随机森林,R² 只涨了 0.0145。
而 MAE 从 0.00968 降到 0.00790 —— 相当于"平均误差从 0.97 个百分点降到 0.79 个百分点"。
这个幅度值不值 6 秒训练时间? 看你后面会看到的"卡点在哪"。
点开:这张表里最该注意的三件事
① 线性回归就已经 0.9505 —— 它几乎赢了 85%
从 0 到 0.9505:靠的是"加特征"(7 个特征 vs 1 个特征)
从 0.9505 到 0.9650:靠的是"换模型"(线性 → 树)
↑
后者只贡献了 0.0145对比第 13 章的数字:
单特征线性 : 0.82070
七特征线性 : 0.94253 ← 加特征涨了 0.12183
七特征森林 : 0.96503 ← 换模型涨了 0.02250加特征带来的提升,是换模型带来的 5 倍以上。
② 随机森林和梯度提升几乎打平(0.96503 vs 0.96429),差距 0.0007
这推翻了一个常见的直觉:
常见直觉:"梯度提升是'更高级'的算法,应该更准"
↓
实测:两者只差 0.0007,而随机森林训练快 2.4 倍(6.27s vs 15.22s)
↓
结论:【在这个数据上,随机森林是更划算的选择】★ 这引出一个重要的工程观念:
"更强的算法"不等于"更好的选择"。 当两个模型的精度只差 0.001 时,训练时间、维护成本、可解释性才是决定因素。
③ 训练耗时差异巨大,而它常被忽略
线性回归 : 0.01 s
随机森林 : 6.27 s (627 倍)
梯度提升 : 15.22 s (1522 倍)在离线场景(每周跑一次报告)这无所谓; 在实时场景(每条数据都要预测)就完全不同 —— 不过要注意:
训练耗时 ≠ 预测耗时看"单行预测"那一列:线性回归是 0.000ms(微秒级)、随机森林 0.022ms。 预测都很快 —— 部署时更关心的是这个。
★ 一个容易混淆的点:
| 指标 | 什么时候重要 |
|---|---|
| 训练耗时 | 离线批处理、需要频繁重训(每天/每小时) |
| 单行预测耗时 | 在线服务、实时决策(有延迟预算) |
| 模型大小 | 边缘设备部署(内存有限) |
第 26 章选型时,这三个要分开考虑。
⚠️ 一个关于"耗时"的提醒:树模型的训练耗时在不同运行之间波动很大 (受并行调度和机器负载影响,同一设置可能差 2 倍以上)。 所以报耗时请报数量级("毫秒级 / 秒级 / 十秒级"),不要报"快 7.6 倍"这种精确倍数 —— 换个环境就不成立了。R² 不受这个影响,它是稳定的。
23.3 ★ 这一章真正的结论
真实输出:
★ 三个主流模型(线性/提升/森林)的 R² 极差 = 0.01449
→ 换模型带来的收益只有 0.0145;这个量级说明【卡点不在模型】★ 这是全书最重要的结论之一。
换个角度读这个数字:
数据里"可预测的部分"(R² 的上限)大约在 0.96 附近
↓
线性回归拿到了 0.9505(占上限的 98.7%)
随机森林拿到了 0.9650(占上限的 100%)
↓
从线性到森林,我们只挤出了 1.3% 的剩余空间
↓
剩下的 3.5%(1 - 0.965)去哪了?
↓
【是数据里的测量噪声 —— 任何模型都消不掉】为什么剩下的部分消不掉
第 10 章讲过这份数据的误差模型:每个通道有"每支设备固定的零点偏置"
- "每行独立的测量波动"。这两层误差都不是"特征 → 标签"的函数关系, 所以任何模型都无法从单行特征里把它们分离出来。
★ 这就是"卡点不在模型"的含义:
你换更强的模型(更深的树、更大的集成、甚至神经网络)
↓
能改善的只有那 1.3% 的空间
↓
而真正的水面下还有 3.5% 的噪声,和一大片"没被特征表达出来的信息"真实输出里的另一句提醒:
⚠️ GPR 的 R² 明显低,但它是【只喂 600 行 + 只用一个特征】——
它的卖点是 ±σ 置信带(第 24 章),不是精度。不能这样比。★ 这里有一个"如何读表格"的教训
GPR 的 R² 是 0.82518,看起来"最差"。但这个比较本身就是错的:
线性/森林/提升 : 17,244 行 训练, 7 个特征
GPR : 600 行 训练, 1 个特征
↑ ↑
少 28 倍 少 6 倍★ 一条通用纪律:
看到"某模型明显更差"时,先问一句"它们是在同等条件下比的吗"。
同样"看起来更好"也要问。这是这本书反复训练的反应。
点开:那"卡点不在模型"之后,该怎么办?
这是个关键的分岔路口,走错方向会浪费几周。
横评结果:换模型只有 0.0145 的收益
↓
三个可能的方向:
方向 A:继续换/调模型
→ 期望收益:很小(上限就在 0.96 附近)
→ 代价:时间
→ 结论:【不推荐】
方向 B:加数据
→ 期望收益:需要先看学习曲线(第 21 章)
→ 第 21 章实测:验证线在 8,000 行后就平了 → 【加数据收益也很小】
方向 C:改特征
→ 期望收益:可能很大(因为"没被表达出来的信息"就在这)
→ 代价:需要领域知识 + 试错
→ 结论:【推荐】★ 判据是第 21 章的学习曲线
验证线还在涨 → 选 B(加数据)
验证线已走平 → 选 C(改特征)
两者都不是 → 选 A(但收益有限)第 21 章的实测结论:
训练规模 训练 R² 验证 R² 差距
2832 0.9992 0.9473 +0.0519
5664 0.9990 0.9517 +0.0473
8496 0.9988 0.9579 +0.0409
11328 0.9986 0.9584 +0.0402
14160 0.9985 0.9582 +0.0403
↑
从 8,496 行起就基本不动了样本量翻 5 倍(2,832 → 14,160),验证 R² 只从 0.9473 涨到 0.9582(+0.011)。
所以:加数据的边际收益已经很低了。
★ 那"改特征"具体是什么意思?
不是"加更多同类的测量列",而是"构造出能表达【个体差异】和【变化趋势】的特征":
当前特征都是"这个窗口内的瞬时状态":
cycle、平均电压、平均温升、内阻、库仑效率、衰减速率、温度标准差
缺的是"这支设备【和历史相比】怎么样":
· 相对自身基线的偏移(这支设备现在 vs 它自己早期)
· 变化率(最近 100 圈 vs 前 100 圈)
· 与"同批次同龄设备"的比较★ 这就是"特征工程",而它是下一本书的主题。
这本书在第 27 章会把接力棒交出去,并说明"为什么现在该做特征,而不是继续换模型"。
而你现在已经知道了判断依据:
① 横评:换模型只有 0.0145 的收益 ← 这一章
② 学习曲线:验证线走平,加数据没用 ← 第 21 章
③ 所以:剩下的空间在特征里 ← 下一本书★ 一个可以带走的思维模式
遇到"效果不够好",不要问"换个什么模型",而要问:
"我现在离【数据的上限】还有多远?
而那个上限,是由什么决定的?"模型只决定"你离上限多近";上限本身由数据和特征决定。
23.4 四路横评的完整对照表
除了分数,选型还要看"能不能给不确定性"这类能力:
模型 速度 可解释性 不确定性 适用场景
线性 / 岭回归 快 高(系数即公式) 无 基线;可解释性要求高时首选
随机森林 中 中(重要性) 无 表格数据稳健起点,几乎不用调参
XGBoost / LightGBM 中 中低 无 精度优先;有缺失值也扛得住
高斯过程 GPR 慢(O(n³)) 低 ★ 有 ±σ 样本少、要给置信区间时
SVR / KNN 中 低 无 小样本;特征必须缩放★ 这张表的第四列是全书的伏笔:
前三路(线性/森林/提升):都不给不确定性
GPR :给 ±σ这就是第 24~26 章的主题 —— "精度"不是唯一的竞争维度。
点开:为什么"可解释性"在真实项目里常常比精度更重要
一个真实的决策场景:
模型 A:R² = 0.965,随机森林
你可以说:"预测值是 0.815。"
工程师问:"为什么?"
你:"……树太多了,说不清。"
模型 B:R² = 0.950,线性回归
你可以说:"预测值是 0.815 —— 主要是循环次数贡献了 -0.02,
库仑效率贡献了 +0.01。"
工程师:"那我如果改善哪个环节,能提升?"
你:"按模型看,库仑效率的边际影响更大。"★ 模型 B 的 R² 更低,但它能支撑决策。
什么场景需要可解释性:
| 场景 | 为什么需要 |
|---|---|
| 要指导改进(该优化哪个环节?) | 需要知道"哪个因素影响大" |
| 要说服别人(凭什么信你的预测?) | 需要一个能讲通的逻辑 |
| 要合规(为什么拒绝了这个申请?) | 法规要求给出理由 |
| 要排错(模型为什么错了?) | 需要定位是哪个特征导致的 |
什么场景不需要:
纯预测、不需要解释、没有监管要求
例:天气预报的数值模式、推荐系统的排序分★ 一个折中方案:用简单模型做解释,用复杂模型做预测
# 用随机森林预测(精度高)
rf = RandomForestRegressor(n_estimators=200, random_state=42).fit(Xtr, ytr)
# 用线性回归解释方向(系数可读)
lin = LinearRegression().fit(Xtr, ytr)
for name, coef in zip(FEATS, lin.coef_):
print(f"{name:<30} {coef:>+10.5f}")这就是"双模型策略" —— 一个负责准,一个负责讲。 第 26 章会再看到它(GPR + GBDT 两条腿走路也是同一思路)。
23.5 常见坑
① 四路用了不同的划分 → 差距可能全部来自"抽签"(分差可达 0.036)
② 四路预处理泄漏程度不同(有的在 Pipeline 里,有的全表 fit)→ 不可比
③ 四路特征数不同却不说明 → 读者以为在比模型,其实在比特征
④ 用训练耗时评价"速度" → 部署时关心的是【单行预测耗时】和模型大小
⑤ 看到"某模型明显更差"就下结论 → 先问"它们是在同等条件下比的吗"(GPR 就是反例)
⑥ 横评完就结束 → 真正的结论是"卡点不在模型",要接着往特征走23.6 完整示例与输出
cd code
python demos/ch23-model-comparison.py真实输出:
四路横评:同一份数据、同一套指标、同一份划分
训练 17,244 行 / 48 支设备
测试 3,995 行 / 12 支设备
两边设备有交集:False ← 必须是 False
提升模型:HistGB(替 XGBoost,300棵)
模型 MAE RMSE R² 训练 单行
线性回归(+标准化) 0.00968 0.01217 0.95053 0.01s 0.000ms
HistGB(替 XGBoost,300棵) 0.00787 0.01034 0.96429 15.22s 0.063ms
随机森林(200棵) 0.00790 0.01023 0.96503 6.27s 0.022ms
高斯过程 GPR(600行,仅cycle) 0.01768 0.02288 0.82518 0.03s 0.005ms
★ 三个主流模型(线性/提升/森林)的 R² 极差 = 0.01449
→ 换模型带来的收益只有 0.0145;这个量级说明【卡点不在模型】
★ 最优:随机森林(200棵)(R² 0.96503)本章练习
练习 1:亲手做一次公平横评
在同一份按设备划分的数据上,比较三个模型:线性回归(+标准化)、随机森林(100 棵)、HistGB。
要求:
- 四路必须共用同一组
tr/te索引; - 打印 MAE / RMSE / R² / 训练耗时;
- 计算三个 R² 的极差,并写一句话结论。
参考答案
import time
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor, RandomForestRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score, root_mean_squared_error
from sklearn.model_selection import GroupShuffleSplit
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
df = pd.read_csv("data/battery/soh_features.csv")
FEATS = ["cycle", "mean_charge_voltage_v", "mean_temp_rise_c", "resistance_mohm",
"coulombic_eff", "cap_fade_rate_ah_per_100", "temp_std_c"]
X, y, groups = df[FEATS], df["soh"], df["cell_id"]
# ① 只划分一次,三路共用
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
tr, te = next(gss.split(X, y, groups=groups))
Xtr, Xte, ytr, yte = X.iloc[tr], X.iloc[te], y.iloc[tr], y.iloc[te]
print(f"训练 {len(tr):,d} 行 / 测试 {len(te):,d} 行")
MODELS = [
("线性回归(+标准化)", Pipeline([("s", StandardScaler()), ("m", LinearRegression())])),
("随机森林(100棵)", RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)),
("HistGB", HistGradientBoostingRegressor(max_iter=300, learning_rate=0.05,
max_depth=4, random_state=42,
early_stopping=False)),
]
print(f"\n{'模型':<22}{'MAE':>10}{'RMSE':>10}{'R²':>10}{'训练':>9}")
r2s = []
for name, model in MODELS:
t0 = time.time()
model.fit(Xtr, ytr)
fit_s = time.time() - t0
pred = model.predict(Xte)
r2 = r2_score(yte, pred)
r2s.append(r2)
print(f"{name:<22}{mean_absolute_error(yte, pred):>10.5f}"
f"{root_mean_squared_error(yte, pred):>10.5f}{r2:>10.5f}{fit_s:>8.2f}s")
print(f"\n三路 R² 极差 = {max(r2s) - min(r2s):.5f}")期望输出:
训练 17,244 行 / 测试 3,995 行
模型 MAE RMSE R² 训练
线性回归(+标准化) 0.00968 0.01217 0.95053 0.01s
随机森林(100棵) 0.00791 0.01026 0.96487 2.54s
HistGB 0.00787 0.01034 0.96429 11.26s
三路 R² 极差 = 0.01434③ 一句话结论
从最朴素的线性回归到 100 棵随机森林,R² 只从 0.95053 涨到 0.96487(极差 0.0143)。 这说明在这份数据上,【换模型】能挤出的空间已经很小了 —— 真正卡住效果的不是模型,而是特征与数据本身的噪声上限。
★ 三个观察
观察一:线性回归就已经吃掉了大部分可预测性
线性回归 0.95053 / 最好 0.96487 = 98.5%
↑
最朴素的模型拿到了 98.5% 的成绩观察二:随机森林 100 棵 vs 200 棵,收益已经很小
100 棵:0.96487
200 棵:0.96503 ← 翻倍棵树只涨 0.0002这是第 17 章讲过的"饱和"现象。
观察三:耗时差异很大,但这是【训练】耗时
如果关心的是"上线后每条数据预测要多久",
就要单独测 predict 的耗时(本 demo 也打印了那一列)★ 这个练习最重要的收获
不是"我学会了横评",而是"我看到了换模型的天花板"。
这个认知会让你在真实项目里少走很多弯路 —— 当有人建议"换个更高级的模型试试"时,你可以回答:
"我们先测一下换模型的收益上限。 如果横评的极差只有 0.01,那说明问题不在模型。"
练习 2:找出"横评无效"的三个错误
下面三段代码各有一处让横评失效的错误,请指出并改正:
# 版本 A
r2_lr = r2_score(yte, Pipeline([("s", StandardScaler()),
("m", LinearRegression())]).fit(Xtr, ytr).predict(Xte))
r2_rf = r2_score(yte, RandomForestRegressor(random_state=42).fit(X, y).predict(X))# 版本 B
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"线性 {r2_score(yte, LinearRegression().fit(Xtr, ytr).predict(Xte)):.5f}")
print(f"森林 {r2_score(yte, RandomForestRegressor(random_state=42).fit(Xtr, ytr).predict(Xte)):.5f}")# 版本 C
scores = {}
scores["线性"] = r2_score(yte, LinearRegression().fit(X, y).predict(Xte))
scores["森林"] = r2_score(yte, RandomForestRegressor(random_state=42).fit(Xtr, ytr).predict(Xte))参考答案
版本 A 的错误:用整表训练 + 在训练集上评估
r2_rf = r2_score(yte, RandomForestRegressor(...).fit(X, y).predict(X))
# ↑ ↑ ↑
# 用的是测试标签 用【全量数据】训练 在【全量数据】上预测问题有两层:
① 训练用了 X(含测试集)→ 泄漏
② 预测也是 X,而 yte 只对应测试部分 → 形状/语义完全对不上改正:
r2_rf = r2_score(yte, RandomForestRegressor(random_state=42).fit(Xtr, ytr).predict(Xte))版本 B 的错误:按行随机划分
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42)
# ↑ 没有 groups=问题:
同一支设备的相邻窗口一个进训练、一个进测试
↓
模型"背邻居"就能答对
↓
两个模型的分数都虚高,而且【虚高程度可能不同】
(线性模型背不了那么细,树可以 → 差距被夸大)
↓
结论完全不可信改正:
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
tr, te = next(gss.split(X, y, groups=df["cell_id"]))
Xtr, Xte, ytr, yte = X.iloc[tr], X.iloc[te], y.iloc[tr], y.iloc[te]版本 C 的错误:线性模型用全量训练,森林用训练集 —— 不公平
scores["线性"] = r2_score(yte, LinearRegression().fit(X, y).predict(Xte))
# ↑ 全量(含测试集)
scores["森林"] = r2_score(yte, RandomForestRegressor(...).fit(Xtr, ytr).predict(Xte))
# ↑ 只用训练集问题:
线性模型见过测试数据 → 它的分数被抬高了
↓
如果你因此得出"线性模型和森林差不多"的结论 → 错
(真实差距应该更大,因为线性模型本来更弱)★ 这是最隐蔽的一种"不公平":
因为泄漏的模型分数更高,你会误以为"它更强"。
改正:三路统一用 Xtr。
★ 三个版本的共同教训
| 版本 | 错误 | 后果 |
|---|---|---|
| A | 训练用全量 + 预测用全量 | 完全错位 |
| B | 按行随机划分 | 分数虚高,且虚高程度因模型而异 |
| C | 各路训练集不同 | 不公平,且偏向泄漏更严重的那路 |
一个总结性的判断法则:
横评时,除了"模型"这一个变量,其他所有条件都必须完全相同。
一个可执行的检查:把四路的训练代码写成循环,而不是四份复制粘贴。
# ✅ 用循环,从结构上保证公平
for name, model in MODELS:
model.fit(Xtr, ytr) # ← 只有 Xtr/ytr,不可能写错
r2 = r2_score(yte, model.predict(Xte))复制粘贴四份代码,就是在邀请自己写出版本 C 那种错误。
练习 3:给三个场景选模型
用第 23.4 节的对照表(速度 / 可解释性 / 不确定性)为下面三个场景各选一个模型, 并说明取舍的理由:
- 离线周报:每周一算一次全批设备的健康度分布,给质量部门看;
- 产线实时看板:每来一组传感器数据就要在 100 ms 内刷新 SOH;
- 退役决策支持:工程师要决定"这批设备该不该换",需要一个可信的风险区间。
参考答案
| 场景 | 推荐 | 理由 |
|---|---|---|
| 1 离线周报 | 梯度提升 / 随机森林 | 精度优先;一周跑一次,几分钟无所谓;没有延迟要求 |
| 2 实时看板 | 线性回归 / 小树模型 | 单行预测耗时优先(线性 0.000ms vs 森林 0.022ms);精度损失很小(0.9505 vs 0.9650) |
| 3 退役决策 | 高斯过程 GPR | 需要 ±σ;精度虽低,但能触发"人工复检",比一个自信的错数字安全 |
★ 逐个展开
场景 1:离线周报 → 精度优先
约束:无延迟要求,一周一次
优化目标:精度
↓
选 R² 最高的(本数据上是随机森林 0.96503 或 HistGB 0.96429)
↓
可以用全量数据训练(不需要考虑推理延迟)★ 但要注意一个细节:
"每周算一次全批分布" → 用【全量数据】训练是可以的(这不是评估,是生产)
但如果你要报"模型的预测精度" → 那次评估必须用留出的测试集这两个用途不要混(第 11 章讲过:调参看 CV,报成绩看留出集)。
场景 2:实时看板 → 速度优先
约束:100 ms 内必须出结果
↓
先算一下预算:
线性回归 0.000 ms ← 完全没问题
随机森林 0.022 ms ← 也没问题!
GPR O(n³) ← 取决于训练集大小,可能超预算⚠️ 注意:这个数据上"速度"其实不是瓶颈 —— 所有模型的单行预测都在 1 ms 以内。
所以真实的选择逻辑会变成:
如果所有模型都满足延迟要求 → 那"速度"这个维度就不构成约束
↓
于是回归到"精度优先" → 选随机森林
↓
【而不是机械地"实时场景就要选线性模型"】★ 这是一个很重要的思维修正:
约束只有在"真的构成约束"时才有意义。先量化,再取舍 —— 不要凭场景标签做决定。
场景 3:退役决策 → 不确定性优先
约束:决策错误代价高(提前退役浪费,推迟退役有风险)
↓
"0.815" 这个数字本身不够 —— 你需要知道"这个 0.815 有多可信"
↓
GPR 给出:"0.808 ± 0.109" → 区间跨过 0.80 的退役线 → 【触发人工复检】
↓
随机森林给出:"0.815" → 你只能凭感觉决定★ 这就是"精度不是唯一指标"的具体体现
随机森林 R² 0.96503 vs GPR R² 0.82518
↑ ↑
精度赢了 0.14 但它不会说"我不确定"
↓
在【需要承担决策后果】的场景里,后者常常更值得推荐★ 三段建议的写法(练习要求"给业务方看")
① 离线周报:
"推荐用梯度提升模型。它的平均误差约 0.8 个百分点,
一周跑一次,多花几分钟不影响交付。"
← 只说业务关心的(误差多大、多快),不说 R² / HistGB
② 实时看板:
"推荐用线性回归。预测延迟在微秒级,远低于 100 ms 要求;
平均误差 0.97 个百分点,比最复杂的模型只差 0.18 个百分点。
这个精度差在实时监控场景里可以接受,换来的是极低的延迟和运维成本。"
← 量化了"付出了什么、换来了什么"
③ 退役决策:
"推荐用高斯过程模型。它除了给出预测值,还会给出不确定度:
例如'0.81 ± 0.11'表示这次预测不够确定,建议人工复检。
单纯追求精度的模型只会给出一个数字,无法支撑'是否要换'这类决策。"
← 说清了"为什么精度不是唯一标准"★ 三条建议的共同点
① 都说了【数字】(误差、延迟、区间)
② 都说了【取舍】(付出了什么、换来了什么)
③ 都【没有出现】R²、HistGB、O(n³) 这类术语这是"面向业务方"写作的标准。
第 26 章会让你再写一次,那时你会用上四维(精度×速度×可解释性×不确定性)。
面向工程实践:横评是"决策",不是"排名"
① 横评的产出不是一张排名表,而是一个决策依据
❌ 错误的产出:"随机森林最好(R² 0.96503),就它了。"
→ 没有回答"为什么不是别的",也没有回答"差多少算差"
✅ 正确的产出:
"三个主流模型的 R² 极差 0.0145,说明换模型的空间已经不大。
随机森林在精度(0.96503)、训练速度(6.27s)、维护成本上综合最优;
GPR 精度更低但提供 ±σ,留给需要风险区间的场景。"
→ 说明了差距量级、选择理由、以及"不选别的"的原因② 一个实用的"横评报告"模板
## 横评设置
- 数据:<什么数据,多少行,多少实体>
- 划分:<按什么划分,训练/测试各多少>
- 指标:<哪几个,为什么选它们>
- 共同条件:同一份 tr/te 索引、同一套特征、预处理都在 Pipeline 里
## 结果
<table: 模型 | MAE | RMSE | R² | 训练耗时 | 单行预测>
## 结论
1. 极差:<最好的 - 最差的> → 说明<卡点在模型还是别处>
2. 选定:<哪个>,理由:<精度/速度/不确定性哪个是主导约束>
3. 未选:<其他>,理由:<为什么不选>
## 局限
- <这份数据是合成的 / 样本量 / 有没有覆盖某个子群>
- <结论的适用范围>★ 第 ④ 节"局限"最容易被省略,但最重要:
没有局限说明的结论 = 无法判断它什么时候会失效③ 最后一次强调这一章的核心
横评的结果通常是:换模型的收益有限
↓
这不是"横评没用",恰恰相反 ——
↓
它帮你【排除】了一个方向,让你把精力投到真正有收益的地方(特征)★ 在工程上,"排除"和"发现"一样有价值:
"我们试了四路模型,收益上限 0.015"
→ 这个结论能阻止团队在未来三个月里继续换模型
→ 它的价值等于省下了三个月的工期"做了实验,得到否定结果"不是失败,是有效信息。
这本书教你做实验,也教你从否定结果里读出方向。