机器学习与因果推断 - 第十讲:因果森林——异质性分析与政策学习

从估计到评估与决策:完整讲义

作者
单位

陈志远

中国人民大学商学院

发布于

2026年5月26日

1 引言

在第九讲中,我们学习了广义随机森林(GRF)框架——利用 Robinson 变换消除混杂、通过因果森林自适应地学习核权重,并利用渐近正态性理论进行逐点推断。GRF 让我们能够灵活地估计条件平均处理效应(CATE)τ(x)=E[Y(1)Y(0)X=x]\tau(x) = E[Y(1) - Y(0) \mid X = x]

然而,获得一个 CATE 估计只是第一步。本讲聚焦三个递进的后续问题:

  1. 检验:估计到的异质性是真实的,还是过拟合产生的统计噪声?
  2. 评估:如果异质性真实存在,我们的 CATE 排序有多好?
  3. 决策:如何将 CATE 估计转化为最优处理分配规则?

这三个问题的核心参考文献包括:Chernozhukov et al. (2023) 关于 BLP 和 GATES 的通用检验框架、Yadlowsky et al. (2021) 关于 RATE/AUTOC/Qini 的排序评估理论、以及 Athey & Wager (2021) 关于最优政策学习的经典论文。

1.1 上节课回顾

在第九讲中,我们建立了以下关键概念:

  • 异质性处理效应:CATE τ(x)=E[Y(1)Y(0)X=x]\tau(x) = E[Y(1) - Y(0) \mid X = x] 刻画处理效应随个体特征的变化。ATE 只是 τ(x)\tau(x) 在群体上的平均,掩盖了丰富的异质性信息。
  • 因果树的思想:与传统 CART 最大化 YY 的可预测性不同,因果树最大化子节点间 处理效应差异。这一思想是理解因果森林的基础。
  • GRF 框架:先通过 Robinson 变换 Ỹ=Ym(X)\tilde{Y} = Y - m(X)T̃=Te(X)\tilde{T} = T - e(X) 去除混杂,再用因果森林对 (Ỹ,T̃)(\tilde{Y}, \tilde{T}) 学习自适应权重,最终通过加权局部矩方程求解 CATE。
  • 统计推断:GRF 在正则条件下具有渐近正态性,honesty 机制保证逐点推断有效。诚实分裂确保了估计的无偏性。

本讲在此基础上,转向 CATE 估计的 验证、评估和应用

2 第一部分:从估计到评估——过拟合陷阱

2.1 为什么不能直接信任 CATE 估计

机器学习模型非常灵活,这既是其优势也是其风险。一个关键的陷阱在于:

警告过拟合陷阱

即使真实的处理效应完全均匀(不存在异质性),灵活的 ML 模型也可以在训练数据中拟合出看似显著的异质性模式。这种”虚假异质性”会误导决策。

考虑以下场景:

  • 一位研究者使用因果森林估计了就业培训项目的 CATE
  • 模型显示年轻男性的效应为 +8 分,年长女性的效应为 +2 分
  • 基于此推荐对年轻男性优先提供培训名额
  • 但如果这个异质性只是噪声拟合的结果,就会导致资源错配

2.1.1 预测建模 vs 因果建模的验证差异

在预测建模中,我们有清晰的验证框架:

阶段 预测建模 因果建模
模型训练 拟合 f̂(x)\hat{f}(x) 估计 τ̂(x)\hat{\tau}(x)(上节课)
模型验证 测试集 MSE、交叉验证 异质性检验 + 排序评估(本讲前半)
模型部署 预测未来观测 政策学习与分配规则(本讲后半)

因果建模的验证困难在于:我们 永远无法同时观测 Y(1)Y(1)Y(0)Y(0),因此无法直接计算 τ̂(x)\hat{\tau}(x) 的预测误差。这要求我们发展专门的评估方法。

2.2 三步框架

本讲围绕三个递进问题展开,形成完整的”估计 → 验证 → 决策”管道:

注记从估计到决策的三步框架
  1. 检验(Is it real?):估计到的异质性是真实的吗?→ BLP 检验
  2. 评估(How good?):CATE 模型的排序有多好?→ TOC、AUTOC、Qini 曲线
  3. 决策(What to do?):如何将估计转化为最优分配规则?→ 政策树

3 第二部分:异质性检验——BLP 与 GATES

3.1 BLP 检验的核心思想

最佳线性预测器(Best Linear Predictor, BLP)由 Chernozhukov, Demirer, Duflo & Fernández-Val (2023) 提出,其核心思路非常直观:

如果 τ̂(x)\hat{\tau}(x) 真的捕捉了异质性,那么用它来预测真实 τ(x)\tau(x) 时,应该有正的线性关系。

形式化地,考虑 τ(X)\tau(X)τ̂(X)\hat{\tau}(X) 的最佳线性预测:

τBLP(x)=α0+α1(τ̂(x)τ̂)\tau_{BLP}(x) = \alpha_0 + \alpha_1 (\hat{\tau}(x) - \bar{\hat{\tau}})

其中:

  • α0=E[τ(X)]\alpha_0 = E[\tau(X)]:平均处理效应(ATE)
  • α1=Cov(τ(X),τ̂(X))Var(τ̂(X))\alpha_1 = \frac{\text{Cov}(\tau(X), \hat{\tau}(X))}{\text{Var}(\hat{\tau}(X))}:异质性信号强度

3.1.1 理解 α1\alpha_1 的含义

α1\alpha_1 的值揭示了模型估计质量:

α1\alpha_1 范围 含义
α10\alpha_1 \approx 0 τ̂(x)\hat{\tau}(x) 的变化与真实异质性无关,只是噪声
0<α1<10 < \alpha_1 < 1 模型捕捉到了部分真实异质性,但信号被稀释
α11\alpha_1 \approx 1 模型完美校准,τ̂(x)\hat{\tau}(x) 的线性变化等于 τ(x)\tau(x) 的变化
α1>1\alpha_1 > 1 理论上不常见,可能是模型低估了 CATE 的方差

3.2 BLP 回归方程

如何在实际数据中估计 α0\alpha_0α1\alpha_1?Chernozhukov et al. (2023) 的巧妙之处在于,他们将 BLP 转化为一个标准的 OLS 回归问题。

独立的测试样本 上运行如下回归:

Yi=γXi+α0(Die(Xi))+α1(Die(Xi))(τ̂(Xi)τ̂)+εiY_i = \gamma' X_i + \alpha_0 (D_i - e(X_i)) + \alpha_1 (D_i - e(X_i))(\hat{\tau}(X_i) - \bar{\hat{\tau}}) + \varepsilon_i

其中:

  • DiD_i 是处理状态(0 或 1)
  • e(Xi)=P(Di=1Xi)e(X_i) = P(D_i = 1 \mid X_i) 是倾向得分
  • γXi\gamma' X_i 控制了基线结果的异质性
  • (Die(Xi))(D_i - e(X_i)) 是中心化的处理指标
  • τ̂(Xi)τ̂\hat{\tau}(X_i) - \bar{\hat{\tau}} 是中心化的 CATE 估计
警告样本分割至关重要

训练集用于估计 τ̂(x)\hat{\tau}(x),测试集用于运行 BLP 回归。如果用同一份数据做估计和检验,过拟合会导致虚假的显著性结果——即使模型只拟合了噪声,α1\alpha_1 也可能显著偏离零。

3.2.1 两个检验统计量

BLP 回归自然产生两个检验:

检验 原假设 含义
校准检验 H0:α0=0H_0: \alpha_0 = 0 ATE 是否显著不为零
异质性检验 H0:α1=0H_0: \alpha_1 = 0 CATE 模型是否捕捉了真实异质性

对于异质性分析而言,核心检验是 α1\alpha_1

  • α1\alpha_1 的 p-value 很小 → 存在统计显著的异质性,模型捕捉到了
  • α1\alpha_1 的 p-value 较大 → 无法拒绝”无异质性”的假设

3.3 Python 实现:BLP 检验

下面通过模拟实验演示 BLP 检验的完整流程。

3.3.1 数据生成与模型训练

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LassoCV
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.tree import DecisionTreeRegressor, plot_tree
from econml.dml import CausalForestDML

# 模拟随机实验:异质性处理效应
n = 2000
X = np.column_stack([
    np.random.normal(0, 1, n),      # X0: 年龄
    np.random.binomial(1, 0.5, n),  # X1: 性别
    np.random.normal(0, 1, n),      # X2: 收入
    np.random.normal(0, 1, n),      # X3: 噪声
    np.random.normal(0, 1, n)       # X4: 噪声
])
feature_names = ['年龄', '性别', '收入', '噪声1', '噪声2']

# 随机分配(已知 e(x) = 0.5)
T = np.random.binomial(1, 0.5, n)

# 真实 CATE: τ(x) = 2 + x₀ + 2·x₁
tau_true = 2 + X[:, 0] + 2 * X[:, 1]

# 观测结果
Y = 1 + X[:, 0] + 0.5 * X[:, 2] + T * tau_true + np.random.normal(0, 1, n)

# 样本分割:训练集估计 CATE,测试集评估
train_idx, test_idx = train_test_split(
    np.arange(n), test_size=0.5, random_state=42
)
X_tr, X_te = X[train_idx], X[test_idx]
Y_tr, Y_te = Y[train_idx], Y[test_idx]
T_tr, T_te = T[train_idx], T[test_idx]
tau_true_te = tau_true[test_idx]

# 在训练集上拟合因果森林
cf = CausalForestDML(
    model_y=LassoCV(),
    model_t=LassoCV(),
    n_estimators=500,
    min_samples_leaf=10,
    random_state=42
)
cf.fit(Y_tr, T_tr, X=X_tr, W=X_tr)
tau_hat = cf.effect(X_te).flatten()
n_te = len(Y_te)
print(f"训练集: {len(Y_tr)} 观测;  测试集: {n_te} 观测")
print(f"估计 CATE 范围: [{tau_hat.min():.2f}, {tau_hat.max():.2f}]")
print(f"真实 CATE 范围: [{tau_true_te.min():.2f}, {tau_true_te.max():.2f}]")
训练集: 1000 观测;  测试集: 1000 观测
估计 CATE 范围: [0.38, 5.04]
真实 CATE 范围: [-0.65, 7.85]

3.3.2 运行 BLP 检验

# BLP 检验:在测试样本上验证
D_c = T_te - 0.5                       # 中心化处理(已知 e=0.5)
S = tau_hat - np.mean(tau_hat)          # 中心化 CATE 估计

# 回归矩阵: [1, X, D-e, (D-e)·(τ̂-τ̄)]
Z = np.column_stack([np.ones(n_te), X_te, D_c, D_c * S])

# OLS 估计 + 标准误
b = np.linalg.solve(Z.T @ Z, Z.T @ Y_te)
r = Y_te - Z @ b
se = np.sqrt(np.sum(r**2) / (n_te - Z.shape[1])
             * np.diag(np.linalg.inv(Z.T @ Z)))

print("=" * 55)
print("BLP 检验结果")
print("=" * 55)
print(f"α₀ (ATE 估计)   = {b[-2]:.3f}  (SE = {se[-2]:.3f}, t = {b[-2]/se[-2]:.1f})")
print(f"α₁ (异质性检验) = {b[-1]:.3f}  (SE = {se[-1]:.3f}, t = {b[-1]/se[-1]:.1f})")
print("=" * 55)
if abs(b[-1] / se[-1]) > 1.96:
    print("结论: α₁ 在 5% 水平显著 → 存在真实异质性 ✓")
else:
    print("结论: α₁ 不显著 → 无法拒绝无异质性假设")
=======================================================
BLP 检验结果
=======================================================
α₀ (ATE 估计)   = 3.040  (SE = 0.063, t = 48.4)
α₁ (异质性检验) = 1.002  (SE = 0.047, t = 21.4)
=======================================================
结论: α₁ 在 5% 水平显著 → 存在真实异质性 ✓

3.3.3 结果解读

BLP 检验的两个系数回答了两个不同的问题:

  • α0\alpha_0 显著 → 平均处理效应不为零。这与传统的 ATE 估计一致。
  • α1\alpha_1 显著 → 因果森林估计的 τ̂(x)\hat{\tau}(x) 确实捕捉到了真实的异质性方向。这是关键发现——它意味着不同个体的处理效应确实存在系统性差异。
提示BLP 检验的直觉理解

BLP 检验本质上在问:如果我把 τ̂(x)\hat{\tau}(x) 的值高的人和低的人放在一起比较,高值组真的有更大的处理效应吗?α1>0\alpha_1 > 0 意味着”是的”。

3.4 GATES:分组检验异质性

BLP 检验给出的是一个总结性的统计量。为了更直观地展示异质性的梯度,Chernozhukov et al. (2023) 还提出了分组平均处理效应(GATES)。

3.4.1 GATES 的定义

将测试样本按 τ̂(x)\hat{\tau}(x) 的大小分为 KK 组(通常 K=5K = 5),计算每组的条件平均处理效应:

GATEk=E[Y(1)Y(0)XGk],k=1,,K\text{GATE}_k = E[Y(1) - Y(0) \mid X \in G_k], \quad k = 1, \ldots, K

其中 GkG_k 是按 τ̂(x)\hat{\tau}(x) 的第 kk 个五分位组。

如果异质性是真实的,GATE 应当从 Q1(最低效应组)到 Q5(最高效应组)单调递增。如果所有 GATE 大致相等,则说明 τ̂(x)\hat{\tau}(x) 的排序只是噪声。

3.4.2 GATES 的优势

  • 直观可视化:条形图直接展示异质性的梯度大小
  • 不依赖绝对值:只关注相对排序,因此对 CATE 估计的尺度偏差不敏感
  • 可加置信区间:每个 GATEk\text{GATE}_k 都可以通过标准方法计算置信区间,检验组间差异的统计显著性
  • 非参数性质:不假设 τ(x)\tau(x)τ̂(x)\hat{\tau}(x) 之间的线性关系

3.4.3 Python 实现:GATES 可视化

fig, ax = plt.subplots(figsize=(10, 6))
order = np.argsort(tau_hat)
n_groups = 5
effects, true_effs, group_labels = [], [], []

for g in range(n_groups):
    idx = order[g * n_te // n_groups:(g + 1) * n_te // n_groups]
    # 简单差分估计(随机实验下无偏)
    effect_est = Y_te[idx][T_te[idx]==1].mean() - Y_te[idx][T_te[idx]==0].mean()
    true_eff = np.mean(tau_true_te[idx])
    effects.append(effect_est)
    true_effs.append(true_eff)
    if g == 0:
        group_labels.append(f'Q1\n(低效应)')
    elif g == 4:
        group_labels.append(f'Q5\n(高效应)')
    else:
        group_labels.append(f'Q{g+1}')

x_pos = np.arange(n_groups)
ax.bar(x_pos - 0.15, effects, 0.3, color='#3498db', label='估计 GATE', edgecolor='white')
ax.bar(x_pos + 0.15, true_effs, 0.3, color='#e74c3c', alpha=0.7, label='真实 GATE', edgecolor='white')
ax.set_xticks(x_pos)
ax.set_xticklabels(group_labels)
ax.set_ylabel('组平均处理效应', fontsize=14)
ax.set_title('分组平均处理效应(GATES)', fontsize=16, fontweight='bold')
ax.legend(fontsize=12)
ax.axhline(np.mean(tau_true_te), color='gray', ls=':', alpha=0.5, label='ATE')
plt.tight_layout()
plt.show()

3.4.4 GATES 结果解读

  • 蓝色条形:基于实验数据的差分估计,反映每个五分位组中被处理者和未被处理者结果的差异
  • 红色条形:真实 GATE(在模拟中已知),用于验证估计的准确性
  • 从 Q1 到 Q5 的递增趋势清晰表明异质性是真实的
  • Q1 和 Q5 之间的差距越大,异质性越强
注记BLP vs GATES

BLP 给出一个 p-value,回答”异质性是否存在”的二元问题。GATES 通过可视化回答”异质性有多大、模式是什么”的更丰富问题。两者互补使用效果最佳。

4 第三部分:效应排序评估——TOC 与 Qini

4.1 处理优先级问题

在确认异质性真实存在之后,下一个问题是:我们的 CATE 模型排序得有多好?

假设你有两个 CATE 模型(例如因果森林 vs X-Learner),两者都通过了 BLP 检验。你需要比较它们的排序质量——哪个模型更好地识别了谁应该优先接受处理。

4.1.1 为什么需要专门的排序评估

这个问题与预测建模的评估有本质区别:

  • 在预测建模中,我们直接比较 ŷ\hat{y}yy,计算 MSE 或 AUC
  • 在因果建模中,真实的个体处理效应 τi=Yi(1)Yi(0)\tau_i = Y_i(1) - Y_i(0) 是不可观测的
  • 因此我们无法计算 |τ̂(xi)τi||\hat{\tau}(x_i) - \tau_i| 的均值

一个关键洞察是:对于政策制定而言,我们不需要 τ̂(x)\hat{\tau}(x) 的绝对值准确,只需要 排序正确。即使 τ̂(x)\hat{\tau}(x) 在尺度上有系统性偏差,只要它正确识别了”谁的效应大、谁的效应小”,就足以支撑好的政策决策。

4.2 TOC 曲线

处理操作特征曲线(Treatment Operating Characteristic, TOC)由 Yadlowsky et al. (2021) 系统化提出。

4.2.1 定义

τ̂(x)\hat{\tau}(x) 从高到低排序,计算 top-qq 人群 的平均处理效应:

TOC(q)=E[τ(X)τ̂(X)τ̂1q]\text{TOC}(q) = E[\tau(X) \mid \hat{\tau}(X) \geq \hat{\tau}_{1-q}]

其中 τ̂1q\hat{\tau}_{1-q}τ̂(x)\hat{\tau}(x) 分布的 (1q)(1-q) 分位数。

4.2.2 解读

  • 横轴:目标人群比例 qq(从 0% 到 100%)
  • 纵轴:被选中人群的平均处理效应
  • 理想曲线:从高处开始(最受益的人),随 qq 增大逐渐下降
  • 极端情况q=0+q = 0^+ 时 TOC 接近最大单体效应;q=1q = 1 时 TOC = ATE

一个好的 CATE 模型的 TOC 曲线应当在小 qq 处远高于 ATE,说明模型成功识别了最受益的群体。

4.3 AUTOC 与 RATE 框架

4.3.1 AUTOC

TOC 曲线下面积(Area Under the TOC)定义为:

AUTOC=01[TOC(q)ATE]dq\text{AUTOC} = \int_0^1 [\text{TOC}(q) - \text{ATE}] \, dq

AUTOC 衡量模型的排序能力优于随机分配的整体程度。AUTOC = 0 意味着模型的排序与随机无异。

4.3.2 RATE 框架

AUTOC 是更一般的 RATE(Rank-Weighted Average Treatment Effects)框架的特例:

RATE(τ̂,ω)=01TOC(q)ω(q)dq\text{RATE}(\hat{\tau}, \omega) = \int_0^1 \text{TOC}(q) \cdot \omega(q) \, dq

通过选择不同的权重函数 ω(q)\omega(q),可以得到不同的评估指标:

权重函数 指标名称 特点
ω(q)=1\omega(q) = 1 AUTOC 均匀权重,关注整体排序
ω(q)=q\omega(q) = q Qini 系数 更关注高优先级群体的排序
提示AUTOC vs Qini 的选择
  • 当所有处理分配决策同等重要时(例如全民政策),使用 AUTOC
  • 当预算严重受限、只能处理一小部分人时,使用 Qini 系数(它对 top 排序更敏感)

4.4 Qini 曲线

4.4.1 定义

τ̂(x)\hat{\tau}(x) 从高到低排序,绘制累积增益:

Qini(q)=q[TOC(q)ATE]\text{Qini}(q) = q \cdot [\text{TOC}(q) - \text{ATE}]

Qini 曲线衡量的是:相比于随机分配同样比例的人群,按模型排序能额外获得多少增益。

4.4.2 Qini 系数解读

Qini 系数 含义
Qini > 0 模型排序优于随机分配
Qini \approx 0 模型的排序与随机无异
Qini 越大 排序效果越好

Qini 曲线和 Qini 系数在营销领域(uplift modeling)有着广泛的应用历史,Radcliffe & Surry (2011) 是经典参考文献。

4.5 Python 实现:TOC 与 Qini 曲线

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sorted_idx = np.argsort(-tau_hat)

# 左图:TOC 曲线
fracs = np.linspace(0.05, 1.0, 20)
toc_est, toc_true = [], []
for q in fracs:
    k = int(q * n_te)
    idx = sorted_idx[:k]
    toc_est.append(Y_te[idx][T_te[idx]==1].mean() - Y_te[idx][T_te[idx]==0].mean())
    toc_true.append(np.mean(tau_true_te[idx]))

axes[0].plot(fracs, toc_est, 'b-o', ms=4, lw=2, label='估计 TOC')
axes[0].plot(fracs, toc_true, 'r--', lw=2, alpha=0.7, label='真实 TOC')
axes[0].axhline(np.mean(tau_true_te), color='gray', ls=':', label='ATE')
axes[0].set_xlabel('目标人群比例 q', fontsize=13)
axes[0].set_ylabel('Top-q 组平均效应', fontsize=13)
axes[0].set_title('TOC 曲线', fontsize=15, fontweight='bold')
axes[0].legend(fontsize=11)

# 右图:Qini 曲线
ate = np.mean(tau_true_te)
qini_vals = [q * (t - ate) for q, t in zip(fracs, toc_true)]
axes[1].plot(fracs, qini_vals, 'b-o', ms=4, lw=2, label='因果森林')
axes[1].axhline(0, color='gray', ls=':', label='随机分配')
axes[1].fill_between(fracs, 0, qini_vals, alpha=0.15, color='blue')
axes[1].set_xlabel('目标人群比例', fontsize=13)
axes[1].set_ylabel('累积增益(vs 随机)', fontsize=13)
axes[1].set_title('Qini 曲线', fontsize=15, fontweight='bold')
axes[1].legend(fontsize=11)
plt.tight_layout()
plt.show()

# 计算 AUTOC 和 Qini 系数
autoc = np.trapz([t - ate for t in toc_true], fracs)
qini_coef = np.trapz(qini_vals, fracs)
print(f"AUTOC (排序增益面积):  {autoc:.3f}")
print(f"Qini 系数:             {qini_coef:.3f}")

AUTOC (排序增益面积):  1.104
Qini 系数:             0.400

4.5.1 TOC 曲线解读

  • 蓝色实线(估计 TOC):基于因果森林排序的实验差分估计
  • 红色虚线(真实 TOC):基于已知的 τ(x)\tau(x) 计算的真实 TOC
  • 灰色虚线:ATE 基准线
  • 曲线从高处平滑下降表明模型排序质量良好:优先选中的人确实具有更高的效应

4.5.2 Qini 曲线解读

  • 正值区域(蓝色填充)表示模型优于随机分配
  • 面积越大,排序质量越高
  • 如果曲线出现负值区域,说明模型在某个比例段的排序甚至不如随机

4.6 评估指标对比

指标 衡量内容 适用场景 是否需要样本分割
BLP α1\alpha_1 异质性是否真实存在 初步验证,单模型
GATES 各组效应的梯度 可视化异质性强度
AUTOC 排序整体优劣 模型间比较
Qini 系数 高优先级排序质量 预算受限的场景
警告所有指标都需要样本分割

在训练集上估计 CATE,在独立的测试集上评估。否则过拟合会让任何模型看起来都很好。这一原则贯穿本讲的所有方法。

5 第四部分:变量重要性分析

5.1 识别异质性的驱动因素

在确认异质性存在并评估了排序质量之后,一个自然的问题是:哪些特征驱动了处理效应的差异?

因果森林的变量重要性(variable importance)提供了一个系统化的答案。它衡量每个特征对 CATE 变异的贡献程度。

5.1.1 两种计算方法

方法一:分裂频率法

统计因果森林中每个变量被选为分裂变量的频率。频率越高的变量对 CATE 的异质性贡献越大。在 grf 包中,variable_importance() 函数直接返回这个指标。

方法二:代理模型法

训练一个梯度提升模型来学习 τ̂(x)\hat{\tau}(x),然后提取该模型的特征重要性。这种方法不依赖因果森林内部的分裂结构,可以作为交叉验证的手段。

两种方法衡量的都是同一件事:每个特征对 预测异质性的贡献

5.2 Python 实现:变量重要性

# 代理模型法:用 GBRT 学习 CATE → 提取特征重要性
surrogate = GradientBoostingRegressor(
    n_estimators=200, max_depth=3, random_state=42
)
surrogate.fit(X_te, tau_hat)
importances = surrogate.feature_importances_

fig, ax = plt.subplots(figsize=(10, 5))
sorted_imp = np.argsort(importances)
colors = ['#e74c3c' if feature_names[i] in ['年龄', '性别']
          else '#bdc3c7' for i in sorted_imp]
ax.barh(range(len(importances)), importances[sorted_imp],
        color=colors, edgecolor='white')
ax.set_yticks(range(len(importances)))
ax.set_yticklabels([feature_names[i] for i in sorted_imp])
ax.set_xlabel('重要性得分', fontsize=14)
ax.set_title('CATE 变量重要性', fontsize=16, fontweight='bold')

# 添加图例
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor='#e74c3c', label='真实驱动变量'),
                   Patch(facecolor='#bdc3c7', label='噪声变量')]
ax.legend(handles=legend_elements, fontsize=11)
plt.tight_layout()
plt.show()

print("各变量重要性得分:")
for i in sorted_imp[::-1]:
    marker = "★" if feature_names[i] in ['年龄', '性别'] else " "
    print(f"  {marker} {feature_names[i]}: {importances[i]:.3f}")

各变量重要性得分:
  ★ 性别: 0.627
  ★ 年龄: 0.372
    噪声1: 0.001
    噪声2: 0.001
    收入: 0.000

5.2.1 结果解读

在我们的模拟中,真实的 CATE 为 τ(x)=2+x0+2x1\tau(x) = 2 + x_0 + 2x_1,其中 x0x_0(年龄)和 x1x_1(性别)是唯一的驱动因素。变量重要性分析应当将这两个变量排在前两位,而噪声变量(收入、噪声 1、噪声 2)的重要性接近零。

5.3 变量重要性 \neq 因果关系

警告重要区分

变量重要性回答的是”哪些特征 预测 了异质性”,而非”哪些特征 导致 了异质性”。两者有本质区别。

为什么不是因果关系?

  • 年龄的重要性高 \neq 年龄导致了处理效应的差异
  • 可能是年龄与某个未观测的真正调节变量(例如学习动机)高度相关
  • 如果年龄只是代理变量(proxy),基于年龄进行干预分配可能无效

最佳实践

  1. 将变量重要性作为 假设生成 的起点,而非结论
  2. 结合 领域知识 对结果进行验证和解释
  3. 如果需要因果性的调节效应分析,应使用更严格的框架(例如 causal mediation analysis)

5.4 R 实现参考

在 R 的 grf 包中,变量重要性的获取非常直接:

library(grf)

# 训练因果森林
cf <- causal_forest(X, Y, W)

# 变量重要性(分裂频率法)
var_imp <- variable_importance(cf)
names(var_imp) <- colnames(X)
sort(var_imp, decreasing = TRUE)

grf 包的 variable_importance() 直接返回标准化的分裂频率,无需额外步骤。

6 第五部分:政策树与政策学习

6.1 从 CATE 到最优决策

到目前为止,我们已经完成了 CATE 的估计、检验和评估。最后一步也是最重要的一步:将 CATE 估计转化为可执行的政策决策

6.1.1 政策制定的核心问题

回到就业培训的例子:你已经估计了每个人的 τ̂(x)\hat{\tau}(x),通过 BLP 检验确认异质性真实存在,Qini 曲线显示模型排序质量良好。现在的问题是:

注记预算约束下的分配问题

培训预算只够覆盖 50% 的申请者。如何设计分配规则 π(x){0,1}\pi(x) \in \{0, 1\},使得社会福利最大化?

6.1.2 朴素方案 vs 政策学习

朴素方案:按 τ̂(x)\hat{\tau}(x) 从高到低排序,选取 top-50%。

  • 优点:简单、直接利用 CATE 估计
  • 缺点:规则不透明,无法向决策者解释”为什么选这些人”
  • 问题:τ̂(x)\hat{\tau}(x) 的绝对值可能有偏差,排序可能不够稳健

政策学习方案:学习一棵简单的决策树,给出可解释的分配规则。

  • 优点:规则透明、可审计、便于向非技术决策者解释
  • 输出形式:例如”年龄 \leq 30 且未就业 → 分配培训”

6.2 政策学习的形式化

6.2.1 最优分配问题

寻找政策规则 π:𝒳{0,1}\pi: \mathcal{X} \to \{0, 1\},最大化社会福利:

π*=argmaxπΠE[τ(X)π(X)]\pi^* = \arg\max_{\pi \in \Pi} E[\tau(X) \cdot \pi(X)]

如果有预算约束 E[π(X)]BE[\pi(X)] \leq B,则成为约束优化问题。

6.2.2 估计社会福利

由于 τ(X)\tau(X) 不可直接观测,Athey & Wager (2021) 提出使用 AIPW (Augmented Inverse Propensity Weighting) 得分作为 τ(X)\tau(X) 的替代:

Γ̂i=μ̂1(Xi)μ̂0(Xi)+Di(Yiμ̂1(Xi))e(Xi)(1Di)(Yiμ̂0(Xi))1e(Xi)\hat{\Gamma}_i = \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) + \frac{D_i (Y_i - \hat{\mu}_1(X_i))}{e(X_i)} - \frac{(1-D_i)(Y_i - \hat{\mu}_0(X_i))}{1 - e(X_i)}

注记AIPW 得分的优越性
  • Γ̂i\hat{\Gamma}_iτ(Xi)\tau(X_i) 的无偏个体级估计
  • Γ̂i\hat{\Gamma}_i 求加权平均直接给出社会福利的无偏估计
  • 双重稳健性:即使倾向得分模型 ê(x)\hat{e}(x) 或结果模型 μ̂d(x)\hat{\mu}_d(x) 之一有偏差,AIPW 得分仍然一致

这样,最优政策问题变为:

π*=argmaxπΠ1ni=1nΓ̂iπ(Xi)\pi^* = \arg\max_{\pi \in \Pi} \frac{1}{n} \sum_{i=1}^{n} \hat{\Gamma}_i \cdot \pi(X_i)

6.3 最优政策树

6.3.1 为什么用树

在政策空间 Π\Pi 的选择上,Athey & Wager (2021) 推荐使用 深度有限的决策树

Π={深度d 的决策树}\Pi = \{\text{深度} \leq d \text{ 的决策树}\}

限制树的深度确保规则可解释:

深度 最大叶节点数 最大条件数 可解释性
1 2 1 极高:一个二分规则
2 4 3 高:两层简单条件
3 8 7 中等:三层条件组合

6.3.2 实现步骤

  1. 计算 AIPW 得分 Γ̂i\hat{\Gamma}_i(或近似使用 τ̂(Xi)\hat{\tau}(X_i)
  2. 搜索使 Γ̂iπ(Xi)\sum \hat{\Gamma}_i \cdot \pi(X_i) 最大的深度-dd
  3. 叶节点中 Γ>0\bar{\Gamma} > 0 → 分配处理;Γ0\bar{\Gamma} \leq 0 → 不分配
提示简化实现

在实际中,可以用 τ̂(Xi)\hat{\tau}(X_i) 代替 AIPW 得分作为拟合政策树的目标。虽然理论上不如 AIPW 得分稳健,但在随机实验的设置下(已知 e(x)e(x))差别通常较小。

6.4 Python 实现:政策树

# 深度-2 政策树(基于 CATE 估计)
tree = DecisionTreeRegressor(max_depth=2, random_state=42)
tree.fit(X_te, tau_hat)

fig, ax = plt.subplots(figsize=(14, 7))
plot_tree(tree, feature_names=feature_names, filled=True,
          rounded=True, fontsize=11, ax=ax, impurity=False)
ax.set_title('深度-2 政策树', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

6.4.1 解读政策树

  • 每个叶节点显示该组的 CATE 预测均值和样本量
  • 叶节点值 > 0 → 建议干预(处理效应为正)
  • 叶节点值 \leq 0 → 不建议干预
  • 分裂条件给出了可解释的分配规则

在我们的模拟中,由于 τ(x)=2+x0+2x1\tau(x) = 2 + x_0 + 2x_1,理想的政策树应当主要基于性别(x1x_1)和年龄(x0x_0)进行分裂。

6.5 Python 实现:分配策略比较

下面比较三种不同分配策略在预算约束下的表现:

# 预算约束:只能干预 50% 的人群
budget = n_te // 2
np.random.seed(123)
random_idx = np.random.choice(n_te, budget, replace=False)
model_idx = np.argsort(-tau_hat)[:budget]
oracle_idx = np.argsort(-tau_true_te)[:budget]

strats = ['随机分配', '因果森林\nTop-50%', '神谕策略\n(理论最优)']
effs = [np.mean(tau_true_te[random_idx]),
        np.mean(tau_true_te[model_idx]),
        np.mean(tau_true_te[oracle_idx])]

fig, ax = plt.subplots(figsize=(9, 5))
bars = ax.bar(strats, effs, color=['#95a5a6', '#3498db', '#e74c3c'], width=0.5)
for bar, val in zip(bars, effs):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05,
            f'{val:.2f}', ha='center', fontsize=14, fontweight='bold')
ax.set_ylabel('被干预人群的平均处理效应', fontsize=14)
ax.set_title('预算约束下的分配策略比较', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()

# 详细比较
print("=" * 50)
print("分配策略比较(预算 = 50%)")
print("=" * 50)
for s, e in zip(['随机分配', '因果森林 Top-50%', '神谕策略(理论最优)'], effs):
    print(f"  {s}: 平均效应 = {e:.3f}")
print(f"\n因果森林 vs 随机: 提升 {effs[1] - effs[0]:.3f}")
print(f"理论最优 vs 随机: 提升 {effs[2] - effs[0]:.3f}")
print(f"因果森林效率 = {(effs[1]-effs[0])/(effs[2]-effs[0])*100:.0f}%(相对于理论上限)")

==================================================
分配策略比较(预算 = 50%)
==================================================
  随机分配: 平均效应 = 3.055
  因果森林 Top-50%: 平均效应 = 4.250
  神谕策略(理论最优): 平均效应 = 4.257

因果森林 vs 随机: 提升 1.196
理论最优 vs 随机: 提升 1.202
因果森林效率 = 99%(相对于理论上限)

6.5.1 分配策略比较解读

  • 随机分配:无差别选取 50% 人群,预期平均效应等于 ATE
  • 因果森林 Top-50%:选取 τ̂(x)\hat{\tau}(x) 最高的 50% 人群
  • 神谕策略:如果我们知道真实 τ(x)\tau(x),选取最高的 50%(理论上限)

因果森林策略的效率比例(相对于理论最优的提升百分比)衡量了实际可实现的收益。

6.6 政策学习的实践要点

警告三条核心准则

1. 交叉验证福利

不要在训练政策的数据上评估福利。应当使用独立的验证数据评估政策的期望效果。

2. 约束要明确

预算上限、公平性约束、伦理限制都应在政策学习中显式编码。

3. 简单优先

优先选择可解释的规则(浅树、线性规则)。复杂的深度决策树虽然可能在训练数据上表现更好,但泛化性和可审计性差。

6.6.1 伦理考量

政策学习的应用需要注意以下伦理问题:

  • 公平性:按种族、性别等敏感属性差异化分配可能引发公平性和法律问题
  • 历史偏见τ̂(x)\hat{\tau}(x) 可能反映历史数据中的偏见而非真实因果效应
  • 人类监督:政策学习 \neq 自动决策。算法的建议应当与领域专家的判断结合
  • 透明度:政策规则需要向受影响群体公开,接受公众审查

6.7 R 实现参考:policytree

R 的 policytree 包(由 Athey 团队开发)提供了最优政策树的原始实现:

library(grf)
library(policytree)

# 1. 估计因果森林
cf <- causal_forest(X_train, Y_train, W_train)

# 2. 计算 AIPW 得分
gamma <- double_robust_scores(cf)

# 3. 训练最优政策树(深度 = 2)
pt <- policy_tree(X_test, gamma[test_idx, ], depth = 2)

# 4. 预测分配
pi <- predict(pt, X_new)  # 返回 1(处理)或 2(不处理)

# 可视化
plot(pt)
提示Python vs R 工具推荐
  • Pythoneconml.dml.CausalForestDML + sklearn.tree.DecisionTreeRegressor(简易政策树)
  • Rgrf::causal_forest + policytree::policy_tree(推荐,理论最优实现)
  • policytree 实现了精确的最优政策树搜索,而 sklearn 的 DecisionTreeRegressor 使用贪心分裂,可能不是全局最优

7 第六部分:完整工作流总结

7.1 从 CATE 到决策的完整管道

本讲介绍的方法构成了一个完整的分析管道:

                              ┌──────────────┐
                              │  估计 CATE   │
                              │ (上节课 GRF)│
                              └──────┬───────┘
                                     │
                              ┌──────▼───────┐
                  Step 1      │  BLP 检验    │
               (是否真实?)    │  + GATES     │
                              └──────┬───────┘
                                     │ 如果 α₁ 显著
                              ┌──────▼───────┐
                  Step 2      │  TOC / Qini  │
               (排序多好?)    │  AUTOC 比较   │
                              └──────┬───────┘
                                     │
                     ┌───────────────┼───────────────┐
                     │               │               │
              ┌──────▼──────┐ ┌─────▼──────┐ ┌──────▼──────┐
              │ 变量重要性  │ │  政策树    │ │ 策略比较   │
              │ (哪些特征)│ │ (分配规则)│ │ (福利评估)│
              └─────────────┘ └────────────┘ └─────────────┘

7.1.1 关键原则

  1. 始终先检验:BLP/GATES 是后续所有分析的前提
  2. 始终样本分割:训练 ↔︎ 评估/决策使用不同数据
  3. 排序优先于校准:绝对值不重要,排序才重要
  4. 可解释性约束:政策树深度 \leq 2-3

7.2 推荐工具

7.2.1 Python 生态

功能
econml CausalForestDML(CATE 估计 + 推断)
causalml UpliftTreeClassifier、plot_qini
sklearn DecisionTreeRegressor(简易政策树)

7.2.2 R 生态

功能
grf causal_forest、best_linear_projection、rank_average_treatment_effect
policytree optimal_policy_tree(推荐,原始实现)
uplift Qini 曲线绘制

8 第七部分:练习与思考

8.0.1 练习 1:BLP 检验的直觉

假设在一个完全同质的实验中(所有人的处理效应都是 τ=3\tau = 3),你仍然使用因果森林估计了 τ̂(x)\hat{\tau}(x)(此时 τ̂(x)\hat{\tau}(x) 的变化纯粹来自噪声)。

  1. 在 BLP 检验中,α0\alpha_0 应该等于多少?
  2. α1\alpha_1 的期望值是多少?通过样本分割获得的 α̂1\hat{\alpha}_1 是否显著?
  3. 如果不做样本分割,直接在训练数据上跑 BLP 回归,α1\alpha_1 会怎样?

8.0.2 练习 2:Qini 曲线的构建

给定以下数据(10 个个体,随机实验),手动绘制 Qini 曲线:

个体 X1X_1 TT YY τ̂(x)\hat{\tau}(x)
1 1 8 4.2
2 0 3 1.1
3 0 4 3.8
4 1 5 0.9
5 1 9 4.5
6 0 2 1.3
7 0 5 3.5
8 1 4 0.7
9 1 7 3.9
10 0 3 1.0

步骤:(a) 按 τ̂(x)\hat{\tau}(x) 排序;(b) 计算 ATE;(c) 对 q=20%,40%,,100%q = 20\%, 40\%, \ldots, 100\%,计算 TOC(q)\text{TOC}(q)Qini(q)=q[TOC(q)ATE]\text{Qini}(q) = q \cdot [\text{TOC}(q) - \text{ATE}]

8.0.3 练习 3:政策树设计

你是一家在线教育平台的数据科学家,公司想为用户提供个性化的学习辅导(有预算限制)。已知的特征包括:学习时长、测验成绩、课程完成率、登录频率。

  1. 你会如何设计 CATE 估计 → 政策制定的完整管道?
  2. 如果政策树的深度限制为 2,最多能产生几种分配策略?
  3. 在此场景下,“公平性”约束应如何体现?

8.0.4 练习 4:方法选择

对于以下场景,选择最合适的评估工具并解释原因:

  1. 一个慈善组织想确认其 microcredit 项目是否对不同收入群体有差异化影响
  2. 两个研究团队分别用因果森林和 X-Learner 估计了 CATE,需要比较哪个更好
  3. 一家制药公司想基于基因特征为患者制定个性化用药方案

9 参考文献

  1. Chernozhukov, V., Demirer, M., Duflo, E., & Fernández-Val, I. (2023). Generic Machine Learning Inference on Heterogeneous Treatment Effects in Randomized Experiments. NBER Working Paper No. 24678.

  2. Yadlowsky, S., Fleming, S., Shah, N., Brunskill, E., & Wager, S. (2021). Evaluating Treatment Prioritization Rules via Rank-Weighted Average Treatment Effects. arXiv:2111.07966.

  3. Athey, S., & Wager, S. (2021). Policy Learning with Observational Data. Econometrica, 89(1), 133–161.

  4. Athey, S., & Imbens, G. W. (2016). Recursive Partitioning for Heterogeneous Causal Effects. Proceedings of the National Academy of Sciences, 113(27), 7353–7360.

  5. Athey, S., Tibshirani, J., & Wager, S. (2019). Generalized Random Forests. Annals of Statistics, 47(2), 1148–1178.

  6. Radcliffe, N. J., & Surry, P. D. (2011). Real-World Uplift Modelling with Significance-Based Uplift Trees. Stochastic Solutions White Paper.

  7. Wager, S., & Athey, S. (2018). Estimation and Inference of Heterogeneous Treatment Effects using Random Forests. Journal of the American Statistical Association, 113(523), 1228–1242.

下节课预告

第十一讲:双重机器学习——高维控制与去偏估计

  • 朴素 ML 插件估计的偏差问题
  • Neyman 正交性:为什么需要”双重”修正
  • DML 框架与部分线性模型
  • 高维控制变量的因果效应估计

核心思想:当控制变量数量很多时(甚至超过样本量),传统方法失效。双重机器学习 通过正交化技巧,让我们在使用 ML 控制混杂的同时得到有效的因果推断。