S / T / X / R Learner 入门
你想搞清楚「这条促销短信发给张三,他会不会多买一单」——可你手上只有一万人群、一半发了一半没发、还有一堆乱七八糟的用户特征。这时候机器学习能帮你算那个"假如",但普通的回归模型会骗你。这篇文章讲清楚四个最常用的「因果效应学习器」—— S / T / X / R ——它们各自怎么工作、什么时候该用哪一个、什么时候全都不…
文本版 · 供搜索与朗读
S/T/X/R Learner 入门与原理 — 给想给每个用户算清楚账的人
Causal Inference · 教程
S / T / X / R Learner 入门
给每个用户算清楚账 —— 四个 meta-learner 的原理、脾气与选法
费曼笔法
约 25 分钟
causalml · Potential Outcomes
本文目录
故事开场:促销要不要发给张三
先把世界想成两张表:Y(0) 和 Y(1)
三个隐形的鬼:混淆、选择偏差、异质效应
四个学习器登台前的共识
S-Learner:一个模型装下所有人
T-Learner:两个模型两张嘴
X-Learner:把 T-Learner 反着用
R-Learner:Robinson 的残差化魔法
决策卡:你该用哪一个
causalml 上的代码骨架
局限:什么时候它们都救不了你
你想搞清楚「这条促销短信发给张三,他会不会多买一单」——可你手上只有一万人群、一半发了一半没发、还有一堆乱七八糟的用户特征。这时候机器学习能帮你算那个"假如",但普通的回归模型会骗你。这篇文章讲清楚四个最常用的「因果效应学习器」—— S / T / X / R ——它们各自怎么工作、什么时候该用哪一个、什么时候全都不管用。
1 · 故事开场:促销要不要发给张三
周五下午四点,市场部的小王冲进来说:
"我们手上有两万人,我明天想发一万条促销短信。预算有限,能不能算清楚发谁、不发谁?我想知道:对每一个具体的人,发了短信比不发多挣多少钱。"
你脑子里立刻蹦出几个想法:
那就做个分类模型?预测「收到短信的人会不会下单」——不行,因为没收到短信的人没机会下单,这事儿没法预测。
那就 A/B 测试平均一下?——也不行,小王要的是每个人的"假如发了会怎样",平均值会把穷鬼和富二代混在一起,掩盖该发给谁。
那就做个回归?——还是不行,把"是否发短信"当一个特征塞进去,模型会告诉你相关性,不告诉你因果。发短信的可能本来就是高活用户,他们买不买跟短信没关系。
这就到了 CausalML 出场的时刻。我们想要的不是「预测 Y」,而是「Y(1) − Y(0)」——如果发了和如果没发两个世界之间的差。
这差值有个学术名:条件平均处理效应(CATE),全称 Conditional Average Treatment Effect。它长这样:
τ(x) = E[ Y(1) − Y(0) | X = x ]
直白讲:对一个特征向量为 x 的人,处理(treatment)能给他带来多少额外收益。
本文术语速查
Treatment T:干预措施,0/1 二元变量。这里就是"发不发短信"。
Outcome Y:结果变量。这里是"这周消费金额"。
Covariates X:用户特征。年龄、城市、历史消费、活跃度等等。
CATE τ(x):给定特征 x,处理对结果的因果效应。我们最终想要的就是这个。
2 · 先把世界想成两张表:Y(0) 和 Y(1)
因果推断的"魔法前提"——也是它折磨人之处——叫潜在结果(Potential Outcomes)。想象世界上有张三、李四、王五三个人,每个人都有两个命运:
人Y(0) 没收到短信的消费Y(1) 收到短信的消费个体效应 τᵢ = Y(1)−Y(0)
张三100150+50
李四200210+10
王五5030−20(反效果!)
最右那列就是个体处理效应。但麻烦来了:你一辈子都只能观察到张三其中一个 Y——要么看到他没发短信时的 100(如果他被分到对照组),要么看到他发了短信时的 150(如果他被分到处理组)。你永远看不到另一个。这叫因果推断的根本问题(Fundamental Problem of Causal Inference),荷兰统计学家 Rubin 想明白这件事之后干脆把它写进了名字里,叫Rubin Causal Model。
所以我们做的事,本质都是用一群人的命运去猜另一个人看不见的那一半。四个 meta-learner 就是四种"猜法"。
每个人永远只能"活在"一个分支里。实色是观测到的,虚线是反事实(counterfactual)—— 必须由模型推断。
3 · 三个隐形的鬼:混淆、选择偏差、异质效应
在动手前,得先认识我们要打的三个 boss。
3.1 混淆(Confounding)
用户活跃度高 → 既会提高消费(Y),又更可能收到促销(T)。这个"活跃度"就是个混淆变量,它让 T 和 Y 之间产生了伪相关。如果不主动处理,你会发现"发短信的人消费更高"——但其实主要是因为这群人本来就活跃。
因果推断的绝大多数功夫,都花在如何拆掉这条混杂管道。
3.2 选择偏差(Selection Bias / Treatment Assignment Bias)
谁会被分到处理组?谁会被分到对照组?理想情况下是抛硬币,但真实业务里往往是营销人员凭直觉分配("看起来会买的发给一点小恩惠试试")。这种非随机分配让处理组和对照组在观测前就长得不一样,再多的统计调整也只能救一部分。
3.3 异质效应(Heterogeneous Treatment Effects, HTE)
同一个处理,对张三有效,对王五可能反效果。这就是"促销对老客户有效、对新客户骚扰"的本质——同一种动作,效应分布高度依赖个体特征。我们要算的 CATE,正是把这种异质性抓出来的工具。
红线提醒
所有 meta-learner 都假设 无混淆(unconfoundedness / ignorability):给定了 X 之后,T 和 Y(0), Y(1) 条件独立。通俗说:X 里包含了所有该控制的混淆因素。
如果有重要混淆因素没观测到(比如"用户当前心情",你数据里没有)—— 这四个学习器救不了你,得用 IV 工具变量、DiD 双重差分、RDD 断点回归这些设计型方法。
4 · 四个学习器登台前的共识
后面四个家伙看似做法不同,其实都共享两个底层假设:
平行宇宙假设(SUTVA):你处理张三不会影响李四(没有溢出效应)。对个人促销通常成立;对"满减"这种挤兑型活动就不成立。
重叠假设(Overlap / Common Support):对任意特征 x,处理组和对照组都得有样本。不能出现"100 岁的老奶奶 100% 都在对照组"——那样模型对她只能编。
满足这两条,下面四兄弟才能上场。
5 · S-Learner:一个模型装下所有人
S 是 Single 的缩写。思路直白得像外卖套餐——
把 T 当作一个普通特征,塞进一个机器学习模型,让它学着预测 Y:
μ(x, t) = E[ Y | X = x, T = t ]
训练完,用两次:一次喂 T=1,一次喂 T=0,相减就是 CATE:
τ̂(x) = μ̂(x, 1) − μ̂(x, 0)
举个例子:你用 XGBoost 拟合一个 μ̂(x, t)。想知道张三的 CATE,就把张三的特征喂进去两次——一次告诉他"发了短信",一次告诉他"没发"——输出差就是张三的因果效应。
5.1 优势
简单到爆。一个模型,改两次特征。调试成本最低。
如果 T 和 X 之间本来就有强相关(比如 T 是某个 X 的衍生变量),这种"塞进去"的方式可能反而比强行分离更稳。
5.2 致命伤
Regularization 歧视 T:XGBoost/Lasso 这种带正则的模型会"偷懒"——T 只有两个值、变量重要性低,模型会倾向把 T 的信号抹平,结果 τ̂(x) ≈ 0,啥也学不到。
特征空间不平衡:训练时每个 x 只见过一个 t(因为同一个人的两个潜在结果永远只有一个被观测),模型对 (x, 1−t) 的预测是凭空外推,依赖太强容易崩。
什么时候用 S-Learner
数据极大、特征与处理强相关、且基模型不带强正则(如深度网络)
否则 T 的信号会被正则抹掉,得到"对谁都没效果"的废物模型。
6 · T-Learner:两个模型两张嘴
T 是 Two 的缩写。看到 S 的问题,自然会想:那就给处理组和对照组各训一个模型呗。
μ₀(x) = E[ Y(0) | X = x ], μ₁(x) = E[ Y(1) | X = x ]
两个模型分别学"没发短信会怎样"和"发了会怎样",预测时相减:
τ̂(x) = μ̂₁(x) − μ̂₀(x)
举个例子:μ̂₀ 用所有没发短信的人训练,μ̂₁ 用所有发了的人训练。两个模型都拿不到 T 这个变量,它纯粹是个分组开关。
6.1 优势
不再有正则歧视 T 的问题——T 根本没进模型。
每个组可以独立选模型:处理组样本少就上简单模型,对照组样本多就上 XGBoost。
直观、好解释、市场部小王一听就懂。
6.2 致命伤
每个模型只看到一半样本:处理组只有 5000 人,对照组也只有 5000 人——方差天然大。
两组样本分布可能差很大:处理组里都是高活用户,模型对"低活用户会怎样"完全靠外推。如果两组分布不重叠(比如处理组全是老客户),T-Learner 会给出离谱的预测。
左右阴影表示两组样本没重叠的地方。两个模型各自外推,互不帮助,预测尾部时方差巨大。
什么时候用 T-Learner
处理组和对照组样本都充足、特征分布基本重叠
这是最朴素也最稳的选择。但处理组样本极少(比如连 1000 都没有)时不要用。
7 · X-Learner:把 T-Learner 反着用
X 的发明者叫 Künzel——他在 2017 年的论文里发现:当处理组样本远少于对照组时(比如医疗新疗法试验,治了 100 人,没治 900 人),T-Learner 训处理组的那个 μ̂₁ 会因为数据太少而方差爆炸。X-Learner 的反直觉思路是:既然治疗组样本少,干脆用对照组的信息去"补"。
三步走:
第一步:照 T-Learner 训两个模型 μ̂₀、μ̂₁。
第二步:分别算个体的"想象效应"——
对处理组的每个个体 i:用对照组模型预测他的 Y(0),再用真实 Y(1) 减去这个预测。相当于"用对照组的知识,帮处理组的人想象他们没被治疗会怎样"。记为 D̃₁。
对对照组的每个个体 j:用处理组模型预测他的 Y(1),再用真实 Y(0) 减去这个预测(取负号)。相当于"用处理组的知识,帮对照组的人想象他们被治疗会怎样"。记为 D̃₀。
D̃₁ᵢ = Y(1)ᵢ − μ̂₀(Xᵢ) (i ∈ 处理组)
D̃₀ⱼ = μ̂₁(Xⱼ) − Y(0)ⱼ (j ∈ 对照组)
第三步:用 D̃₁ 和 D̃₀ 分别训练两个效应模型 τ̂₁(x) 和 τ̂₀(x),然后加权平均:
τ̂(x) = g(x) · τ̂₁(x) + (1 − g(x)) · τ̂₀(x)
其中 g(x) 是处理组的倾向得分(propensity score),权重偏向样本多的那一组。
7.1 优势
极端不平衡数据的救星:处理组只有几百样本也能用,因为第二步的 D̃₁ 是用大样本对照组算出来的。
两组样本的重叠区域,预测方差通常比 T-Learner 小。
7.2 致命伤
第二步用了"想象效应"——这是反事实,依赖第一步两个模型 μ̂₀、μ̂₁ 的外推能力。如果第一步就离谱,后面更离谱。
分组不重叠时(极端倾向得分接近 0 或 1),X-Learner 的方差也大。
X-Learner 的核心是把 T-Learner 的两个模型反过来用——借大样本组的大脑,帮小样本组补反事实。
什么时候用 X-Learner
处理组样本远少于对照组(10% 或更少),且两组分布有重叠
医疗试验、稀有事件干预、营销灰度上线——这是 X-Learner 的主战场。
8 · R-Learner:Robinson 的残差化魔法
R 是 Robinson,致敬 1988 年这位统计学家。它的哲学和前三家完全不同——前三个都在拟合 Y 的期望,R-Learner 直接拟合残差之间的关系。
先把形式摆出来:
Y = m(X) + τ(X) · T + ε
这是 Neyman-Rubin 的因果断裂回归模型。其中 m(x) = E[Y | X=x, T=0](不管处理怎样,X 本身对 Y 的影响),τ(x) 是我们要的 CATE,ε 是噪声。
Robinson 在 1988 年证明了一个聪明的恒等式。把上式两边都减去 m(X),T·m(X) 也减去。剩下:
(Y − m(X)) = τ(X) · (T − e(X)) + 噪声修正
其中 e(x) = P(T=1 | X=x) 是倾向得分。这两个减出来的 (Y − m(X)) 和 (T − e(X)) 叫残差。神奇之处在于:它们不再依赖 X 的具体形式,τ(x) 是它们之间的"斜率"。
所以 R-Learner 的两步走:
第一步:用任何模型拟合两个 nuisance(不感兴趣的)函数:
m̂(x):用所有样本,拟合 Y ~ X。这是"不管 T 怎么样的基线 Y"。
ê(x):用所有样本,拟合 T ~ X(logistic/GBDT)。这是倾向得分。
第二步:最小化一个特殊的损失函数:
L(τ) = Σᵢ [ (Yᵢ − m̂(Xᵢ)) − τ(Xᵢ) · (Tᵢ − ê(Xᵢ)) ]²
直观理解:每条样本的 (Y − m̂) 和 (T − ê) 都是"扣掉了 X 的影响"之后的剩余信号。我们要让 τ̂(x) 当"斜率",把这些残差点拟合得最稳。
8.1 优势
理论最扎实:在 mild 条件下,n 趋近无穷时 R-Learner 的 CATE 估计是渐近正态的,可以构造置信区间。
对混淆特别稳:残差化把 X 的影响扣得干干净净,剩下的是 T 的"纯效应"。
如果 τ(x) 是稀疏或光滑的,加正则化(Ridge、Tree、Lasso)效果特别好。
8.2 致命伤
第一步的 m̂ 和 ê 必须准。m̂ 偏了,Y 残差偏,τ̂ 全偏。ê 偏了,T 残差偏,τ̂ 全偏。这叫"nuisance 估计误差的乘性放大",是 R-Learner 的最大隐患。
需要先估好倾向得分——这一步本身就是个大坑。
实现复杂,需要用 cross-fitting(交叉拟合)来避免过拟合——具体做法见 causalml 文档。
左图:Y 与 X 强相关,处理组和对照组混杂。右图:扣掉 m̂(X) 和 ê(X) 之后,T 的独立效应被拉开——这个 gap 就是 τ̂。
什么时候用 R-Learner
数据量充裕、能用 cross-fitting、想拿到置信区间和假设检验
学术论文、效果验证报告、监管报送场景优先选 R。但小样本别碰,nuisance 估计不稳。
9 · 决策卡:你该用哪一个
学习器核心思路最适用避免场景
S
T 当普通特征塞进一个模型
数据极大 / 弱正则基模型
强正则模型(T 被抹平)
T
两个独立模型,相减
两组样本都充足 + 分布重叠
处理组样本极少
X
借对照组大脑补反事实
处理组样本远少于对照组
两组完全不重叠
R
Robinson 残差化 + 元学习
数据充裕 + 需要置信区间
小样本(nuisance 估不准)
现实经验
实战里 80% 的情况下我会先用 T-Learner 当基线,因为它最稳、最容易解释。
如果处理组样本 < 10%,换 X-Learner。
如果需要写论文 / 出报表,换 R-Learner。
S-Learner 几乎只在「特征工程 + 深度模型」场景下值得尝试。
9.1 别忘了基线
无论用哪个 learner,一定要跟一个简单基线对比:
ATE 基线:所有人的平均处理效应。τ̄ = E[Y | T=1] − E[Y | T=0]。这个值"无脑",但用来验证你的 learner 学出来的 CATE 平均起来是否合理。
IPW / Doubly Robust:用倾向得分加权的版本。能交叉验证你的 τ̂ 是不是幻觉。
10 · causalml 上的代码骨架
Uber 的 causalml 包把这四个 learner 都封装好了,最常用的接口叫 BaseXLearner / BaseTLearner / BaseRLearner / BaseSLearner。它们继承自一个元类 BaseLearner,背后接 XGBoost / Random Forest / Lasso 等可插拔基模型。
import numpy as np
from causalml.inference.meta import BaseXLearner, BaseTLearner
from xgboost import XGBRegressor
# 假设你已经有这些数据
X = np.array(...) # shape (n, p), 用户特征
y = np.array(...) # shape (n,), 实际观察到的 Y
t = np.array(...) # shape (n,), 0/1 处理指示
# T-Learner
t_learner = BaseTLearner(learner=XGBRegressor())
t_learner.fit(X, treatment=t, y=y)
cate_t = t_learner.predict(X) # 每个人一个 τ̂
# X-Learner(适合处理组样本少)
x_learner = BaseXLearner(learner=XGBRegressor())
x_learner.fit(X, treatment=t, y=y)
cate_x = x_learner.predict(X)
几个实战细节:
倾向得分裁剪:propensity_score 接近 0 或 1 的样本权重极大,会让 R-Learner 爆炸。causalml 内部会自动 clip 到 [0.05, 0.95] 之类。
基模型选择:默认用 XGBoost 没毛病;如果你追求可解释,换 LassoCV 或决策树;想偷懒,换 RandomForestRegressor。
评估:causalml.metrics 提供 AUUC 和 Qini 系数,专门用来评估 CATE 排序质量。注意:别用 MSE——因为 τ̂ 的"标签"是反事实,你根本不知道真值。
from causalml.metrics import auuc_score, qini_score
# 把 CATE 排序,看排序质量
auuc_score(y, cate_t, treatment=t)
qini_score(y, cate_t, treatment=t)
11 · 局限:什么时候它们都救不了你
写到这里该泼冷水了。四个 meta-learner 都不是银弹。它们救不了以下场景:
11.1 关键混淆变量没观测到
比如你想测「红包对留存的影响」,但用户当天的心情既影响他抢不抢红包、又影响他次日来不来——而你数据里没有心情。NoXLearner 救不了你,得用 IV、DiD、RDD 这类设计型因果推断。
11.2 处理不是 0/1
四个 learner 都默认 T 是二元的。如果你的处理是连续剂量("发多少红包"),需要用 Generalized Random Forest、CEVAE 这类方法。
11.3 网络效应 / 溢出
如果张三发短信会让他的朋友李四也多买一单(社交传染),这四个 learner 默认 SUTVA 会被违反。要么用 spatial / network causal models,要么在随机化时分 cluster。
11.4 时间相关混淆
营销活动随时间变化——周末人多、周一人少。如果你的对照和处理不在同一时间窗,季节性会污染一切。要么时间窗对齐,要么上 Difference-in-Differences。
11.5 太小样本
处理组 < 200 样本的实验,四个 learner 都会很挣扎。这时候往往要先扩样本,或者上 Bayesian Causal Forest。
四个 meta-learner 是统计调整的工具,不是实验设计的替代品。
好的随机化是因,它们只是果。
尾声
回到开头的故事。如果你现在手上有一万条观测、五千条处理、五千条对照,用户特征齐全,小王想知道"该不该发张三"——
别拍脑袋。先用 T-Learner 跑个 baseline,把 50% 的"看起来有效"的用户筛出来;如果处理组样本远少于对照组(比如风控实验 100 个白名单),换 X-Learner;如果要写进财报或监管报表,换 R-Learner。最后——无论选哪个——用 AUUC 排序、跟随机化对照的 ATE 对比一下,再拿给市场部小王。
记住:机器学习给你的不是因果,是带置信区间的猜测。剩下那一截不确定性,得靠业务判断、实验设计、敬畏之心。
延伸阅读(按从浅到深)
① Hernán & Robins《Causal Inference: What If》—— 必读,免费在线版。
② Künzel et al. 2017《Meta-learners for Estimating Heterogeneous Treatment Effects》—— X-Learner 原始论文。
③ Nie & Wager 2017《Quasi-Oracle Estimation of Heterogeneous Treatment Effects》—— R-Learner 原始论文。
④ Uber causalml 官方 notebook —— 看一遍,跑一遍,比读论文管用。
⑤ Brady Neal《Causal Inference for The Brave and True》—— PyWhy 出品的入门书,代码友好。
本文用费曼笔法写就 · 单文件离线 HTML · causalml 0.x 接口约定