🏠 总目录📚 本教程 附录A · 速查
📑 本页目录(点开跳转)

附录 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 实验

👉 回到首页

打卡记录保存在你的浏览器里,首页能看到总进度