Mímisbrunnr知恵の泉

← 因果推論 一覧

🎓 レベル:発展 | 重要度:C(発展的)

📎 前提:異質処置効果とメタ学習器(S/T/X-learner) | 識別の仮定 | 数理:バギングとランダムフォレスト(機械学習)

要点(BLUF)


因果フォレストの考え方

異質処置効果とメタ学習器(S/T/X-learner)の T-learner は「予測のための木」を 2 つ作って引き算した。これは予測誤差を小さくするよう分岐するので、効果の異質性とはズレた所で分かれてしまう。因果フォレストは最初から「効果 τ(x)\tau(x) の違い」を狙って分岐する点が違う(Wager & Athey, 2018/一般化ランダムフォレスト grf)。

識別の仮定

例によって因果フォレストは推定の道具。識別の仮定の条件付き交換可能性・正値性・SUTVA が満たされて初めて、葉内の処置群・対照群の差が τ(x)\tau(x) になる。未観測交絡があれば森を組んでも因果は出ない。

honest splitting(正直な分割)

flowchart LR
    D["訓練データ"] --> S["標本A:木の分岐を決める(どこで分けるか)"]
    D --> E["標本B:葉の中で効果を推定する"]
    S --> TREE["honest tree"]
    E --> TREE
    TREE --> F["森:適応的な近傍重み αᵢ(x)"]
    F --> TAU["τ̂(x) + 信頼区間"]

同じデータで「どこで分けるか」と「葉の効果」を両方決めると、たまたま効果が大きく出た方向に分岐してしまい、効果を過大評価する(選択バイアス)。honesty は両者を別標本に分けることでこれを断ち、葉内推定を(分岐構造を所与として)不偏にする。これが漸近正規性と妥当な信頼区間の根拠になる。

局所モーメントによる τ(x)

森は各訓練点 ii が点 xx と同じ葉に落ちる頻度を適応的な重み αi(x)\alpha_i(x) とみなし、局所的な残差回帰で τ(x)\tau(x) を解く(交絡はDouble/Debiased Machine Learning(DML)と同じ残差化で除く)。

τ^(x)=iαi(x)(TiTˉx)(YiYˉx)iαi(x)(TiTˉx)2\hat\tau(x) = \frac{\sum_i \alpha_i(x)\,(T_i-\bar T_x)\,(Y_i-\bar Y_x)}{\sum_i \alpha_i(x)\,(T_i-\bar T_x)^2}

ここで Tˉx,Yˉx\bar T_x,\bar Y_x は重み αi(x)\alpha_i(x) 付きの局所平均。木が近傍 αi(x)\alpha_i(x) をデータ駆動で決めるので、効果が変わる境界(ここでは x0=0x_0=0)を自動で見つける

コード:RandomForest による近似実装(必ず動く)

まず econml 不要の近似から。処置群・対照群に RandomForest を当てて引く T-learner で、段差をどこまで捉えられるか見る(正式な honest tree ではない点に注意)。

# === X0>0 で効果が段差状に増える擬似データを作り、RandomForest の T-learner で近似 ===
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from sklearn.ensemble import RandomForestRegressor

rng = np.random.default_rng(11)
n, p = 2000, 5
X = rng.standard_normal((n, p))
e = 1.0 / (1.0 + np.exp(-(0.7 * X[:, 0] + 0.7 * X[:, 1])))     # 傾向(交絡)
T = rng.binomial(1, e)
mu0 = 2 * X[:, 1] + X[:, 2]
tau_fn = lambda Z: 1.0 + 2.0 * (Z[:, 0] > 0)                   # 真のCATE: X0>0 で段差 +2
Y = mu0 + tau_fn(X) * T + rng.standard_normal(n)

forest_args = dict(n_estimators=300, min_samples_leaf=20, random_state=0)
rf1 = RandomForestRegressor(**forest_args).fit(X[T == 1], Y[T == 1])
rf0 = RandomForestRegressor(**forest_args).fit(X[T == 0], Y[T == 0])

rng_te = np.random.default_rng(222)
Xte = rng_te.standard_normal((4000, p))
tau_true = tau_fn(Xte)
tau_rf = rf1.predict(Xte) - rf0.predict(Xte)
print("RF T-learner RMSE :", round(np.sqrt(np.mean((tau_rf - tau_true) ** 2)), 3))
print("  X0<0 平均効果 :", round(tau_rf[Xte[:, 0] < 0].mean(), 3),
      " / X0>0 平均効果 :", round(tau_rf[Xte[:, 0] > 0].mean(), 3), " (真値 1.0 と 3.0)")

grid = np.linspace(-2.5, 2.5, 100)
G = np.zeros((100, p)); G[:, 0] = grid
plt.figure(figsize=(8, 5))
plt.plot(grid, 1.0 + 2.0 * (grid > 0), "k--", lw=2, label="真の τ(x)")
plt.plot(grid, rf1.predict(G) - rf0.predict(G), label="RF T-learner 近似")
plt.xlabel("効果修飾子 X0"); plt.ylabel("推定処置効果 τ(x)")
plt.title("RandomForest T-learner による異質効果の近似")
plt.legend(); plt.tight_layout(); plt.show()

出力は次のとおり。

RF T-learner RMSE : 0.741
  X0<0 平均効果 : 1.302  / X0>0 平均効果 : 2.912  (真値 1.0 と 3.0)

RF 近似は段差の方向は捉えるが、x0=0x_0=0 付近でなまって、低い側を 1.30(真 1.0)と過大、高い側を 2.91(真 3.0)と過小に見積もる。各群を予測誤差最小で当てているため、境界がぼやけ、信頼区間も付かない。

コード:econml の CausalForestDML(要最新確認)

正式な honest splitting の因果フォレストは econml(v0.16.0 で確認)の CausalForestDMLAPI は要最新確認(未導入なら pip install econml)。X に効果修飾子、discrete_treatment=True で二値処置。

# === 同じデータに honest splitting の因果フォレストを当て、段差とCIを回収 ===
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from sklearn.ensemble import RandomForestRegressor
from econml.dml import CausalForestDML

rng = np.random.default_rng(11)
n, p = 2000, 5
X = rng.standard_normal((n, p))
e = 1.0 / (1.0 + np.exp(-(0.7 * X[:, 0] + 0.7 * X[:, 1])))
T = rng.binomial(1, e)
mu0 = 2 * X[:, 1] + X[:, 2]
Y = mu0 + (1.0 + 2.0 * (X[:, 0] > 0)) * T + rng.standard_normal(n)

cf = CausalForestDML(discrete_treatment=True, n_estimators=500, random_state=0)
cf.fit(Y, T, X=X, W=None)

rng_te = np.random.default_rng(222)
Xte = rng_te.standard_normal((4000, p))
tau_true = 1.0 + 2.0 * (Xte[:, 0] > 0)
tau_cf = cf.effect(Xte)
print("CausalForestDML RMSE :", round(np.sqrt(np.mean((tau_cf - tau_true) ** 2)), 3))
print("  X0<0 平均効果 :", round(tau_cf[Xte[:, 0] < 0].mean(), 3),
      " / X0>0 平均効果 :", round(tau_cf[Xte[:, 0] > 0].mean(), 3), " (真値 1.0 と 3.0)")

grid = np.linspace(-2.5, 2.5, 100)
G = np.zeros((100, p)); G[:, 0] = grid
tau_grid = cf.effect(G)
lb, ub = cf.effect_interval(G, alpha=0.05)
rf1 = RandomForestRegressor(n_estimators=300, min_samples_leaf=20, random_state=0).fit(X[T == 1], Y[T == 1])
rf0 = RandomForestRegressor(n_estimators=300, min_samples_leaf=20, random_state=0).fit(X[T == 0], Y[T == 0])

plt.figure(figsize=(8, 5))
plt.plot(grid, 1.0 + 2.0 * (grid > 0), "k--", lw=2, label="真の τ(x)")
plt.plot(grid, rf1.predict(G) - rf0.predict(G), color="tab:orange", label="RF T-learner 近似")
plt.plot(grid, tau_grid, color="tab:green", label="CausalForestDML")
plt.fill_between(grid, lb, ub, color="tab:green", alpha=0.2, label="95%信頼区間")
plt.xlabel("効果修飾子 X0"); plt.ylabel("推定処置効果 τ(x)")
plt.title("因果フォレスト(honest splitting)による異質効果とCI")
plt.legend(); plt.tight_layout(); plt.show()

出力は次のとおり。

CausalForestDML RMSE : 0.191
  X0<0 平均効果 : 1.006  / X0>0 平均効果 : 3.019  (真値 1.0 と 3.0)

RMSE 0.19 と RF 近似(0.74)の約 4 分の 1。 段差を 1.01/3.02 とほぼ正確に回収し、しかも各点の信頼区間(図の緑の帯)まで付く。honest splitting とDouble/Debiased Machine Learning(DML)の直交化により、境界が鋭く、推論も妥当になる。図では緑の階段が真の破線に重なり、橙の RF 近似だけが境界でなまる。


直観:いつ因果フォレストか


⚠️ よくある誤解・落とし穴


関連ノート