---
title: "机器学习与因果推断 - 第十讲:因果森林——异质性分析与政策学习"
subtitle: "从估计到评估与决策:完整讲义"
author: "陈志远"
institute: "中国人民大学商学院"
date: "2026-05-26"
format:
html:
theme: cosmo
css: lecture-notes.css
html-math-method: mathml
toc: true
toc-depth: 3
number-sections: true
code-fold: false
code-tools: true
highlight-style: github
embed-resources: true
page-layout: article
execute:
echo: true
warning: false
message: false
eval: true
cache: false
fig-width: 10
fig-height: 6
dpi: 150
lang: zh
jupyter: python3
---
```{python}
#| echo: false
#| output: false
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import warnings
warnings.filterwarnings('ignore')
# Set up matplotlib for Chinese display
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 150
np.random.seed(42)
```
# 引言 {#sec-intro}
在第九讲中,我们学习了广义随机森林(GRF)框架——利用 Robinson 变换消除混杂、通过因果森林自适应地学习核权重,并利用渐近正态性理论进行逐点推断。GRF 让我们能够灵活地估计条件平均处理效应(CATE)$\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) 关于最优政策学习的经典论文。
## 上节课回顾 {#sec-review}
在第九讲中,我们建立了以下关键概念:
- **异质性处理效应**:CATE $\tau(x) = E[Y(1) - Y(0) \mid X = x]$ 刻画处理效应随个体特征的变化。ATE 只是 $\tau(x)$ 在群体上的平均,掩盖了丰富的异质性信息。
- **因果树的思想**:与传统 CART 最大化 $Y$ 的可预测性不同,因果树最大化子节点间 *处理效应差异*。这一思想是理解因果森林的基础。
- **GRF 框架**:先通过 Robinson 变换 $\tilde{Y} = Y - m(X)$、$\tilde{T} = T - e(X)$ 去除混杂,再用因果森林对 $(\tilde{Y}, \tilde{T})$ 学习自适应权重,最终通过加权局部矩方程求解 CATE。
- **统计推断**:GRF 在正则条件下具有渐近正态性,honesty 机制保证逐点推断有效。诚实分裂确保了估计的无偏性。
本讲在此基础上,转向 CATE 估计的 *验证、评估和应用*。
# 第一部分:从估计到评估——过拟合陷阱 {#sec-overfit}
## 为什么不能直接信任 CATE 估计 {#sec-why-not-trust}
机器学习模型非常灵活,这既是其优势也是其风险。一个关键的陷阱在于:
::: {.callout-warning}
## 过拟合陷阱
即使真实的处理效应完全均匀(不存在异质性),灵活的 ML 模型也可以在训练数据中拟合出看似显著的异质性模式。这种"虚假异质性"会误导决策。
:::
考虑以下场景:
- 一位研究者使用因果森林估计了就业培训项目的 CATE
- 模型显示年轻男性的效应为 +8 分,年长女性的效应为 +2 分
- 基于此推荐对年轻男性优先提供培训名额
- 但如果这个异质性只是噪声拟合的结果,就会导致资源错配
### 预测建模 vs 因果建模的验证差异
在预测建模中,我们有清晰的验证框架:
| 阶段 | 预测建模 | 因果建模 |
|:---|:---|:---|
| 模型训练 | 拟合 $\hat{f}(x)$ | 估计 $\hat{\tau}(x)$(上节课) |
| 模型验证 | 测试集 MSE、交叉验证 | **异质性检验 + 排序评估**(本讲前半) |
| 模型部署 | 预测未来观测 | **政策学习与分配规则**(本讲后半) |
因果建模的验证困难在于:我们 *永远无法同时观测* $Y(1)$ 和 $Y(0)$,因此无法直接计算 $\hat{\tau}(x)$ 的预测误差。这要求我们发展专门的评估方法。
## 三步框架 {#sec-three-steps}
本讲围绕三个递进问题展开,形成完整的"估计 → 验证 → 决策"管道:
::: {.callout-note}
## 从估计到决策的三步框架
1. **检验**(Is it real?):估计到的异质性是真实的吗?→ BLP 检验
2. **评估**(How good?):CATE 模型的排序有多好?→ TOC、AUTOC、Qini 曲线
3. **决策**(What to do?):如何将估计转化为最优分配规则?→ 政策树
:::
# 第二部分:异质性检验——BLP 与 GATES {#sec-blp}
## BLP 检验的核心思想 {#sec-blp-idea}
最佳线性预测器(Best Linear Predictor, BLP)由 Chernozhukov, Demirer, Duflo & Fernández-Val (2023) 提出,其核心思路非常直观:
*如果 $\hat{\tau}(x)$ 真的捕捉了异质性,那么用它来预测真实 $\tau(x)$ 时,应该有正的线性关系。*
形式化地,考虑 $\tau(X)$ 对 $\hat{\tau}(X)$ 的最佳线性预测:
$$\tau_{BLP}(x) = \alpha_0 + \alpha_1 (\hat{\tau}(x) - \bar{\hat{\tau}})$$
其中:
- $\alpha_0 = E[\tau(X)]$:平均处理效应(ATE)
- $\alpha_1 = \frac{\text{Cov}(\tau(X), \hat{\tau}(X))}{\text{Var}(\hat{\tau}(X))}$:异质性信号强度
### 理解 $\alpha_1$ 的含义
$\alpha_1$ 的值揭示了模型估计质量:
| $\alpha_1$ 范围 | 含义 |
|:---|:---|
| $\alpha_1 \approx 0$ | $\hat{\tau}(x)$ 的变化与真实异质性无关,只是噪声 |
| $0 < \alpha_1 < 1$ | 模型捕捉到了部分真实异质性,但信号被稀释 |
| $\alpha_1 \approx 1$ | 模型完美校准,$\hat{\tau}(x)$ 的线性变化等于 $\tau(x)$ 的变化 |
| $\alpha_1 > 1$ | 理论上不常见,可能是模型低估了 CATE 的方差 |
## BLP 回归方程 {#sec-blp-regression}
如何在实际数据中估计 $\alpha_0$ 和 $\alpha_1$?Chernozhukov et al. (2023) 的巧妙之处在于,他们将 BLP 转化为一个标准的 OLS 回归问题。
在 *独立的测试样本* 上运行如下回归:
$$Y_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$$
其中:
- $D_i$ 是处理状态(0 或 1)
- $e(X_i) = P(D_i = 1 \mid X_i)$ 是倾向得分
- $\gamma' X_i$ 控制了基线结果的异质性
- $(D_i - e(X_i))$ 是中心化的处理指标
- $\hat{\tau}(X_i) - \bar{\hat{\tau}}$ 是中心化的 CATE 估计
::: {.callout-warning}
## 样本分割至关重要
训练集用于估计 $\hat{\tau}(x)$,测试集用于运行 BLP 回归。如果用同一份数据做估计和检验,过拟合会导致虚假的显著性结果——即使模型只拟合了噪声,$\alpha_1$ 也可能显著偏离零。
:::
### 两个检验统计量
BLP 回归自然产生两个检验:
| 检验 | 原假设 | 含义 |
|:---|:---|:---|
| **校准检验** | $H_0: \alpha_0 = 0$ | ATE 是否显著不为零 |
| **异质性检验** | $H_0: \alpha_1 = 0$ | CATE 模型是否捕捉了真实异质性 |
对于异质性分析而言,核心检验是 $\alpha_1$:
- $\alpha_1$ 的 p-value 很小 → 存在统计显著的异质性,模型捕捉到了
- $\alpha_1$ 的 p-value 较大 → 无法拒绝"无异质性"的假设
## Python 实现:BLP 检验 {#sec-blp-python}
下面通过模拟实验演示 BLP 检验的完整流程。
### 数据生成与模型训练
```{python}
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}]")
```
### 运行 BLP 检验
```{python}
# 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 检验的两个系数回答了两个不同的问题:
- **$\alpha_0$ 显著** → 平均处理效应不为零。这与传统的 ATE 估计一致。
- **$\alpha_1$ 显著** → 因果森林估计的 $\hat{\tau}(x)$ 确实捕捉到了真实的异质性方向。这是关键发现——它意味着不同个体的处理效应确实存在系统性差异。
::: {.callout-tip}
## BLP 检验的直觉理解
BLP 检验本质上在问:如果我把 $\hat{\tau}(x)$ 的值高的人和低的人放在一起比较,高值组真的有更大的处理效应吗?$\alpha_1 > 0$ 意味着"是的"。
:::
## GATES:分组检验异质性 {#sec-gates}
BLP 检验给出的是一个总结性的统计量。为了更直观地展示异质性的梯度,Chernozhukov et al. (2023) 还提出了分组平均处理效应(GATES)。
### GATES 的定义
将测试样本按 $\hat{\tau}(x)$ 的大小分为 $K$ 组(通常 $K = 5$),计算每组的条件平均处理效应:
$$\text{GATE}_k = E[Y(1) - Y(0) \mid X \in G_k], \quad k = 1, \ldots, K$$
其中 $G_k$ 是按 $\hat{\tau}(x)$ 的第 $k$ 个五分位组。
如果异质性是真实的,GATE 应当从 Q1(最低效应组)到 Q5(最高效应组)单调递增。如果所有 GATE 大致相等,则说明 $\hat{\tau}(x)$ 的排序只是噪声。
### GATES 的优势
- **直观可视化**:条形图直接展示异质性的梯度大小
- **不依赖绝对值**:只关注相对排序,因此对 CATE 估计的尺度偏差不敏感
- **可加置信区间**:每个 $\text{GATE}_k$ 都可以通过标准方法计算置信区间,检验组间差异的统计显著性
- **非参数性质**:不假设 $\tau(x)$ 与 $\hat{\tau}(x)$ 之间的线性关系
### Python 实现:GATES 可视化
```{python}
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()
```
### GATES 结果解读
- **蓝色条形**:基于实验数据的差分估计,反映每个五分位组中被处理者和未被处理者结果的差异
- **红色条形**:真实 GATE(在模拟中已知),用于验证估计的准确性
- 从 Q1 到 Q5 的递增趋势清晰表明异质性是真实的
- Q1 和 Q5 之间的差距越大,异质性越强
::: {.callout-note}
## BLP vs GATES
BLP 给出一个 p-value,回答"异质性是否存在"的二元问题。GATES 通过可视化回答"异质性有多大、模式是什么"的更丰富问题。两者互补使用效果最佳。
:::
# 第三部分:效应排序评估——TOC 与 Qini {#sec-toc}
## 处理优先级问题 {#sec-priority}
在确认异质性真实存在之后,下一个问题是:*我们的 CATE 模型排序得有多好?*
假设你有两个 CATE 模型(例如因果森林 vs X-Learner),两者都通过了 BLP 检验。你需要比较它们的排序质量——哪个模型更好地识别了谁应该优先接受处理。
### 为什么需要专门的排序评估
这个问题与预测建模的评估有本质区别:
- 在预测建模中,我们直接比较 $\hat{y}$ 和 $y$,计算 MSE 或 AUC
- 在因果建模中,*真实的个体处理效应 $\tau_i = Y_i(1) - Y_i(0)$ 是不可观测的*
- 因此我们无法计算 $|\hat{\tau}(x_i) - \tau_i|$ 的均值
一个关键洞察是:对于政策制定而言,我们不需要 $\hat{\tau}(x)$ 的绝对值准确,只需要 *排序正确*。即使 $\hat{\tau}(x)$ 在尺度上有系统性偏差,只要它正确识别了"谁的效应大、谁的效应小",就足以支撑好的政策决策。
## TOC 曲线 {#sec-toc-curve}
处理操作特征曲线(Treatment Operating Characteristic, TOC)由 Yadlowsky et al. (2021) 系统化提出。
### 定义
按 $\hat{\tau}(x)$ 从高到低排序,计算 *top-$q$ 人群* 的平均处理效应:
$$\text{TOC}(q) = E[\tau(X) \mid \hat{\tau}(X) \geq \hat{\tau}_{1-q}]$$
其中 $\hat{\tau}_{1-q}$ 是 $\hat{\tau}(x)$ 分布的 $(1-q)$ 分位数。
### 解读
- **横轴**:目标人群比例 $q$(从 0% 到 100%)
- **纵轴**:被选中人群的平均处理效应
- **理想曲线**:从高处开始(最受益的人),随 $q$ 增大逐渐下降
- **极端情况**:$q = 0^+$ 时 TOC 接近最大单体效应;$q = 1$ 时 TOC = ATE
一个好的 CATE 模型的 TOC 曲线应当在小 $q$ 处远高于 ATE,说明模型成功识别了最受益的群体。
## AUTOC 与 RATE 框架 {#sec-autoc}
### AUTOC
TOC 曲线下面积(Area Under the TOC)定义为:
$$\text{AUTOC} = \int_0^1 [\text{TOC}(q) - \text{ATE}] \, dq$$
AUTOC 衡量模型的排序能力优于随机分配的整体程度。AUTOC = 0 意味着模型的排序与随机无异。
### RATE 框架
AUTOC 是更一般的 RATE(Rank-Weighted Average Treatment Effects)框架的特例:
$$\text{RATE}(\hat{\tau}, \omega) = \int_0^1 \text{TOC}(q) \cdot \omega(q) \, dq$$
通过选择不同的权重函数 $\omega(q)$,可以得到不同的评估指标:
| 权重函数 | 指标名称 | 特点 |
|:---|:---|:---|
| $\omega(q) = 1$ | AUTOC | 均匀权重,关注整体排序 |
| $\omega(q) = q$ | Qini 系数 | 更关注高优先级群体的排序 |
::: {.callout-tip}
## AUTOC vs Qini 的选择
- 当所有处理分配决策同等重要时(例如全民政策),使用 AUTOC
- 当预算严重受限、只能处理一小部分人时,使用 Qini 系数(它对 top 排序更敏感)
:::
## Qini 曲线 {#sec-qini}
### 定义
按 $\hat{\tau}(x)$ 从高到低排序,绘制累积增益:
$$\text{Qini}(q) = q \cdot [\text{TOC}(q) - \text{ATE}]$$
Qini 曲线衡量的是:相比于随机分配同样比例的人群,按模型排序能额外获得多少增益。
### Qini 系数解读
| Qini 系数 | 含义 |
|:---|:---|
| Qini > 0 | 模型排序优于随机分配 |
| Qini $\approx$ 0 | 模型的排序与随机无异 |
| Qini 越大 | 排序效果越好 |
Qini 曲线和 Qini 系数在营销领域(uplift modeling)有着广泛的应用历史,Radcliffe & Surry (2011) 是经典参考文献。
## Python 实现:TOC 与 Qini 曲线 {#sec-toc-python}
```{python}
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}")
```
### TOC 曲线解读
- **蓝色实线**(估计 TOC):基于因果森林排序的实验差分估计
- **红色虚线**(真实 TOC):基于已知的 $\tau(x)$ 计算的真实 TOC
- **灰色虚线**:ATE 基准线
- 曲线从高处平滑下降表明模型排序质量良好:优先选中的人确实具有更高的效应
### Qini 曲线解读
- 正值区域(蓝色填充)表示模型优于随机分配
- 面积越大,排序质量越高
- 如果曲线出现负值区域,说明模型在某个比例段的排序甚至不如随机
## 评估指标对比 {#sec-metrics-compare}
| 指标 | 衡量内容 | 适用场景 | 是否需要样本分割 |
|:---|:---|:---|:---|
| BLP $\alpha_1$ | 异质性是否真实存在 | 初步验证,单模型 | 是 |
| GATES | 各组效应的梯度 | 可视化异质性强度 | 是 |
| AUTOC | 排序整体优劣 | 模型间比较 | 是 |
| Qini 系数 | 高优先级排序质量 | 预算受限的场景 | 是 |
::: {.callout-warning}
## 所有指标都需要样本分割
在训练集上估计 CATE,在独立的测试集上评估。否则过拟合会让任何模型看起来都很好。这一原则贯穿本讲的所有方法。
:::
# 第四部分:变量重要性分析 {#sec-varimp}
## 识别异质性的驱动因素 {#sec-what-drives}
在确认异质性存在并评估了排序质量之后,一个自然的问题是:*哪些特征驱动了处理效应的差异?*
因果森林的变量重要性(variable importance)提供了一个系统化的答案。它衡量每个特征对 CATE 变异的贡献程度。
### 两种计算方法
**方法一:分裂频率法**
统计因果森林中每个变量被选为分裂变量的频率。频率越高的变量对 CATE 的异质性贡献越大。在 `grf` 包中,`variable_importance()` 函数直接返回这个指标。
**方法二:代理模型法**
训练一个梯度提升模型来学习 $\hat{\tau}(x)$,然后提取该模型的特征重要性。这种方法不依赖因果森林内部的分裂结构,可以作为交叉验证的手段。
两种方法衡量的都是同一件事:每个特征对 *预测异质性的贡献*。
## Python 实现:变量重要性 {#sec-varimp-python}
```{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}")
```
### 结果解读
在我们的模拟中,真实的 CATE 为 $\tau(x) = 2 + x_0 + 2x_1$,其中 $x_0$(年龄)和 $x_1$(性别)是唯一的驱动因素。变量重要性分析应当将这两个变量排在前两位,而噪声变量(收入、噪声 1、噪声 2)的重要性接近零。
## 变量重要性 $\neq$ 因果关系 {#sec-varimp-caution}
::: {.callout-warning}
## 重要区分
变量重要性回答的是"哪些特征 *预测* 了异质性",而非"哪些特征 *导致* 了异质性"。两者有本质区别。
:::
**为什么不是因果关系?**
- 年龄的重要性高 $\neq$ 年龄导致了处理效应的差异
- 可能是年龄与某个未观测的真正调节变量(例如学习动机)高度相关
- 如果年龄只是代理变量(proxy),基于年龄进行干预分配可能无效
**最佳实践**
1. 将变量重要性作为 *假设生成* 的起点,而非结论
2. 结合 *领域知识* 对结果进行验证和解释
3. 如果需要因果性的调节效应分析,应使用更严格的框架(例如 causal mediation analysis)
## R 实现参考 {#sec-varimp-r}
在 R 的 `grf` 包中,变量重要性的获取非常直接:
```r
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()` 直接返回标准化的分裂频率,无需额外步骤。
# 第五部分:政策树与政策学习 {#sec-policy}
## 从 CATE 到最优决策 {#sec-cate-to-policy}
到目前为止,我们已经完成了 CATE 的估计、检验和评估。最后一步也是最重要的一步:*将 CATE 估计转化为可执行的政策决策*。
### 政策制定的核心问题
回到就业培训的例子:你已经估计了每个人的 $\hat{\tau}(x)$,通过 BLP 检验确认异质性真实存在,Qini 曲线显示模型排序质量良好。现在的问题是:
::: {.callout-note}
## 预算约束下的分配问题
培训预算只够覆盖 50% 的申请者。如何设计分配规则 $\pi(x) \in \{0, 1\}$,使得社会福利最大化?
:::
### 朴素方案 vs 政策学习
**朴素方案**:按 $\hat{\tau}(x)$ 从高到低排序,选取 top-50%。
- 优点:简单、直接利用 CATE 估计
- 缺点:规则不透明,无法向决策者解释"为什么选这些人"
- 问题:$\hat{\tau}(x)$ 的绝对值可能有偏差,排序可能不够稳健
**政策学习方案**:学习一棵简单的决策树,给出可解释的分配规则。
- 优点:规则透明、可审计、便于向非技术决策者解释
- 输出形式:例如"年龄 $\leq$ 30 且未就业 → 分配培训"
## 政策学习的形式化 {#sec-policy-formal}
### 最优分配问题
寻找政策规则 $\pi: \mathcal{X} \to \{0, 1\}$,最大化社会福利:
$$\pi^* = \arg\max_{\pi \in \Pi} E[\tau(X) \cdot \pi(X)]$$
如果有预算约束 $E[\pi(X)] \leq B$,则成为约束优化问题。
### 估计社会福利
由于 $\tau(X)$ 不可直接观测,Athey & Wager (2021) 提出使用 AIPW (Augmented Inverse Propensity Weighting) 得分作为 $\tau(X)$ 的替代:
$$\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)}$$
::: {.callout-note}
## AIPW 得分的优越性
- $\hat{\Gamma}_i$ 是 $\tau(X_i)$ 的无偏个体级估计
- 对 $\hat{\Gamma}_i$ 求加权平均直接给出社会福利的无偏估计
- **双重稳健性**:即使倾向得分模型 $\hat{e}(x)$ 或结果模型 $\hat{\mu}_d(x)$ 之一有偏差,AIPW 得分仍然一致
:::
这样,最优政策问题变为:
$$\pi^* = \arg\max_{\pi \in \Pi} \frac{1}{n} \sum_{i=1}^{n} \hat{\Gamma}_i \cdot \pi(X_i)$$
## 最优政策树 {#sec-policy-tree}
### 为什么用树
在政策空间 $\Pi$ 的选择上,Athey & Wager (2021) 推荐使用 *深度有限的决策树*:
$$\Pi = \{\text{深度} \leq d \text{ 的决策树}\}$$
限制树的深度确保规则可解释:
| 深度 | 最大叶节点数 | 最大条件数 | 可解释性 |
|:---|:---|:---|:---|
| 1 | 2 | 1 | 极高:一个二分规则 |
| 2 | 4 | 3 | 高:两层简单条件 |
| 3 | 8 | 7 | 中等:三层条件组合 |
### 实现步骤
1. 计算 AIPW 得分 $\hat{\Gamma}_i$(或近似使用 $\hat{\tau}(X_i)$)
2. 搜索使 $\sum \hat{\Gamma}_i \cdot \pi(X_i)$ 最大的深度-$d$ 树
3. 叶节点中 $\bar{\Gamma} > 0$ → 分配处理;$\bar{\Gamma} \leq 0$ → 不分配
::: {.callout-tip}
## 简化实现
在实际中,可以用 $\hat{\tau}(X_i)$ 代替 AIPW 得分作为拟合政策树的目标。虽然理论上不如 AIPW 得分稳健,但在随机实验的设置下(已知 $e(x)$)差别通常较小。
:::
## Python 实现:政策树 {#sec-policy-python}
```{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()
```
### 解读政策树
- **每个叶节点**显示该组的 CATE 预测均值和样本量
- **叶节点值 > 0** → 建议干预(处理效应为正)
- **叶节点值 $\leq$ 0** → 不建议干预
- 分裂条件给出了可解释的分配规则
在我们的模拟中,由于 $\tau(x) = 2 + x_0 + 2x_1$,理想的政策树应当主要基于性别($x_1$)和年龄($x_0$)进行分裂。
## Python 实现:分配策略比较 {#sec-allocation-python}
下面比较三种不同分配策略在预算约束下的表现:
```{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% 人群,预期平均效应等于 ATE
- **因果森林 Top-50%**:选取 $\hat{\tau}(x)$ 最高的 50% 人群
- **神谕策略**:如果我们知道真实 $\tau(x)$,选取最高的 50%(理论上限)
因果森林策略的效率比例(相对于理论最优的提升百分比)衡量了实际可实现的收益。
## 政策学习的实践要点 {#sec-policy-practice}
::: {.callout-warning}
## 三条核心准则
**1. 交叉验证福利**
不要在训练政策的数据上评估福利。应当使用独立的验证数据评估政策的期望效果。
**2. 约束要明确**
预算上限、公平性约束、伦理限制都应在政策学习中显式编码。
**3. 简单优先**
优先选择可解释的规则(浅树、线性规则)。复杂的深度决策树虽然可能在训练数据上表现更好,但泛化性和可审计性差。
:::
### 伦理考量
政策学习的应用需要注意以下伦理问题:
- **公平性**:按种族、性别等敏感属性差异化分配可能引发公平性和法律问题
- **历史偏见**:$\hat{\tau}(x)$ 可能反映历史数据中的偏见而非真实因果效应
- **人类监督**:政策学习 $\neq$ 自动决策。算法的建议应当与领域专家的判断结合
- **透明度**:政策规则需要向受影响群体公开,接受公众审查
## R 实现参考:policytree {#sec-r-policytree}
R 的 `policytree` 包(由 Athey 团队开发)提供了最优政策树的原始实现:
```r
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)
```
::: {.callout-tip}
## Python vs R 工具推荐
- **Python**:`econml.dml.CausalForestDML` + `sklearn.tree.DecisionTreeRegressor`(简易政策树)
- **R**:`grf::causal_forest` + `policytree::policy_tree`(推荐,理论最优实现)
- `policytree` 实现了精确的最优政策树搜索,而 sklearn 的 `DecisionTreeRegressor` 使用贪心分裂,可能不是全局最优
:::
# 第六部分:完整工作流总结 {#sec-workflow}
## 从 CATE 到决策的完整管道 {#sec-full-pipeline}
本讲介绍的方法构成了一个完整的分析管道:
```
┌──────────────┐
│ 估计 CATE │
│ (上节课 GRF)│
└──────┬───────┘
│
┌──────▼───────┐
Step 1 │ BLP 检验 │
(是否真实?) │ + GATES │
└──────┬───────┘
│ 如果 α₁ 显著
┌──────▼───────┐
Step 2 │ TOC / Qini │
(排序多好?) │ AUTOC 比较 │
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌──────▼──────┐
│ 变量重要性 │ │ 政策树 │ │ 策略比较 │
│ (哪些特征)│ │ (分配规则)│ │ (福利评估)│
└─────────────┘ └────────────┘ └─────────────┘
```
### 关键原则
1. **始终先检验**:BLP/GATES 是后续所有分析的前提
2. **始终样本分割**:训练 ↔ 评估/决策使用不同数据
3. **排序优先于校准**:绝对值不重要,排序才重要
4. **可解释性约束**:政策树深度 $\leq$ 2-3
## 推荐工具 {#sec-tools}
### Python 生态
| 包 | 功能 |
|:---|:---|
| `econml` | CausalForestDML(CATE 估计 + 推断) |
| `causalml` | UpliftTreeClassifier、plot_qini |
| `sklearn` | DecisionTreeRegressor(简易政策树) |
### R 生态
| 包 | 功能 |
|:---|:---|
| `grf` | causal_forest、best_linear_projection、rank_average_treatment_effect |
| `policytree` | optimal_policy_tree(推荐,原始实现) |
| `uplift` | Qini 曲线绘制 |
# 第七部分:练习与思考 {#sec-exercises}
### 练习 1:BLP 检验的直觉
假设在一个完全同质的实验中(所有人的处理效应都是 $\tau = 3$),你仍然使用因果森林估计了 $\hat{\tau}(x)$(此时 $\hat{\tau}(x)$ 的变化纯粹来自噪声)。
1. 在 BLP 检验中,$\alpha_0$ 应该等于多少?
2. $\alpha_1$ 的期望值是多少?通过样本分割获得的 $\hat{\alpha}_1$ 是否显著?
3. 如果不做样本分割,直接在训练数据上跑 BLP 回归,$\alpha_1$ 会怎样?
### 练习 2:Qini 曲线的构建
给定以下数据(10 个个体,随机实验),手动绘制 Qini 曲线:
| 个体 | $X_1$ | $T$ | $Y$ | $\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) 按 $\hat{\tau}(x)$ 排序;(b) 计算 ATE;(c) 对 $q = 20\%, 40\%, \ldots, 100\%$,计算 $\text{TOC}(q)$ 和 $\text{Qini}(q) = q \cdot [\text{TOC}(q) - \text{ATE}]$
### 练习 3:政策树设计
你是一家在线教育平台的数据科学家,公司想为用户提供个性化的学习辅导(有预算限制)。已知的特征包括:学习时长、测验成绩、课程完成率、登录频率。
1. 你会如何设计 CATE 估计 → 政策制定的完整管道?
2. 如果政策树的深度限制为 2,最多能产生几种分配策略?
3. 在此场景下,"公平性"约束应如何体现?
### 练习 4:方法选择
对于以下场景,选择最合适的评估工具并解释原因:
1. 一个慈善组织想确认其 microcredit 项目是否对不同收入群体有差异化影响
2. 两个研究团队分别用因果森林和 X-Learner 估计了 CATE,需要比较哪个更好
3. 一家制药公司想基于基因特征为患者制定个性化用药方案
# 参考文献 {#sec-references}
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.
# 下节课预告 {#sec-preview .unnumbered}
**第十一讲:双重机器学习——高维控制与去偏估计**
- 朴素 ML 插件估计的偏差问题
- Neyman 正交性:为什么需要"双重"修正
- DML 框架与部分线性模型
- 高维控制变量的因果效应估计
核心思想:当控制变量数量很多时(甚至超过样本量),传统方法失效。*双重机器学习* 通过正交化技巧,让我们在使用 ML 控制混杂的同时得到有效的因果推断。