Zenn
🐍

PythonによるX-Learnerの実装

に公開

はじめに

X-Learnerについて、Pythonによる実装を交えてまとめました。内容について誤り等ございましたら、コメントにてご指摘いただけますと幸いです。

機械学習を用いた因果推論

機械学習を用いた因果推論手法は大きく分けて下記の2通りが存在します。

  • Meta-Learner系
  • Causal-Tree系

今回はMeta-Learner系の手法の1つであるX-Learnerについて紹介します。

Meta-Leanrerとは

Meta-Learnerとは、機械学習と因果推論の考え方を掛け合わせて条件付き平均処置効果(CATE: Conditional Average Treatment Effect)を推定する手法の総称です。

条件付き平均処置効果(CATE)とは、平均処置効果(ATE: Average Treatment Effect)をある条件(X=xX=x)に限定して算出したものであり、処置を実施した場合の結果をY(1)Y(1)、処置を実施しなかった場合を結果Y(0)Y(0)とすると

τ^(x)=E[Y(1)Y(0)X=x]=E[Y(1)X=x]E[Y(0)X=x]\hat{\tau}(x) = E[Y(1)-Y(0)|X=x] = E[Y(1)|X=x] - E[Y(0)|X=x]
で与えられます。X=xについて条件付けした場合の効果を推定するため、個体の属性における効果の異質性、すなわちxによって効果が異なるような非線形な効果も捉えることができます。

ここで、X=xX=xの条件である処置を実施した場合、Y(1)X=xY(1)|X=x \:のデータは取れますが、Y(0)X=xY(0)|X=x \:のデータは取れません。逆にX=xX=xの条件である処置を実施しなかった場合、Y(0)X=xY(0)|X=x \:のデータは取れますが、Y(1)X=xY(1)|X=x \:のデータは取れません。

そのため、機械学習を使って取得できないデータの予測値を算出し、推定に利用しようとするのがMeta-Learnerの考え方です。その中でも今回はX-Learnerについて紹介します。

X-Learnerとは

X-Learnerは、T-Learnerの結果を傾向スコアを用いて補正し、条件付き平均処置効果(CATE)を推定しようとする手法です。

ダイエット商品の広告を回した際に

  • iさんの共変量: XiX_i
  • iさんが広告を見たかどうか(処置): TiT_i(Ti=1T_i=1であれば広告を見た、Ti=0T_i=0であれば広告を見てない)
  • iさんの売上: YiY_i

というデータから広告の効果を推定するという例をもとにX-Learnerの手順を説明します。

  1. 処置を受けていない、すなわち、広告を見ていない(T=0T=0)グループの共変量X=xX=xを用いて、処置を受けていない(広告を見ていない)場合の売上(Y(0)Y(0))を予測する回帰モデルμ0(x)\mu_0(x)を作成します。
    μ^0(x)=M1(Y0X0)\hat{\mu}_0(x) = M_1(Y^0 \sim X^0)
  2. 処置を受けた、すなわち、広告を見た(T=1T=1)グループの共変量X=xX=xを用いて、処置を受けた(広告を見た)場合の売上(Y(1)Y(1))を予測する回帰モデルμ1(x)\mu_1(x)を作成します。
    μ^1(x)=M2(Y1X1)\hat{\mu}_1(x) = M_2(Y^1 \sim X^1)
  3. 処置を受けていない(T=0T=0)グループのデータと処置を受けた場合の売上を予測する回帰モデルμ1(x)\mu_1(x)を用いて処置効果D0D^0(ATT)を推定し、推定された処置効果(ATT)を目的変数として回帰モデルτ0(x)\tau_0(x)を作成します。
    D^0=μ^1(X0)Y0,τ^0=M3(D^0X0)\hat{D}^0 = \hat{\mu}_1(X^0) - Y^0, \: \hat{\tau}_0 = M_3(\hat{D}^0 \sim X^0)
  4. 処置を受けた(T=0T=0)グループのデータと処置を受けていない場合の売上を予測する回帰モデルμ0(x)\mu_0(x)を用いて処置効果D1D^1(ATU)を推定し、推定された処置効果(ATU)を目的変数として回帰モデルμ3(x)\mu_3(x)を作成します。
    D^1=Y0μ^0(X1),τ^1=M4(D^1X1)\hat{D}^1 = Y^0 - \hat{\mu}_0(X^1), \: \hat{\tau}_1 = M_4(\hat{D}^1 \sim X^1)
  5. 全データから傾向スコアを算出します。ここで傾向スコアを予測するモデルをg(x)g(x)とします。
  6. 全データをモデルτ0(x),τ1(x)\tau_0(x), \: \tau_1(x)で予測した効果を傾向スコアで調整し、効果τX\tau_Xを推定します。
    τ^=g(x)τ^0(x)+(1g(x))τ^1(x)\hat{\tau} = g(x) \hat{\tau}_0(x) + (1-g(x)) \hat{\tau}_1(x)

Pythonによる実装

ダイエット商品の広告効果を例に、Pythonでデータを作成し、X-Learnerを用いて効果を算出してみます。

設定

とある健康食品会社では、ECにてダイエット商品を販売しています。ダイエット商品の販促のため広告を回した結果から広告効果を推定することになりました。

手元には下記のデータがあります。

  • iさんのダイエットへの意識の高さを表す指標xix_i
    • 一様分布(-1, 1)に従う
  • iさんが広告を見たかどうかを表すダミー変数TiT_i
    • 下記のモデルによって決定されるとする
      Ti={1(xi+noisei>0):広告を見た0(xi+noisei0):広告を見てないT_i = \left\{\begin{array}{ll} 1 & (x_i + noise_i > 0): 広告を見た \\ \\ 0 & (x_i + noise_i \leq 0): 広告を見てない \end{array}\right.
    • noiseinoise_iは標準正規分布に従う
  • iさんの購入金額YiY_i
    • xix_iTiT_iの影響を受け、真のモデルは下記のように表される
      Yi=1000([3+τiTi+3xi+noisei])Y_i = 1000(\: [\: 3 + \tau_i T_i + 3x_i + noise_i \:] \: )
    • ただし[x][x]は、nx<n+1n \leq x < n+1を満たす整数nを表す
    • また、効果τi\tau_ixix_iによって異なる
      τi={1(xi0)2(0<xi0.5)3(0.5<xi1)\tau_i = \left\{\begin{array}{ll} 1 & (x_i \leq 0) \\ \\ 2 & (0 < x_i \leq 0.5) \\ \\ 3 & (0.5 < x_i \leq 1) \end{array}\right.
    • noiseinoise_iは一様分布(0, 1)に従う

データの作成

Pythonで上記の設定を満たすデータを作成します。

# 必要なライブラリをインポート
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
# グラフをJupyter上に描画
%matplotlib inline

# データ数
size = 1000
# シードの設定
np.random.seed(0)

# ダイエットへの意識の高さ
x = np.random.uniform(-1, 1, size)
noise = np.random.randn(size)

# 広告ダミー
_T = x + noise
T = np.where(_T>0, 1, 0)

# ダイエットへの意識の高さによって広告効果が異なる
t = np.zeros(size)
for i in range(size):
    if x[i] < 0:
        t[i] = 1
    elif x[i] < 0.5:
        t[i] = 2
    else:
        t[i] = 3
        
# 売上
noise = np.random.uniform(0, 1, size)
Y = np.clip(t*T + 3*x + 3 + noise, 0, 10).astype("int") * 1000

# 売上のヒストグラムを描画
plt.hist(Y)
plt.xlabel("ダイエット商品の売上(単位:円)")
plt.show()

(出力結果)

X-Learnerによる効果検証

今回は売上YYを予測するモデルにランダムフォレストを選択して、効果を推定していきます。

# 必要なライブラリをインポート
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression

# 手順1: 広告を見ていないグループの回帰モデルを作成
df_t0 = df[df["T"] == 0]
mu0 = RandomForestRegressor(max_depth=3, random_state=0)
mu0.fit(df_t0[["x"]], df_t0["Y"])

# 手順2: 広告を見たグループの回帰モデルを作成
df_t1 = df[df["T"] == 1]
mu1 = RandomForestRegressor(max_depth=3, random_state=0)
mu1.fit(df_t1[["x"]], df_t1["Y"])

# 手順3: ATTを求める回帰モデルを作成
tau0 = mu1.predict(df_t0[["x"]]) - df_t0["Y"]
mu2 = RandomForestRegressor(max_depth=3, random_state=0)
mu2.fit(df_t0[["x"]], tau0)

# 手順4: ATUを求める回帰モデルを作成
tau1 = df_t1["Y"] - mu0.predict(df_t1[["x"]])
mu3 = RandomForestRegressor(max_depth=3, random_state=0)
mu3.fit(df_t1[["x"]], tau1)

# 手順5: 傾向スコアを算出
lr = LogisticRegression().fit(df[["x"]], T)
ps = lr.predict_proba(df[["x"]])[:, 1]

# 手順6: 予測した効果を傾向スコアで調整
tau = ps*mu2.predict(df[["x"]]) + (1 - ps)*mu3.predict(df[["x"]])

# 推定された効果の描画
plt.scatter(df[["x"]], tau, alpha=0.3)
plt.hlines(1000, -1, 0, linestyles='--', color="red")
plt.hlines(1000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(2000, 0, 0.5, linestyles='--', color="red")
plt.hlines(2000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(3000, 0.5, 1.0, linestyles='--', color="red")
plt.hlines(3000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.xlabel("ダイエットへの意識の高さ")
plt.ylabel("広告効果")
plt.ylim(0, 4000)
plt.show()

(出力結果)

EconMLの紹介

EconMLとは、観察可能なデータから機械学習を用いて条件付き平均処置効果(CATE)を推定するPythonパッケージです。Microsoft社が開発しているもので、さまざまなMeta-Learner系やCausal-Tree系の因果推論手法が実装されています。

EconMLの公式ドキュメントはこちらです。
https://econml.azurewebsites.net/

EconMLによるX-Learnerの実装

X-Learnerは推定に利用する機械学習モデルを選択するだけで実装できます。
今回はランダムフォレスト回帰をモデルに選択します。

# 必要なライブラリのインポート
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression
from econml.metalearners import XLearner

# モデルの構築
models = RandomForestRegressor(max_depth=3, random_state=0)
propensity_model = LogisticRegression()
X_learner = XLearner(models=models, propensity_model=propensity_model)
X_learner.fit(Y, T, X=x.reshape(-1, 1))

# 効果の推定
tau = X_learner.effect(x.reshape(-1, 1))

# 推定された効果の可視化
plt.scatter(df[["x"]], tau, alpha=0.3)
plt.hlines(1000, -1, 0, linestyles='--', color="red")
plt.hlines(1000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(2000, 0, 0.5, linestyles='--', color="red")
plt.hlines(2000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(3000, 0.5, 1.0, linestyles='--', color="red")
plt.hlines(3000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.xlabel("ダイエットへの意識の高さ")
plt.ylabel("広告効果")
plt.ylim(0, 4000)
plt.show()

(出力結果)

先ほどと同様の結果が得られました。

補足

先ほども少し触れましたが、現実社会においては広告効果を推定したい場合、広告を見た人もそうでない人もその多くが商品を購入しないというシーンがほとんどです。

先ほどの例で売上YYの真のモデルを

Yi=1000([τiTi+3xi+noisei])Y_i = 1000(\: [\: \tau_i T_i + 3x_i + noise_i \:] \: )
とし、Pythonで実装すると下記のようになります。

# 必要なライブラリをインポート
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
# グラフをJupyter上に描画
%matplotlib inline

# データ数
size = 1000
# シードの設定
np.random.seed(0)

# ダイエットへの意識の高さ
x = np.random.uniform(-1, 1, size)
noise = np.random.randn(size)

# 広告ダミー
_T = x + noise
T = np.where(_T>0, 1, 0)

# ダイエットへの意識の高さによって広告効果が異なる
t = np.zeros(size)
for i in range(size):
    if x[i] < 0:
        t[i] = 1
    elif x[i] < 0.5:
        t[i] = 2
    else:
        t[i] = 3
        
# 売上
noise = np.random.uniform(0, 1, size)
Y = np.clip(t*T + 3*x + noise, 0, 10).astype("int") * 1000
# Y = np.clip(t*T + 3*x + 3 + noise, 0, 10).astype("int") * 1000

# 売上のヒストグラムを描画
plt.hist(Y)
plt.xlabel("ダイエット商品の売上(単位:円)")
plt.show()

(出力結果)

このデータにおいて、X-Learnerを実装してみます。

# 必要なライブラリをインポート
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression
from econml.metalearners import XLearner

# モデルの構築
models = RandomForestRegressor(max_depth=3, random_state=0)
propensity_model = LogisticRegression()
X_learner = XLearner(models=models, propensity_model=propensity_model)
X_learner.fit(Y, T, X=x.reshape(-1, 1))

# 効果の推定
tau = X_learner.effect(x.reshape(-1, 1))

# 推定された効果の可視化
plt.scatter(df[["x"]], tau, alpha=0.3)
plt.hlines(1000, -1, 0, linestyles='--', color="red")
plt.hlines(1000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(2000, 0, 0.5, linestyles='--', color="red")
plt.hlines(2000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.hlines(3000, 0.5, 1.0, linestyles='--', color="red")
plt.hlines(3000, -1, 1, linestyles='--', color="red", alpha=0.3)
plt.xlabel("ダイエットへの意識の高さ")
plt.ylabel("広告効果")
plt.ylim(0, 4000)
plt.show()

(出力結果)

特にダイエットへの意識が低いグループの広告効果において、推定の精度がかなり悪いようです。

参考文献

おわりに

最後まで読んでいただきありがとうございました。他にも「Python×データ分析」をメインテーマに記事を執筆しているので、参考にしていただけたら幸いです。内容の誤り等がございましたら、コメントにてご指摘くださいませ。

他にも下記のような記事を書いています。ご一読いただけますと幸いです。

また、過去にLTや勉強会で発表した資料は下記リンクにまとめてあります。ぜひ、ご一読くださいませ。
https://speakerdeck.com/s1ok69oo

Discussion

ログインするとコメントできます