Digital Reactor
機械学習

「どうすれば審査に通ったのか」に答える:DiCEで反実仮想サンプルを作る

「どうすれば審査に通ったのか」に答える:DiCEで反実仮想サンプルを作る

はじめに

ローン審査モデルが「否決」を返したとき、審査担当者や申込者が知りたいのは「なぜ否決なのか」だけではありません。年収があと50万円高ければ通ったのか、勤続年数が足りなかったのか。「何がどうなっていれば承認されたのか」が分かると、次に取る行動が決まります。この「もし特徴量がこうだったら、予測はどう変わっていたか」を示すデータを反実仮想サンプル(Counterfactual Examples)と呼びます。DiCE(Diverse Counterfactual Explanations)というライブラリを使うと、現実に起こりうる範囲に収まった多様な反実仮想サンプルを、最適化計算で生成できます。

対象読者:

  • 機械学習モデルの解釈可能性に関心のあるデータサイエンティスト、機械学習エンジニア
  • モデルの予測結果をビジネス上の意思決定に活かしたいビジネスアナリスト、プロダクトマネージャー
  • PythonでDiCEを動かしてみたい方

記事のポイント:

  • 反実仮想サンプルの主な用途4つ(意思決定支援、公平性検証、説明可能性向上、頑健性評価)と、用途ごとの進め方
  • DiCEが反実仮想サンプルを生成する仕組みと、主要パラメータ(proximity_weightdiversity_weightfeatures_to_vary)の役割
  • 実務で使える反実仮想サンプルが満たすべき4つの条件(実現可能性、最小変更、解釈可能性、多様性)
  • PythonとDiCEを使ったコード例

なぜランダムな特徴量の変更ではなくDiCEを使うのか

反実仮想サンプルを作るだけなら、特徴量の値をランダムに振って予測が変わる点を探す方法もあります。しかしランダムな変更では、望ましい結果にたどり着く保証がありません。たどり着いたとしても、「年齢を20歳下げる」のような実行できない提案になりがちです。

DiCEは、目標とする予測結果(例えばローン審査の承認)を得るための特徴量の変更を、最適化問題として探索します。proximity_weightで元のデータポイントに近い、つまり現実に起こりうる範囲の変更へ絞り込み、diversity_weightで互いに異なる複数の候補を提示します。変更する特徴量の数も最小限に抑えようとするため、何を変えればよいかを把握しやすい提案になります。さらにfeatures_to_varyで変更を許す特徴量を限定できるので、年齢や性別のように現実には変えられない属性を最初から除外できます。

DiCEによる反実仮想サンプル生成

DiCE (Diverse Counterfactual Explanations) は、その名の通り多様な反実仮想サンプルを生成するためのオープンソースライブラリです。多様性(diversity)に加えて、元のデータポイントへの近さである近接性(proximity)と、変更する特徴量の少なさであるスパース性(sparsity)を重視した設計になっています。

内部では、次の条件を同時に満たす最適化問題を解いています。反実仮想サンプルに対するモデルの予測が望ましいクラスに近づくこと、元のデータポイントから離れすぎないこと(proximity_weightで調整)、生成される複数のサンプルが互いに異なること(diversity_weightで調整)、そして変更される特徴量の数が少ないことです。

否決された申込者に3つの改善案を出す

ローン審査を模した合成データでRandomForestの審査モデルを学習し、否決された申込者1件に対してDiCEで反実仮想サンプルを生成します。数値はすべて合成データで、金額の単位は万円です。

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import dice_ml

rng = np.random.default_rng(0)
n = 2000

# ローン審査を模した合成データ(金額の単位は万円)
df = pd.DataFrame({
    "age": rng.integers(22, 65, n),
    "income": rng.integers(250, 1200, n),          # 年収
    "years_employed": rng.integers(0, 30, n),      # 勤続年数
    "existing_debt": rng.integers(0, 400, n),      # 既存借入
    "loan_amount": rng.integers(100, 900, n),      # 借入希望額
})
# 返済余力(年収-既存借入)と希望額、勤続年数で承認が決まる想定
score = (0.008 * (df["income"] - df["existing_debt"])
         - 0.006 * df["loan_amount"] + 0.08 * df["years_employed"]
         - 2.0 + rng.normal(0, 0.6, n))
df["approval"] = (score > 0).astype(int)

train, test = train_test_split(df, test_size=0.2, random_state=0)
X_cols = ["age", "income", "years_employed", "existing_debt", "loan_amount"]
clf = RandomForestClassifier(n_estimators=200, random_state=0)
clf.fit(train[X_cols], train["approval"])

# DiCEの初期化
d = dice_ml.Data(dataframe=train, continuous_features=X_cols, outcome_name="approval")
m = dice_ml.Model(model=clf, backend="sklearn")
exp = dice_ml.Dice(d, m, method="genetic")

# 否決された申込者を1件選び、年齢を除く特徴量の変更を許して生成する
query = test[test["approval"] == 0][X_cols].iloc[[0]]
dice_exp = exp.generate_counterfactuals(
    query,
    total_CFs=3,                 # 生成する反実仮想サンプルの数
    desired_class=1,             # 目標とするクラス(承認)
    proximity_weight=1.5,        # 近接性の重み
    diversity_weight=1.0,        # 多様性の重み
    features_to_vary=["income", "years_employed", "existing_debt", "loan_amount"],
)
print(dice_exp.cf_examples_list[0].final_cfs_df)

説明対象に選ばれたのは、年齢64歳・年収598万円・勤続16年・既存借入277万円・借入希望716万円の申込者で、モデルの承認確率は0.005、はっきりした否決です。この1件に対して、生成された3案を元の値と並べます(テスト精度0.935のモデルでの実行結果です)。

年収勤続年数既存借入借入希望額予測
元の申込者59816277716否決(承認確率0.005)
案1648(+50)18(+2)198(−79)479(−237)承認
案2719(+121)18(+2)153(−124)582(−134)承認
案3763(+165)14(−2)110(−167)598(−118)承認

3案ともfeatures_to_varyから外した年齢には触れず、既存借入の圧縮と希望額の引き下げを組み合わせています。顧客に提示するなら、年収の変更幅がいちばん小さい案1(+50万円)が軸になります。年収を大きく動かす案3は、勤続年数がむしろ下がっても承認に届くことを示していて、この申込者の否決を支配しているのが年収そのものではなく返済余力と希望額のバランスだと分かります。

DiCEのパラメータと意味

DiCEのパラメータは、生成される反実仮想サンプルの質と多様性を左右します。適切に調整すると、より現実的で解釈しやすいサンプルが得られます。

パラメータ意味効果
proximity_weight元のデータポイントとの近さの重み。この値が大きいほど、元のデータポイントに近い(変化の少ない)反実仮想サンプルが生成される。値が小さすぎると、現実的には起こりえないような極端な変更が提案される可能性がある。逆に大きすぎると、予測が変わるような変更が見つかりにくくなる。
diversity_weight生成される複数の反実仮想サンプル間の多様性の重み。この値が大きいほど、互いに異なる特徴量を持つ多様なサンプルが生成される。値が小さすぎると、似たような変更ばかりが提案される可能性がある。逆に大きすぎると、個々の反実仮想サンプルの質(元のデータとの近さなど)が低下する可能性がある。
features_to_vary変更を許可する特徴量のリスト。指定しない場合は、すべての特徴量が変更可能となる。現実的に変更可能な特徴量のみを指定することで、より実用的で解釈しやすい反実仮想サンプルを生成できる。例えば、年齢や性別など変更できない特徴量を除外できる。

sparsity_weightは、DiCEの現在のバージョンでは直接サポートされていませんが、features_to_varyを使って間接的に制御できます。

優れた反実仮想サンプルの特徴

モデルの予測を覆すだけでは、良い反実仮想サンプルとは言えません。実務で使える説明にするには、次の4つの特性を満たす必要があります。

特性説明重要性
実現可能性反実仮想サンプルが示す特徴量の値が、現実世界で実際に起こりうるものであること。実現不可能な提案をしても意味がないため。
最小変更元のデータポイントから、できるだけ少ない変更で目的の予測結果を得られること。ユーザーが実際に行動を起こす際の負担を最小限にするため。
解釈可能性どの特徴量がどのように変更されたのかが、人間にとって理解しやすいこと。ユーザーがモデルの提案を理解し、納得して受け入れるために重要。
多様性複数の異なる反実仮想サンプルが提示されることで、ユーザーが自身の状況や制約に合わせて最適な選択肢を選べること。単一の「正解」を押し付けるのではなく、複数の選択肢を提供することで、ユーザーの自由度を高める。

反実仮想サンプルの利用用途

主要な用途は次のとおりです。

メリット説明具体例
意思決定の支援モデルの予測を望ましい結果に変えるために、具体的にどの特徴量をどう変更すれば良いのかを提示できるローン審査で不承認となった場合に、「年収を〇〇万円増やせば承認される可能性がある」と具体的な改善策を提示できる
公平性検証性別や人種といった保護されるべき属性を変更したときに、モデルの予測が不当に変わらないかを検証できる性別を男性から女性に変更したときにローンの承認確率が大きく下がる場合、モデルが性別による差別をしている可能性を検出できる
説明可能性の向上モデルが「なぜこの予測をしたのか」を、具体的な反例(反実仮想サンプル)を用いて、より人間にとって理解しやすい形で説明できる「もしこの顧客の年齢が〇〇歳若ければ、ローンのリスク評価は〇〇%低くなっていた」というように、モデルの判断根拠を具体的に示せる
モデルの頑健性評価入力データのわずかな変化に対して、モデルの予測がどの程度影響を受けるか(感度)を評価できる特定の特徴量を少しだけ変化させたときに予測結果が大きく変わる場合、モデルが過学習を起こしているか、外れ値に弱い可能性があることを検出できる

4つの用途をDiCEでどう実装するか

意思決定支援

ローン審査で否決された顧客への改善提案を例にします。まず否決された顧客のデータを選び、desired_class=1(承認)を目標に設定します。features_to_varyには年収、勤続年数、借入希望額など、顧客が実際に変えられる特徴量だけを指定します。proximity_weightは高めにして現実的な変更に絞り、複数の反実仮想サンプルを生成します。生成結果と元のデータを比較してどの特徴量がどう変わったかを整理し、「年収を50万円増やすと承認される可能性が高まります」のような形で顧客に伝えます。

モデルの公平性検証

性別に対する公平性を調べる場合は、性別以外の特徴量が似ている男女のペアをデータセットから抽出します。features_to_vary=['性別']と設定して性別だけを入れ替えた反実仮想サンプルを作り、元のデータと予測(承認確率など)がどう変わるかを比較します。性別を入れ替えただけで予測が大きく動くなら、モデルが性別によるバイアスを持っている可能性があります。多数のペアで予測の変化を集め、t検定などで統計的に有意な差かどうかを確かめます。

説明可能性向上

予測理由の説明に使う場合は、説明したい予測結果(例: ローン否決)を持つデータを選び、total_CFsを3〜5程度に設定して複数の反実仮想サンプルを生成します。diversity_weightを適度に効かせると、多様な変更パターンが得られます。元のデータと各サンプルを見比べて変更点を特定し、「もし年収が〇〇万円高ければ、ローンは承認されていた」という形で提示します。

モデルの頑健性評価

入力の変化に対するモデルの感度を評価する場合は、proximity_weightを低めに設定して広い範囲の変化を許容し、テストデータの各点に対してtotal_CFs=10程度の反実仮想サンプルを生成します。元のデータと各サンプルについて、特徴量の変化量と予測確率の変化量を計算し、散布図で両者の関係を確かめます。わずかな変化で予測が大きく動く特徴量があれば、モデルが過学習を起こしているか、外れ値に弱い可能性があります。

まとめ

反実仮想サンプルは、SHAPやLIMEのような手法が示す「どの特徴量が予測に効いたか」にとどまらず、「何がどうなれば結果が変わるか」までを示します。ただし提案が実現可能性を欠けば、説明として役に立ちません。実務でDiCEを使うときは、まずfeatures_to_varyで変えられない属性を除外し、proximity_weightを高めに設定するところから始めてください。生成されたサンプルが良いかどうかは、業務の担当者が「これなら顧客にそのまま伝えられる」と判断できるかで評価します。手元に分類モデルとデータがあれば、DataModelDiceの3つのオブジェクトを差し替えるだけで試せます。

関連記事

← 技術ブログ一覧へ