🏠 总目录📚 本教程 15 · 采样与幸存者偏差 ← →
📑 本页目录(点开跳转)

15 · 采样偏差与幸存者偏差

⏱ 46 分钟 | ⭐ 你的数据里只有「被系统选中过」的那些人


🎯 一句话

你的训练数据只包含被某个系统放行过的样本,而你要预测的是所有人。 上一章的泄漏是「多了不该有的信息」, 这一章是反过来 —— 少了一整块你从来没见过的人群,而且这块缺失不会报错。


✈️ 一、弹孔的故事,和它真正的重点

二战时统计学家看返航飞机的弹孔分布,建议加固弹孔最少的地方 —— 因为中弹在那里的飞机根本没飞回来。

对照

你看到的样本 = 被弹孔打中 AND 还能飞回来

你想推断的 = 所有被弹孔打中的飞机

⭐ 故事的重点不是"要反着想",

而是:【返航飞机的数据本身是完全正确的】。

每一个弹孔都测量准确、没有脏数据、没有缺失、没有标注错误。

💀 这套数据能通过你前面十章学的【全部】质量检查。

⭐ 这是本章和第 04–08 章的分界线: 前面几章讲的是「数据是不是错的」, 这一章讲的是「数据全对,但它不是你以为的那群人」。 质量检查、契约、缺失分析 —— 一个都抓不到这个问题。


🎭 二、三个场景,同一个结构

场景 你有标签的是谁 你要预测的是谁 看不见的那块
招聘 入职并干满一年的人 所有投简历的人 被简历筛掉的人本来能不能干好 ⭐
风控 审批通过并放款的人 所有申请人 被拒绝的人本来会不会还钱
推荐 被曝光过的物品 全部候选物品 从没被推过的物品会不会被点

对照

三个场景剥开之后是同一件事:

一个【旧的决策系统】 S 决定了谁能进入你的数据集

你训练出的新模型 M,只在 S 放行过的人身上学过

然后你要用 M 去替换 S ——

⭐ 也就是让它去决策一群它从来没见过的人

🔑 这个结构有个名字叫「反事实缺失」: 你缺的不是"数据量",是另一半世界的结果 —— 「如果当初批了他会怎样」这个问题,数据库里永远不会有答案。 ⚠️ 所以它不能靠"多攒几个月数据"解决:多攒的每一条,还是 S 放行过的。


🧊 三、先别慌:它什么时候【不】要紧

大多数人对选择偏差的直觉是过度悲观的。 先看一个实跑出来的反例。

模拟一个信贷场景:老策略只看 x1(收入类)和 x2(负债类)打分,批 40%。 新模型能看到 x1, x2, x3 三个特征(x3 老策略没用过但真的有预测力)。 只用「批过的人」训练:

评估对象 AUC
在通过人群上评估 0.761
在全体申请人上评估 0.821 ⭐
上帝视角(假装所有人都有标签) 0.821

⭐ 看第二行和第三行:一模一样。 在这个设定下,选择偏差没有造成任何损失。 条件是两个:① 老策略只依据可观测变量做决策;② 你的模型设定是对的。 满足这两条时,只在被选中的样本上训练不会带来系统性偏差 —— 这在统计里叫「基于可观测量的选择」。

⚠️ 但第一行是个大坑:在通过人群上评估出来的 0.761 是偏低的, 真实能力是 0.821。这叫区间截断(range restriction) —— 你砍掉了最容易区分的那部分人(明显的坏客户全被老策略拒了), 剩下的都是"边缘案例",当然更难分。 ⭐ 所以:「我们模型的 AUC 只有 0.76」这句话,在有选择的人群上是没有意义的。


💥 四、什么时候它真的会害你:两种情况

情况 B:旧策略用了你看不到的东西

真实的审批不只有模型分,还有人工复核 —— 审批员看了流水照片、听了电话、感觉不对就拒了。 这些「感觉」既影响了谁被选中,也真的和还款相关,但它们不在你的特征里。

实跑(给旧策略加一个模型看不到的变量 u,u 同时影响放行和还款):

评估 只用通过样本训练 上帝视角 缺口
全体申请人 AUC 0.759 0.778 0.019

情况 C:真实关系是非线性的,而选择切掉了一整段区间

这是最贵的一种,而且在业务里非常普遍:老策略把某个特征区间整段拒光了, 你的模型在那一段上从来没有一个样本,只能靠外推。

实跑(真实关系带一个二次项,老策略切掉了低分段):

评估对象 只用通过样本训练 上帝视角 缺口
全体申请人 0.813 0.841 0.028
被老策略拒掉的那段人群 ⭐ 0.772 0.829 0.057 💀

💀 看最后一行:在你最需要它的地方,模型差 0.057。 而「被老策略拒掉的人群」恰恰是新模型上线后要重新审视的人群 —— 因为如果新模型的结论和老策略完全一样,那它没有任何商业价值。 ⭐ 你部署新模型的全部意义,正好落在它最不可靠的那一段上。


🎲 五、留随机流量当尺子:代价与收益

唯一能真正补上那块缺失的办法,是故意放行一小撮本该被拒的样本。 业界叫法很多:随机流量、探索流量、reject inference 的黄金层、holdout 放款。

收益(同一组实验,随机放行被拒人群的 5%):

情况 无随机流量 5% 随机流量 上帝视角 补回了多少缺口
A(选择只看可观测量) 0.821 0.821 0.821 — (本来就没缺口)
B(不可观测混杂) 0.759 0.770 0.778 58%
C(区间被切断,在被拒人群上) 0.772 0.819 0.829 83% ⭐

⭐ 这张表是本章最有价值的三行: 5% 的随机流量,在"区间被切断"这种情况下补回了 83% 的能力缺口。 而在情况 A 里它一分钱效果都没有 —— 随机流量不是万能药,它只解决「你没见过的区间」这一个问题,但解决得非常彻底。

代价(同一份模拟里算出来的真实账):

算一算

全体申请人的"好客户"比例 · 33.1%

被老策略【通过】的人里好客户比例 · 54.2%

被老策略【拒绝】的人里好客户比例 · 19.0% ⭐ 这就是代价的来源

随机放行被拒人群的 5%:

覆盖全部申请人的 5% × 60% = 3.0%

这 3% 里约 81% 是坏客户

→ 相对于全部放款量(40%),额外坏账敞口 ≈ 3.0% × 81% / 40% ≈ 6.1%

怎么把代价压下来 做法
限额 ⭐ 随机放行的样本给最低额度(比如正常额度的 10–20%),坏账敞口按额度线性缩小
分层抽样 ⭐ 不要在全体被拒人群里均匀抽 —— 在紧邻阈值的那一段多抽,深度拒绝区少抽
时间稀释 不是每天都放,按周或按月放一批
只在需要的维度上探索 只对「老策略从没见过的特征组合」开口子

⭐ 「分层抽样」这条能把代价砍掉一大半: 你真正需要信息的是决策边界附近,不是那些明显不该批的人。 在阈值上下 10 个百分位里抽样,用 1% 的敞口能换到接近 5% 均匀抽样的信息量。


🧮 六、这一章唯一的一个公式:逆倾向加权(IPW)

如果你能估计出每个样本被选中的概率,就可以给样本加权,把分布掰回全体:

对照

记 π(x) = P(被选中 | x) ← 叫"倾向分" propensity

在【全体人群】上的期望损失,可以用【被选中的样本】估出来:

1 s_i

E[ℓ] ≈ · Σ · ℓ_i

N π(x_i)

↑

被选中概率越低的样本,权重越大

(因为它代表了一大群没被选中的同类)

s_i = 1 表示第 i 个样本被选中(有标签),Σ 只对这些样本求和

用它的前提 说明
π(x) 必须能估准 用「是否被选中」当标签训一个分类器就行,通常很好训
⚠️ 正性假设:每个 x 都有 π(x) > 0 💀 老策略硬阈值拒绝的区间里 π = 0,权重是无穷大 —— IPW 在那里彻底失效
⚠️ 选择必须只依赖可观测量 情况 B(不可观测混杂)下 IPW 也纠不回来

💀 正性假设是 IPW 最常被忽略的死穴,也是它和随机流量的分工所在: 老策略是硬规则(比如"征信查询次数>8 直接拒")时,那一段的 π 严格等于 0, 任何加权方法都无中生有不出信息。 ⭐ 随机流量的真正作用,就是把那些 π = 0 的区间变成 π = 0.05。 它买的不是样本量,是"正性"本身。

权重必须截断,否则方差爆炸:

import numpy as np
from sklearn.linear_model import LogisticRegression

def ipw_weights(X_all, selected, clip=(0.02, 1.0)):
    """selected: 布尔数组,True 表示这一行有标签。返回被选中样本的权重"""
    pm = LogisticRegression(max_iter=1000).fit(X_all, selected.astype(int))
    pi = pm.predict_proba(X_all)[:, 1]
    pi = np.clip(pi, *clip)                    # ⭐ 不截断的话极小的 π 会产生天文数字权重
    w = 1.0 / pi[selected]
    w = w / w.mean()                           # ⭐ 归一化,便于和无权重的结果比较
    ess = w.sum() ** 2 / (w ** 2).sum()        # ⭐ 有效样本量
    return w, {"有效样本量": round(float(ess)),
               "实际样本量": int(selected.sum()),
               "权重最大值": round(float(w.max()), 1)}

# ⭐ 判读:有效样本量 < 实际样本量的 30% 时,IPW 的结论不值得相信 ——
#     说明你在用一小撮罕见样本去代表一大片人群

🔁 七、拒绝推断(reject inference)的四种做法

风控行业专门为这个问题起了名字。四种做法,质量从低到高:

做法 怎么做 值不值
全部当坏客户 被拒的人一律标 bad ❌ 最差,等于把老策略的规则硬编码进新模型,新模型只会复制老策略
模糊标注 / 软标签 用当前模型给被拒样本打个概率,按概率拆成两条加权样本 ⚠️ 只是自证预言的一种精致写法:模型学的还是它自己的输出
IPW / 倾向加权 上面那个公式 ✅ 便宜、有效,但卡在正性假设上(硬拒绝区无解)
随机流量 ⭐ 真的放行一小撮 ✅✅ 唯一能产生新信息的办法,也是唯一有成本的办法

⭐ 一句话总结这张表: 前三种都是在已有信息里重新分配,只有第四种在制造新信息。 💀 前两种最危险的地方在于 —— 它们会让离线指标变好看, 因为你用来评估的测试集也是同一个偏差人群。 一个只会复制老策略的模型,在老策略的地盘上永远看起来很准。


💀 八、一次真实的事故复盘

对照

系统:某招聘平台的"简历-岗位"匹配打分模型

目标:预测候选人投递后是否会【通过面试并入职】

训练数据:过去 3 年的 HR 系统记录,41 万条投递

发生了什么

阶段 事件
建模 只有进入过面试环节的候选人才有"是否入职"的标签,占全部投递的 6.8%
处理方式 团队把「没进面试」的 93.2% 全部标成负样本 ⭐ 这就是「全部当坏客户」
离线 AUC 0.88,团队认为效果非常好
上线 用于简历排序,人工只看 Top 50
4 个月后 HR 反馈:推上来的人越来越像同一种人;入职转化率从 12.1% 降到 9.4%

为什么没被发现

流程图

① 93.2% 的负样本里,绝大多数只是【HR 没来得及看】,不是"不合适" 💀
模型学到的其实是「HR 过去会不会点开这份简历」,
而不是「这个人能不能干好这个活」
② 离线 AUC 0.88 是在【同一个偏差数据】上算的 ——
模型完美复现了老 HR 的筛选习惯,所以它当然很准 ⭐
③ 4 个月里形成了反馈回路:
模型推上来的人→HR 面试→产生新标签→下次训练
⭐ 越来越窄:第 4 个月推荐结果里,
候选人的学校分布熵从 3.1 降到 2.2(降了 29%)

代价

信息关系

入职转化率 12.1%→9.4% (相对下降 22%)
月均入职 约 340 人→264 人
4 个月少入职 约 300 人
⭐ 更贵的是不可见的部分:
被系统性排在后面的那批候选人,永远不会产生"其实他很好"的证据 ——
这个损失【在任何指标上都看不到】

该补什么

缺失 补上之后
把"没被看"当成负样本 💀 区分三类:面试后拒 / 面试后录用 / 从未被看(后者不进训练集,或用 IPW 加权)
没有随机曝光位 ⭐ Top 50 里固定留 3 个随机位(从模型排名 50 名开外随机抽)。代价 6%,用来当尺子
离线评测集和训练集同源 用随机位产生的数据单独建一个无偏评测集(第 13 章)
没有多样性监控 学校/专业/工作年限的分布熵进日报,掉 > 10% 告警
没人问"看不见的那部分" 模型评审新增一问:「哪些人不会出现在你的训练数据里?」 ⭐

⭐ 改进后的关键数字:加上 3 个随机位(6% 的展示量)之后, 用随机位数据评估出来的真实 AUC 是 0.71,不是 0.88。 那 0.17 的差距,就是"模型复制老 HR"和"模型真的会看人"之间的距离。


🧭 九、一张判断表:要不要处理,怎么处理

先问这个 如果是 该做
我的标签是不是只有"被某个系统放行的样本"才有? 否 ✅ 不用管这一章
老策略是不是只依据我也能看到的变量做决策? 是 ⭐ 偏差可能为零,但评估必须在全体人群上做,别被截断区间的低分吓到
老策略里有没有人工判断 / 我看不到的字段? 有 ⚠️ 情况 B,IPW 帮不上,只能靠随机流量
老策略有没有硬规则整段拒绝? 有 💀 情况 C,π = 0,任何加权都无效,必须随机流量
新模型的价值是不是主要来自"改判老策略拒掉的人"? 是 ⭐ 那就必须有随机流量,否则你的评测在最关键的区间上完全空白
系统的输出会不会影响下一批训练数据? 会 ⚠️ 反馈回路,问题会逐月加重,必须有分布熵之类的监控

⭐ 最后一行是最容易被忽略的: 选择偏差如果不管,它不是一个静态的偏差,它会自我加强。 今天窄一点点,三个月后窄很多 —— 招聘那个案例里,四个月熵掉了 29%。


🔗 这一章连到哪里

去哪 为什么
模型上线之后 13 不能做 AB 时的因果推断 ⭐ IPW、倾向分、反事实这一整套工具的完整版。这一章只用了它最简单的一个形式
推荐算法 13 冷启动与探索利用 推荐场景的同一个问题:没被曝光过的物品永远拿不到反馈。那边讲怎么系统性地分配探索预算
强化学习基础 07 探索与利用 「留随机流量」在 RL 里就是 ε-greedy。这一章的代价/收益权衡,那边有完整的理论版本
模型上线之后 14 辛普森悖论与七个陷阱 另一类「数据全对但结论是错的」问题,和本章是同一个家族
SQL 查询这一关 10 拿 SQL 审问一份数据 ⭐ 加权之前得先知道偏在哪个方向、偏了多少:那一章第五节把每一个 WHERE 挡掉的行捞回来看一眼是谁,正是本章 IPW 之前缺的那一步

✅ 检查点

  1. 弹孔故事的真正重点是什么?为什么前面几章的质量检查抓不到这个问题?
  2. 招聘、风控、推荐三个场景剥开之后是同一个什么结构?为什么"多攒几个月数据"解决不了?
  3. 选择偏差什么时候不要紧?实跑数字是多少?这时候还有什么坑?
  4. 什么是区间截断?为什么"我们模型 AUC 只有 0.76"这句话在有选择的人群上没有意义?
  5. 选择偏差真正会害你的两种情况是什么?情况 C 在被拒人群上的缺口是多少?为什么这个缺口特别致命?
  6. 5% 随机流量在三种情况下各补回了多少缺口?代价怎么算、怎么压?
  7. IPW 的公式是什么?正性假设为什么是它的死穴?有效样本量该怎么判读?
  8. 拒绝推断四种做法里,为什么说前三种都不产生新信息?前两种最危险的地方在哪?
  9. 招聘那个事故里,团队怎么处理没进面试的样本?模型实际学到了什么?代价是多少?加上随机位之后真实 AUC 是多少?
  10. 为什么说选择偏差"不是静态的"?案例里的数字是多少?
👀 答案
  1. 重点是返航飞机的数据本身完全正确——每个弹孔都测量准确,没有脏数据、缺失、标注错误,这套数据能通过前面十章的全部质量检查。因为问题不是"数据是错的",而是"数据全对,但它不是你以为的那群人"。
  2. 结构是:一个旧的决策系统 S 决定了谁能进入你的数据集,新模型 M 只在 S 放行过的人身上学过,然后你要用 M 去决策一群它从没见过的人。多攒数据没用是因为这叫反事实缺失——缺的不是数据量而是"另一半世界的结果",多攒的每一条还是 S 放行过的。
  3. 当 ① 老策略只依据可观测变量决策 ② 模型设定正确 时不要紧。实跑:只用通过样本训练,在全体申请人上 AUC 0.821,和上帝视角完全相同。坑在于:在通过人群上评估出来的 0.761 是偏低的,这叫区间截断。
  4. 区间截断:老策略把最容易区分的那部分人(明显的坏客户)拒掉了,剩下的都是边缘案例,所以看起来更难分。所以在有选择的人群上报出来的 AUC 既不代表真实能力也不能横向比较——真实能力是 0.821,报出来是 0.761。
  5. B:旧策略用了你看不到的东西(人工复核的"感觉",既影响放行也影响还款),缺口 0.019;C:真实关系非线性 + 选择切掉了一整段特征区间,全体上缺口 0.028,但在被老策略拒掉的人群上缺口是 0.057。致命是因为被拒人群恰恰是新模型上线后要改判的人群——如果新模型结论和老策略一样,它就没有商业价值,所以你部署它的全部意义正好落在它最不可靠的那一段上。
  6. A 补回 0(本来就没缺口)、B 补回 58%(0.759→0.770,上帝 0.778)、C 在被拒人群上补回 83%(0.772→0.819,上帝 0.829)。代价:被拒人群里只有 19.0% 是好客户(通过人群是 54.2%,全体 33.1%),随机放行被拒的 5% ≈ 全体的 3%,其中 81% 是坏客户,额外坏账敞口约 6.1%。压成本靠:给最低额度(敞口线性缩小)、在紧邻阈值那一段分层抽样(1% 敞口换到接近 5% 均匀抽样的信息量)、时间稀释、只对老策略没见过的特征组合开口。
  7. E[ℓ] ≈ (1/N) Σ (s_i / π(x_i)) · ℓ_i,被选中概率越低的样本权重越大。正性假设(每个 x 都要 π(x) > 0)是死穴:老策略硬阈值拒绝的区间里 π 严格等于 0,权重无穷大,IPW 在那里彻底失效——随机流量的真正作用就是把 π = 0 的区间变成 π = 0.05,它买的不是样本量,是"正性"本身。有效样本量 (Σw)² / Σw²,低于实际样本量的 30% 时结论不值得相信。
  8. 因为前三种都只是在已有信息里重新分配,只有随机流量在制造新信息。前两种(全部当坏客户 / 软标签)最危险的是它们会让离线指标变好看——评估用的测试集也是同一个偏差人群,一个只会复制老策略的模型,在老策略的地盘上永远看起来很准。
  9. 把 93.2% 没进面试的投递全部标成负样本(即「全部当坏客户」),而这些人绝大多数只是HR 没来得及看。模型实际学到的是「HR 过去会不会点开这份简历」,不是"这个人能不能干好"。代价:入职转化率 12.1% → 9.4%(相对降 22%),4 个月少入职约 300 人,学校分布熵从 3.1 降到 2.2。加 3 个随机位(6% 展示量)后测出真实 AUC 是 0.71 而不是 0.88——那 0.17 就是"复制老 HR"和"真的会看人"之间的距离。
  10. 因为存在反馈回路:模型推谁 → 谁被面试 → 产生新标签 → 下次训练,范围越来越窄,会自我加强。案例里 4 个月学校分布熵从 3.1 降到 2.2,掉了 29%。所以必须把分布熵一类的多样性指标放进日报,掉超过 10% 就告警。

🛑 可以停在这里

⚡ 走神救援

⭐ 你的训练数据只包含「被某个旧系统放行过」的样本,而你要预测的是所有人。 上一章的泄漏是「多了不该有的信息」,这一章是少了一整块你从没见过的人群,而且不会报错。

⭐ 弹孔故事的真正重点不是「反着想」,而是返航飞机的数据本身完全正确——每个弹孔都测量准确、没有脏数据没有缺失,它能通过前十章的全部质量检查。这就是本章和前面几章的分界:那些讲「数据是不是错的」,这章讲「数据全对,但它不是你以为的那群人」。

⚠️ 这叫反事实缺失,多攒几个月数据没用——多攒的每一条还是被同一个旧策略放行的。

⭐ 先别慌,它不总是有害:旧策略只依据可观测变量、且模型设定正确时,偏差可以是零。⚠️ 但有个大坑叫区间截断——老策略拒掉了最好的那批人,剩下全是边缘案例,所以「我们 AUC 只有 0.76」这句话在有选择的人群上没有意义。

💀 真正害人的一种是「真实关系非线性 + 选择把一整段特征区间切没了」:被拒人群恰恰是新模型要改判的人群——如果新模型结论和老策略一样它就没商业价值,⭐ 所以部署它的全部意义,正好落在它最不可靠的那一段上。

🎲 随机流量不是万能药,它只解决「没见过的区间」这一个问题,但解决得非常彻底。 压成本三招:给最低额度、在紧邻阈值那一段分层抽样、时间稀释。

⭐ IPW 的死穴是正性假设:老策略硬阈值拒绝的区间里选中概率严格为 0,任何加权都无中生有不出信息——随机流量买的不是样本量,是「正性」本身。

💀 拒绝推断的四种做法里,前两种(全标坏客户、软标签)最危险的地方是它们会让离线指标变好看:测试集也是同一个偏差人群,只会复制老策略的模型,在老策略的地盘上永远看起来很准。

⭐ 评审时必问的一句:「哪些人不会出现在你的训练数据里?」

下一节 👉 16-合成数据.md

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