📑 本页目录(点开跳转)
附录 A · 速查
📌
Ctrl+F搜。不要通读。 出事的时候翻这一页,平时翻第 11 章。
🚨 线上出问题了,从这里开始
① 是真的吗 口径改了?数据跑完了?【分母变了吗】?版本号对吗?
② 什么时候起 按【小时】画曲线找拐点 —— 信息量最大的一个数字 ⭐
③ 谁变了 发布 / 上游 / 配置 / 外部。能回滚就先回滚 ⭐
④ 哪部分流量 新老、渠道、版本、地域、实验组
⑤ 哪个特征 特征监控 + SHAP 对比异常/正常时段 ⭐
⑥ 模型本身 最后才怀疑它
| 症状 / 拐点形状 | 几乎总是 |
|---|---|
| 技术指标全绿,业务却掉了 | ⭐⭐ 降级 / 兜底 / 默认值(真故障留错误率,降级不留) |
| 突变(1 小时内) | 发布、配置、上游故障 |
| 阶梯 | 数据源或口径变了 |
| 缓降(数周) | 漂移,不是故障 |
| 周期性 | 定时任务、跨时区 |
| 分数整体平移 | 某特征的量纲变了 |
| 分数标准差塌缩 | 特征大面积缺失,模型只剩偏置 |
| 总体跌但各人群都涨 | 流量结构变了(辛普森) |
| 连改三版"都没效果" | 💀 先核对线上模型版本号 |
⭐ 一次只改一个变量;⭐⭐ 「没报错」不等于「没出错」。
🌊 四种变差方式 → 该做什么
| 类型 | 输入分布 | 输入→输出关系 | 靠什么发现 | 该做 |
|---|---|---|---|---|
| 管道故障 | 突变 | — | 缺失率 / 新鲜度 | 修管道,💀 绝不重训 ⭐ |
| 数据漂移 P(x) | 渐变 | 不变 | PSI / 对抗验证 | 重训 / 加权 |
| 概念漂移 P(y|x) | 不变 | 变了 | ⚠️ 只有真实指标看得见 | 重训 + 调窗口 |
| 标签漂移 P(y) | — | 正例率变 | 预测均值 vs 实际正例率 | 重新校准即可 ⭐ |
⭐⭐ PSI 和对抗验证对概念漂移完全失明:外卖 ETA 并单上限 3→5,输入特征一个没变 (PSI 全 <0.06),只有 MAE 6.1→9.8 报了出来。
📡 最小监控清单(9 个)
1 错误率 / P99 延迟
2 降级触发次数 ⭐ 静默失效的唯一信号
3 模型版本号
4 每个特征的缺失率 ⭐⭐ 上游故障最快的信号
5 Top-5 重要特征的均值漂移
6 预测值的均值和标准差 ⭐ 标准差塌缩 = 特征大面积缺失
7 核心业务指标(按小时)
8 训练/线上特征一致性抽检 ⭐⭐
9 新鲜度(特征/上游表/模型)⭐ 唯一在坏数据【进模型之前】报警的
| 怎么定 | 做法 |
|---|---|
| 阈值 | 30 天均值±3σ → 用剩下的历史回测会报多少次警 ⭐ |
| 粒度 | ⭐ 比可容忍的故障时长更细。💀 3 小时故障按天看掉 9%,按小时是 −74% |
| 基线 | ⭐ 训练集基线 + 上周同期两条一起用(只用同期会漏掉慢漂移) |
🔍 训练/推理不一致:九种形态
① 两套代码(时区/空值/边界) ② 时间窗口对不齐
③ 特征穿越 ⭐ 离线 CV 发现不了 ④ 新类别静默映射到 0 💀
⑤ 归一化参数没保存 ⑥ 缺失值处理不同
⑦ 特征顺序错位 💀 类型全对、不报错,表现像"模型不行"
⑧ 线上有降级样本训练集没有 ⭐⭐ 占 2~5%,高峰期 15%
⑨ 统计特征口径 —— 只伤长尾,整体指标看不出来
⭐ 只做一件事,就做逐字段比对(代码 3️⃣)—— ⑦⑨ 分布监控完全抓不到。
🔨 代码速查
默认已
import numpy as np, pandas as pd, shap, json。
1️⃣ 带盐的哈希分流 | 灰度或 A/B 分流
import hashlib
def bucket(uid, exp, n=100):
key = f"{exp}:{uid}".encode() # ⭐ 盐(实验名)必须参与哈希
return int.from_bytes(hashlib.md5(key).digest()[:8], "big") % n
# 💀 绝不用内置 hash():每次发版分组会洗牌,且不报错
2️⃣ PSI | 每天算,看输入分布漂没漂
import numpy as np
def psi(base, live, bins=10):
e = np.quantile(base, np.linspace(0, 1, bins + 1)) # ⭐ 边界按训练集切并存下来
e[0], e[-1] = -np.inf, np.inf
f = lambda v: np.clip(np.histogram(v, e)[0] / len(v), 1e-6, None)
b, l = f(base), f(live)
return float(np.sum((l - b) * np.log(l / b)))
3️⃣ 逐字段比对 | 上线前必跑
def compare(online, offline, tol=1e-6):
assert list(online.columns) == list(offline.columns), "列序不符" # ⭐ 先查⑦
num = online.select_dtypes("number").columns
bad = (online[num] - offline[num]).abs().gt(tol).mean()
return bad[bad > 0].sort_values(ascending=False) # ⭐ 标准是完全一致
4️⃣ 拐点检测 | 排查第②步
import numpy as np
def changepoint(x, min_seg=6):
x = np.asarray(x, float); n = len(x)
k = min(range(min_seg, n-min_seg), key=lambda i: x[:i].var()*i + x[i:].var()*(n-i))
a, b = x[:k], x[k:]
se = np.sqrt(a.var()/len(a) + b.var()/len(b)) + 1e-12
return k, (b.mean()-a.mean())/se # ⭐ |z| < 3 就当没有拐点
5️⃣ SHAP 异常批次对比 | 排查第⑤步
import numpy as np, pandas as pd, shap
def shap_diff(model, X_ok, X_bad, top=10):
ex = shap.TreeExplainer(model)
d = pd.DataFrame({"贡献变化": ex.shap_values(X_bad).mean(0) - ex.shap_values(X_ok).mean(0),
"特征值变化": X_bad.mean() - X_ok.mean()}, index=X_ok.columns)
# ⭐ 两列一起看:都变了=数据问题;只有贡献变=换版或顺序错位
return d.reindex(d["贡献变化"].abs().sort_values(ascending=False).index).head(top)
6️⃣ SRM 检查 | 看任何实验结论之前
from scipy.stats import chisquare
def srm(c, alpha=1e-3): # ⭐ 阈值 1e-3 不是 0.05(每天跑很多次,防多重比较)
p = chisquare(c, [sum(c)/len(c)] * len(c)).pvalue
return p, ("🔴 分流有 bug,结论不可信" if p < alpha else "✅ 正常")
print(srm([1043221, 1051884])) # 差 0.83% → p≈2e-9,铁定有 bug 💀
7️⃣ DID | 有前后数据 + 对照组
import statsmodels.formula.api as smf # df 每行 =「地区 × 周」
m = smf.ols("y ~ treated + post + treated:post", data=df).fit(
cov_type="cluster", cov_kwds={"groups": df["region"]}) # ⭐⭐ 必须聚类
print(m.params["treated:post"], m.conf_int().loc["treated:post"])
8️⃣ 黄金样本 | 每次启动 / 升级依赖时
import json, numpy as np, pandas as pd
def verify_golden(model, path, atol=1e-6):
g = json.load(open(path, encoding="utf-8")) # 发布时冻结的 200 条样本+预测值
d = np.abs(model.predict_proba(pd.DataFrame(g["X"]))[:, 1] - np.array(g["p"]))
if (d > atol).any(): # ⭐ raise 拒绝启动,不是 warning
raise RuntimeError("黄金样本对不上")
📏 关键公式与阈值
$$\text{PSI} = \sum_i (p_i^{\text{线上}} - p_i^{\text{训练}})\ln\frac{p_i^{\text{线上}}}{p_i^{\text{训练}}}$$
| 指标 | 分档 |
|---|---|
| PSI | < 0.1 稳定 ✅ / 0.1–0.25 关注 ⚠️ / > 0.25 显著漂移 🔴 |
| ECE | < 0.01 可当概率用 / 0.01–0.05 只能排序 / > 0.05 不做数值决策 🔴 |
| 代理指标 ρ | ⭐⭐ > 0.7 且方向一致率 > 85% 才能决策;< 0.4 是在骗你 |
| 四五分之规则 | 弱势群体通过率 ÷ 优势群体 ≥ 0.8 ⭐;敏感性分析临界 Γ < 1.2 = 极脆弱 |
| 告警准确率 | P0 > 50%;误报率 > 50% 就已经失效了 |
⚠️ PSI 阈值默认上万条样本 —— 500 条时波动就有 0.05~0.12("一拆细就到处报警"的真正原因); 校准别在训练集上做,负采样后一定要修正(它不影响 AUC,能一路通过离线评估)。
| 场景 | 公式 / 数字 |
|---|---|
| 每组样本量 | $n \approx 16\sigma^2/\delta^2$ ⭐ 提升减半 → 样本量 ×4 |
| MDE | 每组 3 万、基线 5% → +0.5pp;每组 5 千 → +1.23pp |
| CUPED | 方差降低 ≈ ρ²;ρ=0.6 → 两周实验缩到 9 天 ⭐ |
| 偷看代价 | 一显著就停、连看 10 天 → 假阳性约 30%⭐⭐ |
| 长期 holdback | 永久留 1~5%;一年累计 +8% 时留 1% 只损失 0.08% ⭐⭐ |
| 日志保留 | ≥ 标签延迟 × 3 ⭐ / 衰减权重 0.5 ** (days_ago/half_life) |
| 容量 | 峰值QPS × 平均耗时 ÷ 单实例压测上限 × 1.5~2;超时设 P99 的 1.5~2 倍 |
🧪 A/B 实验检查清单
设计
[ ] 主指标只有一个,实验前写下来 ⭐⭐
[ ] 算过 MDE(比业务有意义的提升还大就别跑)⭐
[ ] 时长 ≥ 一个完整周期(≥1 周)
[ ] 分流按用户哈希 + 加实验名的盐 ⭐
[ ] 想过网络效应(SUTVA)
运行
[ ] 先跑 A/A 验证分流系统
[ ] 中途只止损 ⭐ 负向可早停,正向必须等满
[ ] 护栏指标在看 ⭐ 用红线否决,不要加权求和
分析
[ ] SRM 检查 ⭐⭐ 没修好之前不要看任何结论
[ ] 报置信区间,不只报均值
[ ] 分人群看(辛普森)/ 看逐日曲线(新奇效应)⭐
SRM 排查顺序 ⭐:分层看哪一层 → 有没有一组没上报日志 → 哈希加盐了吗 → 过滤条件是否不对称。
🪤 七个陷阱(自查用)
[ ] 分人群看还成立吗? 辛普森 ⭐
[ ] 样本是怎么来的、谁被筛掉了? 幸存者
[ ] 有对照组吗? 均值回归 ⭐
[ ] 数据在决策时点存在吗? 前视偏差
[ ] 相对值和绝对值都看了吗?
[ ] 分母变了吗? 比率陷阱 ⭐
[ ] 指标被优化后还代表价值吗? 古德哈特 ⭐
| 陷阱 | 修法 / 该记住的数 |
|---|---|
| 辛普森 | ⭐ 分层加权:用同一套人群权重加权两个版本 |
| 幸存者 | 风控被拒的人没标签 → 离线 AUC 0.78,换全部申请人只剩 0.66 |
| 均值回归 | 挑"最低 10%"干预,下期自动回升约 40%;⭐⭐ 样本越大 p 值越小 |
| 古德哈特 | ⭐⭐ 解药是把主指标和护栏指标画在同一张图上 |
🔗 因果方法选择
| 你有什么 | 用什么 | 关键假设 |
|---|---|---|
| 能随机分配 | A/B ⭐ | 无(黄金标准) |
| 前后数据 + 对照组 | DID 双重差分 ⭐⭐ | 平行趋势(事件研究法看 4–6 个前期) |
| 明确阈值 | RDD 断点回归 | 阈值不可操纵(看直方图有无堆积) |
| 横截面 + 协变量 | PSM 倾向得分匹配 | 无未观测混杂 💀 均衡性用 SMD 不用 p 值 |
| 合适的外生变量 | IV | 排他性(无法用数据检验) |
| 只有一个处理单元 | 合成控制 | 权重非负且和为 1 |
| 什么都没有 | ⚠️ 老实说不知道 | — |
⭐⭐ 当"是否接受处理"由用户自己选择时,未观测混杂几乎必然存在。
📊 特征重要性怎么选
| 场景 | 用什么 |
|---|---|
| 快速看一眼 | 内建(只当粗筛,偏向高基数) |
| 筛特征 | 置换重要性(验证集上、n_repeats≥10) |
| 相关特征互相掩盖 | 分组置换(整组用同一个置换下标)⭐ |
| 单条预测解释 | SHAP ⭐(唯一不需要标签的) |
| 异常批次归因 | SHAP 对比(异常 vs 正常时段)⭐⭐ |
| 判断因果 | ❌ 都不行 → 用因果方法 |
判读:mean 没超过 std 两倍 = 噪声;⭐ 加一列纯噪声当哨兵,排它后面的可以删。 ⭐⭐ 重要性高 ≠ 值得监控 → 监控优先级 = 重要性 × 历史故障率。
🚀 上线流程
影子 → 灰度 1% → 10% → A/B 50/50 → 全量
│ 每档至少一个完整业务周期
└ 逐字段比对训练/推理特征(1000 条,要求完全一致)⭐⭐
⚠️ 别跳过影子:有工程 bug 时那 1% 拿到的是彻底错误的结果。💀 影子链路必须禁写。 ⭐ 1% 灰度看不出效果:基线 CTR 4%、百万请求/天,检出 +5% 提升要 150 天 —— 它是看崩不崩的。 回滚三条件:快、完整(模型+特征逻辑+配置一起回)⭐、可验证; ⭐ 「有回滚方案」和「MTTR 是 5 分钟」是两回事,后者只能靠演练。 降级链路:新模型 → 旧模型 → 简单规则 → 静态兜底。⚠️ 降级本身要有监控。 发布单元五样 ⭐:模型、特征逻辑、预处理产物(编码器/归一化/分桶边界)、配置、环境。
♻️ 重训五道闸
闸1 数据质量 ⭐ 挡"管道坏了还硬训",必须含【逐特征 PSI】
闸2 训练健康
闸3 效果对比 同一验证集不差于现役 + ⭐【分人群】对比
闸4 一致性 ⭐⭐ 训练/推理特征比对
闸5 灰度指标
| 决策 | 怎么定 |
|---|---|
| 频率 | ⭐ 冻结一个历史模型看 1/2/4/8 周后的衰减曲线,周期设成越线点的一半;定时为主 + 漂移触发为辅 |
| 训练窗口 | ⭐ 当超参调:90 天 AUC 0.8118,全部 2 年只有 0.7834 —— 老数据是有害的 |
| 有反馈闭环 | ⚠️ 无脑重训会让模型越来越窄 → 必须掺随机流量数据纠偏 |
💀 26 小时脏数据只占窗口 1.2%,却通过了全部五道闸,新设备欺诈召回 61%→38%。 ⭐⭐ 脏数据占比小 ≠ 影响小 —— 随机噪声会被平均掉,系统性错误会被认真地学进去。
📋 模型卡必备
用途 / 版本 / 负责人
⭐⭐【不应用于】—— 整张卡最有价值的一节
训练数据(来源、时间、人群构成、已知偏差)
评估(整体 + 分人群 + 公平性定义选了哪个)
局限与已知失效模式 / 运维(监控、重训、降级、回滚、联系人)
⚠️ 难点不是写,是不腐烂 → ⭐ 关键字段自动生成。 ⭐⭐ 公平性的几个定义互相冲突:除非各群体基础率恰好相同,否则不可能同时满足。 ⭐⭐ 删掉敏感特征不会消除偏见,只会消除你发现偏见的能力。
💀 事故一行版
| 章 | 事故 → 教训 |
|---|---|
| 01 | 埋点秒→毫秒,特征大 1000 倍 → 不报错,两周后产品经理才发现 |
| 01 | 兜底静默用昨天特征,被当成"假期效应" → ⭐「说得通的解释」≠「找到了原因」 |
| 02 | 评估集 SQL 里三年前的 WHERE → 每个 WHERE 都要有人能解释 |
| 03 | 只回模型不回 schema → 31%→38%,MTTR 其实 6 小时 |
| 04 | sklearn 升级换了列顺序 → 监控全绿,坏账 1.8%→4.1% |
| 07 | 完播率当代理并去优化 → 留存 −1.2pp,r 0.61→0.08 |
| 08 | 48 次 P0 只 1 次真 → 真出事被静音 22 分钟 |
| 10 | 按 SHAP 加三个加购入口 → 它解释模型,不解释世界 |
| 11 | Redis key 配错触发降级 → P99 反而更低,发现延迟 31 小时 |
| 13 | PSM 达标就缩编客服 → 满意度 −9,临界 Γ 只有 1.15 |
| 14 | 一次解决率当 KPI → 71%→89% 而投诉 +34% |
| 18 | 3 秒 GC + 无退避重试 → 空白 22 分钟,触发≠持续原因 |
| 19 | 删掉性别姓名 → 女性通过率 0.61 倍,团队算不出这个比值 |
📖 术语对照
| 中文 | 英文 | 一句话 |
|---|---|---|
| 训练/服务偏斜 | training-serving skew | 两套特征代码不一致,静默掉点 |
| 数据漂移 | covariate shift | P(x) 变了 |
| 概念漂移 | concept drift | P(y|x) 变了 |
| 偷看 | peeking | 反复看 p 值,假阳性飙升 |
| 分流比失衡 | SRM | 分流比例不符预期 = 分流有 bug |
| 最小可检测效应 | MDE | 这些流量最小能测出多大的提升 |
| 方差缩减 | CUPED | 用实验前的同口径指标扣掉个体差异 |
| 新奇效应 | novelty | 新东西刚上线时的虚高 |
| 古德哈特定律 | Goodhart's law | 指标一旦成为目标就不再是好指标 |
| 拒绝推断 | reject inference | 被拒绝的样本永远没有标签 |
| 长期对照组 | holdback | 永久留一小撮不上新策略 |
🔗 ML 基础 · 评估与过拟合 / 推荐算法 · 评估与 A/B 实验
👉 回到首页