📑 本页目录(点开跳转)
02 · 离线好不等于线上好
⏱ 38 分钟 | ⭐ 上线前的最后一道关
🎯 一句话
离线评估回答的是「在过去的数据上,它比旧模型准」; 线上要回答的是「在未来的流量上,它比旧模型好」。 这两句话之间有五道坎,这一章逐个拆。
🕳️ 一、五道坎
结果对照
⚠️ 二、坎 ①:评估数据和线上流量不是一回事
最常见的三种不一致
| 不一致 | 后果 | 怎么发现 |
|---|---|---|
| 时间不一致 | 用了未来数据(ML基础 05的泄漏) | 严格按时间切分 |
| 样本选择偏差 ⭐ | 评估集里全是旧模型曝光过的样本 | 见下 |
| 人群不一致 | 评估用全量,上线只给新用户 | 按上线人群切一份评估 |
🔑 把第二条讲透:幸存者偏差
关键信息
🔗 这就是推荐算法 02 反馈闭环在评估上的后果。
⭐ 务实的缓解办法(不需要改架构): 留一小份随机流量(比如 1%)—— 这部分用户看到的东西不由模型决定, 用它做评估集,就没有幸存者偏差。 代价是这 1% 的体验会差一点,换来的是一把没被污染的尺子。
📏 三、坎 ②:排序指标 ≠ 用户实际看到的
关键信息
换成位置敏感的指标:
| 指标 | 关心什么 | 什么时候用 |
|---|---|---|
| AUC | 全局排序 | 粗排、召回;或样本极不平衡时 |
| NDCG@K ⭐ | 前 K 个的排序质量,位置有折扣 | 精排、搜索 |
| Recall@K | 前 K 个里捞回了多少 | 召回阶段(推荐算法 18) |
| MRR | 第一个正确答案排第几 | 问答、只需一个答案的场景 |
| Precision@1 | 第一个对不对 | 单坑位(开屏、语音助手) |
💡 一个实测过的数字(推荐算法 12 里跑过):
NDCG@3 完美排序 = 1.0;把第 1 和第 3 交换,NDCG@3 就掉到 0.92 —— 而 AUC 几乎不动。 同一个改动,两个指标给出完全不同的评价。
⚠️ AUC 还有一个陷阱:它对分数的绝对值不敏感。
对照
模型 A 预测:[0.90, 0.10] 真实:[1, 0]
模型 B 预测:[0.51, 0.49] 真实:[1, 0]
AUC 完全一样(都是 1.0)✅
但如果你要用分数做【出价】或【阈值决策】——B 是灾难 💀
⭐ 这时候要看【校准度】而不是排序:
预测 0.7 的那批样本,实际正例率是不是真的 70%
🔗 需要精确概率的场景(广告出价、风控、期望收益计算) 必须额外看校准曲线,AUC 高不代表概率对。 常用修法:Platt scaling / isotonic regression 做后校准。
💰 四、坎 ③:模型指标 ≠ 业务指标
信息关系
建立这条连接的做法:
操作步骤
- 前几次 A/B,同时记录【模型指标变化】和【业务指标变化】
- 攒够 5-10 组,画个散点图
- 得到你这个场景的经验换算率
- 例:「历史上 AUC +0.005 大致对应 CTR +1%」
- ⭐ 有了它,以后离线看到 AUC +0.001 就知道:
- 不值得上线,提升会被噪声淹没
💡 这是个高回报的一次性投入: 它让你在做实验之前就能判断「这个改动值不值得占一个实验位」—— 而实验位通常是团队最稀缺的资源。
🚦 五、上线前的检查清单(可以照抄)
信息关系
⭐ 最后一条最容易漏,也最重要: 在实验开始前就写下失败标准。 否则实验跑出负向时,人会本能地找理由继续跑 —— 这在统计上叫 peeking,会让你的结论完全不可信(第 12 章)。
💀 六、一个完整的翻车复盘:离线 +0.02,线上 −1.5%
这是坎 ① 最典型的形态,值得逐步看。
结果对照
排查过程(三天,走了两条弯路):
| 第几步 | 猜测 | 验证结果 |
|---|---|---|
| 1 | 训练/推理不一致? | 逐字段比对,全部一致 ❌ |
| 2 | 延迟变高导致超时降级? | P99 从 38ms 到 41ms,降级次数 0 ❌ |
| 3 | 评估集怎么来的? ⭐ | 找到了 ✅ |
信息关系
| 人群 | 流量占比 | 旧模型 AUC | 新模型 AUC | 差 |
|---|---|---|---|---|
| 有 7 日点击 | 34% | 0.84 | 0.87 | +0.03 |
| 无 7 日点击 | 66% | 0.79 | 0.74 | −0.05 💀 |
| 全量加权 | 100% | 0.807 | 0.784 | −0.023 ⭐ |
⭐ 三条可以带走的教训: 1. 离线评估集的构造 SQL,每一个 WHERE 都要有人负责解释。 一句三年前的过滤条件,能让所有后续实验的结论都失真。 2. 永远同时看「全量」和「分人群」的离线指标。 只看一个数是危险的。 🔗 这就是第 14 章辛普森悖论在离线评估上的形态。 3. 排查时先查最便宜的假设:这次前两步各花了一天, 而"打开评估集的 SQL 看一眼"只要十分钟。 🔗 第 11 章会给一个排序好的排查清单。
📐 七、校准:什么时候必须看它,怎么看
第三节说了 AUC 不管分数的绝对值。什么时候这件事真的会要命:
| 你拿分数干什么 | 校准重要吗 | 说明 |
|---|---|---|
| 只做排序(推荐列表、搜索) | 🟡 不太重要 | 只要顺序对就行 |
| 卡阈值做决策(风控通过/拒绝) | ✅ 重要 | 阈值 0.7 的含义必须稳定 |
算期望收益(p × 客单价) |
✅ 很重要 ⭐ | 概率错了,期望全错 |
广告出价(bid = pCTR × pCVR × 价值) |
✅ 致命 ⭐ | 见下 |
💡 一个有具体数字的例子:
算一算
某广告模型换代后:AUC 0.78 → 0.80(涨了)
但 pCTR 系统性高估 1.8 倍(预测 5%,实际 2.8%)
→ 出价 = pCTR × 价值,出价整体高 1.8 倍
→ 抢到更多曝光,消耗涨 40% ← 看起来是好事 🎉
→ 但实际点击没跟上
→ ROI 从 3.2 掉到 1.7 ← 💀 一个月后财务才发现
⭐ 注意这个事故的形状:排序变好了,业务变差了, 而且中间指标(消耗、曝光)全是涨的 —— 这让它特别难被发现。
🔨 怎么量校准:ECE + 校准表
import numpy as np, pandas as pd
def calibration_table(p, y, bins=10):
"""p: 预测概率, y: 0/1 真实标签。返回分箱校准表 + ECE"""
# ⭐ 按【分位数】切箱,不是等宽——等宽切在长尾场景会有空箱
edges = np.unique(np.quantile(p, np.linspace(0, 1, bins + 1)))
idx = np.clip(np.digitize(p, edges[1:-1]), 0, len(edges) - 2)
rows, ece, n = [], 0.0, len(p)
for b in range(len(edges) - 1):
m = idx == b
if m.sum() == 0:
continue
pred, real, cnt = p[m].mean(), y[m].mean(), int(m.sum())
rows.append((b, cnt, round(pred, 4), round(real, 4), round(pred - real, 4)))
ece += cnt / n * abs(pred - real) # ⭐ ECE = 按样本量加权的平均偏差
tbl = pd.DataFrame(rows, columns=["箱", "样本数", "平均预测", "实际正例率", "偏差"])
return tbl, ece
结果对照
修法(按代价从低到高):
| 方法 | 做法 | 适合 |
|---|---|---|
| Platt scaling | 在验证集上拟合一个 sigmoid | 偏差是单调、平滑的 |
| Isotonic regression ⭐ | 拟合一个单调阶梯函数 | 偏差形状复杂;要求验证集够大(>10k) |
| 分人群分别校准 | 每个人群一套校准器 | 不同人群偏差方向不同时 ⭐ |
| 重新训练 | 换损失函数 / 改采样 | 前面都不行时 |
⚠️ 两个高频坑:
| 坑 | 后果 | 怎么修 |
|---|---|---|
| 在训练集上做校准 | 校准器学到的是过拟合后的分数分布,线上完全不成立 💀 | 必须用独立的验证集 |
| 负采样后忘了修正 ⭐ | 训练时把负样本采样到 1/10,预测概率整体被放大约 10 倍 | 用采样率反推:$p = \frac{p'}{p' + (1-p')/w}$ |
⭐ 第二条在广告和推荐里极其常见:负采样是标配,而采样只影响概率、不影响 AUC —— 所以它能一路通过所有离线评估,直到有人拿分数去出价。
🔗 八、和站内其他章的关系
| 相关的地方 | 和这一章的关系 |
|---|---|
| ML基础 05数据泄漏 | 坎 ① 的时间不一致 |
| 推荐算法 02反馈闭环 | 幸存者偏差的根源 |
| 推荐算法 12 NDCG / A/B | 坎 ② 和坎 ④ 的推荐场景版 |
| Kaggle 01评价指标 | 竞赛视角的指标选择 |
| 数据这一关 13 · 评测集也是数据 | 这四处讲的是各自场景怎么评测;评测集本身怎么造、多大够、有没有被污染、评分器准不准、结果该怎么报 在那一章 ⭐ |
✅ 检查点
- 从「离线 AUC 更高」到「长期确实更好」,中间有哪五道坎?
- 什么是评估里的幸存者偏差?根源是什么?务实的缓解办法是什么?
- 为什么排序场景不该只看 AUC?NDCG@3 那个例子说明了什么?
- AUC 高但分数不能用来做决策,是什么问题?怎么修?
- 模型指标和业务指标的换算率怎么建立?为什么值得做?
- 上线前检查清单里最容易漏的是哪条?漏了会怎样?
- 「离线 +0.02 线上 −1.5%」那个事故的真因是什么?它给出的三条教训是什么?
- 什么场景下校准是致命的?举那个广告出价的数字例子,说明它为什么难被发现。
- ECE 怎么算?三档判读标准是什么?
- 校准的两个高频坑是什么?为什么负采样那个坑能一路通过所有离线评估?
👀 答案
- ①评估数据≠线上流量 ②排序指标≠用户实际看到的 ③模型指标≠业务指标 ④你的模型不是唯一变量 ⑤短期涨≠长期好。
- 评估集里全是旧模型曝光过的样本,新模型想推的东西评估集里根本没有 —— 你在旧模型划定的范围里比较。根源是反馈闭环。缓解:留 1% 随机流量做评估集,这部分不由模型决定,是把没被污染的尺子。
- 因为 AUC 关心整个列表的排序,而用户只看前几个——第 800 和 900 名谁在前对用户毫无意义。NDCG@3 例子:交换第 1 和第 3 位,NDCG@3 从 1.0 掉到 0.92,而 AUC 几乎不动——同一改动两个指标给出完全不同的评价。
- 校准问题:AUC 只看排序、对分数绝对值不敏感。预测 [0.51,0.49] 和 [0.90,0.10] 的 AUC 一样,但用于出价/阈值决策时前者是灾难。修法:看校准曲线 + Platt scaling / isotonic regression 后校准。
- 前几次 A/B 同时记录模型指标和业务指标的变化,攒 5-10 组画散点,得到经验换算率。值得做是因为能在做实验之前判断改动值不值得占一个实验位,而实验位是最稀缺的资源。
- 「在实验开始前就写下失败标准」。漏了的话,实验跑出负向时人会本能找理由继续跑(peeking),结论完全不可信。
- 真因是评估集构造 SQL 里一句三年前的
WHERE user_click_cnt_7d > 0,把 66% 的低活用户过滤掉了。新模型在活跃用户上 AUC 0.84→0.87,但在低活用户上 0.79→0.74,全量加权后其实是 −0.023。三条教训:①评估集的每一个 WHERE 都要有人负责解释 ②永远同时看全量和分人群指标 ③排查时先查最便宜的假设(前两步各花一天,打开 SQL 看一眼只要十分钟)。 - 广告出价(
bid = pCTR × 价值)最致命。例子:AUC 0.78→0.80 但 pCTR 高估 1.8 倍 → 出价高 1.8 倍 → 消耗涨 40%、曝光涨,看起来全是好事,但点击没跟上,ROI 从 3.2 掉到 1.7,一个月后财务才发现。难发现是因为排序变好了、中间指标全是涨的。 - 按分位数分箱,每箱算「平均预测」和「实际正例率」,ECE = 按样本量加权的平均绝对偏差。三档:<0.01 基本可用;0.01~0.05 排序可用但别算期望;>0.05 不要做任何数值决策。
- ①在训练集上做校准(学到的是过拟合后的分数分布,线上完全不成立)②负采样后忘了修正(负样本采样到 1/10,预测概率整体放大约 10 倍)。第二个能通过所有离线评估,是因为采样只影响概率、不影响 AUC——直到有人拿分数去出价。
🛑 可以停在这里
⚡ 走神救援
离线好不等于线上好,中间有五道坎:评估数据不是线上流量、排序指标不是用户看到的、模型指标不是业务指标、你不是唯一变量、短期不是长期。
⭐ 第一道坎的核心是幸存者偏差:评估集全是旧模型曝光过的样本,而新模型想推的东西根本不在里面。务实解法是留一小份随机流量当评估集——代价是这部分体验差一点,换一把没被污染的尺子。
⭐ 第二道坎:AUC 关心整个列表的排序,而用户只看前几个——把第一和第三对调,前几名的指标明显掉,AUC 几乎不动。⚠️ AUC 对分数的绝对值也不敏感:两组差距悬殊的分数可以有一样的 AUC,但要拿分数去出价或卡阈值时,其中一组是灾难。
⭐ 第三道坎没有通用换算公式:模型指标到业务指标的兑换率必须自己用几次 A/B 实测建立——有了它才能在占用实验位之前判断值不值得做。
⭐ 清单里最容易漏的一条:实验开始前就写下失败标准——否则跑出负向时人会找理由继续跑,结论就不可信了。
💀 那次完整复盘最值得记:离线涨、线上跌,真因是评估集 SQL 里一句三年前的过滤条件。⭐ 拆开看是新模型在活跃用户上更好、在低活用户上更差,而低活占大多数,加权之后其实是负的。三条教训:每一个
WHERE都要有人能解释、永远同时看全量和分人群、⭐ 先查最便宜的假设(打开那句 SQL 十分钟,而前两步各要一天)。⭐ 校准什么时候致命:卡阈值、算期望收益、广告出价——真实案例里预估值整体高估,消耗和曝光这些中间指标全是好的,而投入产出比腰斩,一个月后财务才发现。
⚠️ 两个坑:在训练集上校准(线上完全不成立)、⭐ 负采样后忘了修正——概率被整体放大,而采样只影响概率不影响 AUC,所以它能一路通过全部离线评估。
下一节 👉 03-灰度影子与回滚.md