AIアシスタント機能をトグル式のオプトインで出荷したとき、ダッシュボードの数字が3倍近く過大になっている可能性があります。
freeCodeCampが2026年5月5日に公開したチュートリアルでは、LLM機能のオプトインデータに潜む選択バイアスと、Pythonで補正する手順を具体的に解説しています。著者はApplied AI/MLリーダーのRudrendu Paul氏で、5万件の合成SaaSデータセットを使って推定値を地面の真実値と照合できます。
この記事でわかること:
- なぜオプトインデータのナイーブな比較が過大評価になるのか
- プロペンシティスコアによるIPWとマッチングの仕組みと使い分け
- Pythonでの実装手順とバランス診断の方法
- 手法が静かに壊れる代表的なケース
オプトイン機能がA/Bテストを壊す理由
エージェントモードを新たにリリースし、ユーザーにトグルで有効化してもらったとします。ダッシュボードには「オプトインユーザーのタスク完了率が21ポイント高い」と表示されます。この数字が機能そのものの効果だと思ったら危険です。
A/Bテストではコインの裏表で処置が割り当てられるため、群間の比較は因果関係を近似します。オプトインにはコインがありません。ヘビーユーザーはほぼ何でも試し、ライトユーザーはトグルを無視します。つまり「オプトインした群」と「しなかった群」は、機能を使う前からすでに性質が違います。
チュートリアルの合成データでは、エンゲージメント層ごとのオプトイン率がヘビー65%・ミディアム35%・ライト12%に設定されています。地面の真実値として埋め込まれた因果効果は+8ポイントですが、ナイーブな比較では+21ポイントに膨れ上がります。2.6倍の過大評価です。
この「オプトイントラップ」はコード補完の「スマートモード」でも、メール返信の「AI提案」でも、同じ構造で発生します。
プロペンシティスコアが解決すること
プロペンシティスコアは「観測可能な特性を条件として、あるユーザーがオプトインする確率」です。この確率を使うと、オプトイン群と非オプトイン群の共変量バランスを揃えられます。ランダム化実験を事後的に近似するイメージです。
実際的な手法として、逆確率重み付け(IPW) と 最近傍マッチング の2種類があります。
IPWは各ユーザーに「自分のオプトイン確率の逆数」を重みとして付けます。ライトユーザーなのにオプトインした人(希少なケース)には大きな重みを与え、ヘビーユーザーがオプトインした当然のケースには小さな重みを付けます。重み付け後の群は共変量が均一になり、加重平均の差が因果効果を近似します。
最近傍マッチングは、オプトインした各ユーザーに対してプロペンシティスコアが最も近い非オプトインユーザーを対応させ、対応したペアの結果差の平均を処置効果の推定値とします。
ステップ1:プロペンシティスコアの推定
scikit-learn のロジスティック回帰でプロペンシティスコアを推定します。
from sklearn.linear_model import LogisticRegression
import pandas as pd
X = pd.get_dummies(
df[["engagement_tier", "query_confidence"]],
drop_first=True
).astype(float)
y_treat = df.opt_in_agent_mode
ps_model = LogisticRegression(max_iter=1000).fit(X, y_treat)
df["propensity"] = ps_model.predict_proba(X)[:, 1]
モデルのAUCが0.744で、各エンゲージメント層の平均プロペンシティが実際のオプトイン率をほぼ再現している場合、モデルのキャリブレーションは取れています。処置群と対照群のプロペンシティ範囲が重なっていること(どちらも約0.11〜0.67)は、マッチングの前提条件(positivity)が成立していることを示します。
ステップ2:逆確率重み付け(IPW)
import numpy as np
df["ipw"] = np.where(
df.opt_in_agent_mode == 1,
1 / df.propensity,
1 / (1 - df.propensity)
)
t = df[df.opt_in_agent_mode == 1]
c = df[df.opt_in_agent_mode == 0]
ate_ipw = (
(t.task_completed * t.ipw).sum() / t.ipw.sum()
- (c.task_completed * c.ipw).sum() / c.ipw.sum()
)
ATE(全体への平均処置効果)は+0.0851、ATT(オプトインユーザーへの平均処置効果)は+0.0770でした。どちらも地面の真実値+0.08に近く、ナイーブな+0.21から大幅に補正されています。
ATEは「まだオプトインしていないユーザー全体へ機能を展開するか」という意思決定に使います。ATTは「すでにオプトインしたユーザーが実際に得た恩恵」を報告するときに使います。
ステップ3:最近傍マッチング
from sklearn.neighbors import NearestNeighbors
treated_ps = df[df.opt_in_agent_mode == 1][["propensity"]].values
control_ps = df[df.opt_in_agent_mode == 0][["propensity"]].values
nn = NearestNeighbors(n_neighbors=1).fit(control_ps)
_, idx = nn.kneighbors(treated_ps)
att_match = (
df[df.opt_in_agent_mode == 1].task_completed.values
- df[df.opt_in_agent_mode == 0].task_completed.values[idx.flatten()]
).mean()
# → +0.0752
1-NNマッチングのATTは+0.0752で、IPWのATTより若干低くなります。単一の最近傍を使うため分散が高くなりやすく、プロダクション分析では k=3〜5 の置換ありマッチングが標準的な選択です。
ステップ4:バランス診断(SMD)
プロペンシティスコア法は「共変量のバランスが取れた」ことを必ず確認してから使います。標準化平均差(SMD)がその診断指標です。
engagement_tier_heavy のSMDは、重み付け前が+0.742(ヘビーユーザーが処置群に極端に多い状態)ですが、IPW重み付け後は+0.002まで低下します。|SMD| < 0.1 が通過基準で、超えている場合は交互作用項の追加や勾配ブースティングへの切り替えが必要です。
この診断を省くと、見た目は正常でも実は偏った推定値が出ている状況を見逃します。
ステップ5:ブートストラップ信頼区間
IPWと最近傍マッチングの分析標準誤差は理論的に複雑なため、ノンパラメトリックブートストラップが最も実用的です。500回のリサンプリングで95%信頼区間を求めると、次の結果が得られます。
- IPW ATE:[+0.0745, +0.0954]
- IPW ATT:[+0.0687, +0.0865]
- 1-NN ATT:[+0.0659, +0.0940]
3つすべてが地面の真実値+0.08を内包し、ナイーブな+0.21からは大きく外れています。ノートPC1台で約90秒で完了します。ステークホルダーへの報告では点推定とともに区間を提示することで、推定の不確実性が可視化されます。
手法が静かに失敗するケース
プロペンシティスコア法は仮定が成立するときのみ正確に機能します。仮定が崩れても推定値は「それらしい数字」を返し続けるため、失敗に気づきにくいという特徴があります。
未観測交絡変数 があると、モデルが選択バイアスを完全に除去できません。たとえば「エンジニアのブログを読む習慣」がオプトイン確率とタスク完了率の両方に影響していても、その情報がデータになければモデルには見えません。Rosenbaum boundsなどの感度分析ツールで「どれほど強い未観測交絡があれば結論が覆るか」を定量化することが実践的な防衛策です。
オーバーラップの欠如 は、特定のユーザー層でオプトイン確率が0か1に近い状況で起きます。1/0.001=1,000という極端なIPW重みが1件の外れ値に推定値を引き寄せるため、プロペンシティのヒストグラムを確認し、[0.05, 0.95]の範囲外をトリミングします。
プロペンシティモデルの誤特定 は、エンゲージメント層とクエリ信頼度の交互作用をロジスティック回帰の主効果モデルが捉えられない場合に起きます。バランス診断でSMDが0.1を超えた場合、勾配ブースティングへの切り替えがシグナルです。
ユーザー間の波及効果(SUTVA違反) は、チームメンバーが機能を一緒に使うような共有ワークスペースで発生します。この場合、プロペンシティスコア法は適用外で、クラスター化ランダム化やネットワーク対応の実験設計が必要になります。
次のステップ
回帰不連続設計(オプトインが閾値で決まる場合)や二重ロバスト推定量(プロペンシティモデルとアウトカムモデルを組み合わせる手法)は、同シリーズの別ノートブックで扱われています。
コンパニオンリポジトリはGitHubで公開されており、data/generate_data.py でデータを生成後、psm_demo.ipynb を実行すると本記事のすべての数値を再現できます。
LLM機能をトグルで出荷した時点で、ナイーブな比較はほぼ常に過大推定になります。プロペンシティスコアは「同等の特性を持つ非オプトインユーザー」を反事実として構築する手段で、ブートストラップがその推定の不確実性を定量化します。
