LESSON

SHAP分析の統合

「予測できるだけでは不十分だ。なぜその顧客が離反しそうなのか、説明できなければビジネスには使えない。」

田中VPoEが強調する。

「SHAPは予測の根拠をフェアに分解できる手法だ。各特徴量がどれだけ予測に寄与しているかを数値化し、そこから施策に変換する。これがAIエージェントの真価だ。」

SHAPとは

SHAP(SHapley Additive exPlanations)は、ゲーム理論のShapley値に基づく機械学習モデルの説明手法である。

SHAPの特徴:
1. 局所説明: 個別の予測に対する各特徴量の寄与度を計算
2. 大域説明: 全データに対する特徴量の全体的な重要度を計算
3. 一貫性: 特徴量の寄与の合計 = 予測値 - ベースライン
4. 公平性: ゲーム理論に基づく数学的に公正な配分

SHAP値の直感的な理解

# SHAP値の意味
# f(x) = base_value + SHAP(x1) + SHAP(x2) + ... + SHAP(xn)
# ベースライン: 全データの平均予測値
# SHAP(xi): 特徴量xiが予測値をベースラインからどれだけ変化させるか

import shap
import numpy as np

# TreeExplainerの作成(決定木ベースモデル用)
explainer = shap.TreeExplainer(model)

# 単一サンプルのSHAP値を計算
sample = X_test.iloc[0:1]
shap_values = explainer.shap_values(sample)

# クラス1(離反)のSHAP値
if isinstance(shap_values, list):
    sv = shap_values[1][0]  # [クラス][サンプル]
else:
    sv = shap_values[0]

print(f"ベースライン(平均離反確率): {explainer.expected_value[1]:.4f}")
print(f"予測値: {model.predict_proba(sample)[0][1]:.4f}")
print(f"SHAP値の合計: {sv.sum():.4f}")

局所説明(個別の予測)

# 個別予測のSHAP分析
def individual_shap_analysis(model, explainer, sample, feature_names):
    """1件の顧客に対するSHAP分析"""
    shap_values = explainer.shap_values(sample)

    if isinstance(shap_values, list):
        sv = shap_values[1][0]
    else:
        sv = shap_values[0]

    # 要因の整理
    factors = sorted(
        zip(feature_names, sv, sample.values[0]),
        key=lambda x: abs(x[1]),
        reverse=True
    )

    print("=== 離反要因分析 ===")
    for name, shap_val, feat_val in factors[:5]:
        direction = "↑離反促進" if shap_val > 0 else "↓離反抑制"
        print(f"  {name}: SHAP={shap_val:+.4f} (値={feat_val:.2f}) {direction}")

    return factors

factors = individual_shap_analysis(model, explainer, sample, X_test.columns.tolist())

Force Plot(力グラフ)

# 個別予測のForce Plot
shap.force_plot(
    explainer.expected_value[1],
    shap_values[1][0],
    sample.iloc[0],
    feature_names=X_test.columns.tolist(),
    matplotlib=True
)
plt.savefig('shap_force_plot.png', dpi=150, bbox_inches='tight')

Waterfall Plot(滝グラフ)

# Waterfall Plot
shap_explanation = shap.Explanation(
    values=shap_values[1][0],
    base_values=explainer.expected_value[1],
    data=sample.values[0],
    feature_names=X_test.columns.tolist()
)
shap.waterfall_plot(shap_explanation)
plt.savefig('shap_waterfall.png', dpi=150, bbox_inches='tight')

大域説明(全体的な重要度)

# 全テストデータのSHAP値を計算
shap_values_all = explainer.shap_values(X_test)

# Summary Plot(蜂群図)
shap.summary_plot(
    shap_values_all[1] if isinstance(shap_values_all, list) else shap_values_all,
    X_test,
    plot_type='dot',
    show=False
)
plt.savefig('shap_summary.png', dpi=150, bbox_inches='tight')

# Bar Plot(重要度棒グラフ)
shap.summary_plot(
    shap_values_all[1] if isinstance(shap_values_all, list) else shap_values_all,
    X_test,
    plot_type='bar',
    show=False
)
plt.savefig('shap_importance.png', dpi=150, bbox_inches='tight')

SHAP結果からビジネス施策への変換

def shap_to_business_actions(factors, customer_data):
    """SHAP要因をビジネス施策に変換する"""
    actions = []

    # 施策マッピングルール
    rules = {
        "Contract": {
            "condition": lambda sv, fv: sv > 0,
            "action": "年間契約への移行を提案(初月20%割引 + ポイント2倍)",
            "rationale": "短期契約が離反を促進しています",
        },
        "tenure": {
            "condition": lambda sv, fv: sv > 0,
            "action": "オンボーディングプログラムへの招待",
            "rationale": "利用期間が短く、まだサービスの価値を実感できていない可能性があります",
        },
        "MonthlyCharges": {
            "condition": lambda sv, fv: sv > 0,
            "action": "プラン最適化の提案(不要サービスの見直し)",
            "rationale": "月額料金の高さが負担になっている可能性があります",
        },
        "InternetService": {
            "condition": lambda sv, fv: sv > 0,
            "action": "回線品質チェックの実施と改善保証",
            "rationale": "インターネットサービスへの不満が離反要因です",
        },
        "OnlineSecurity": {
            "condition": lambda sv, fv: sv > 0,
            "action": "オンラインセキュリティ3ヶ月無料トライアルの提供",
            "rationale": "セキュリティサービス未加入が離反リスクを高めています",
        },
        "TechSupport": {
            "condition": lambda sv, fv: sv > 0,
            "action": "テックサポート優先対応チケットの提供",
            "rationale": "テックサポート未加入が離反リスクを高めています",
        },
        "PaymentMethod": {
            "condition": lambda sv, fv: sv > 0,
            "action": "自動引き落とし設定で毎月¥500割引",
            "rationale": "手動支払いが離反の一因となっています",
        },
    }

    for name, shap_val, feat_val in factors[:5]:
        if shap_val <= 0:
            continue

        for key, rule in rules.items():
            if key in name and rule["condition"](shap_val, feat_val):
                actions.append({
                    "action": rule["action"],
                    "rationale": rule["rationale"],
                    "shap_impact": round(float(shap_val), 4),
                })
                break

    return actions

エージェントへの統合

# エージェントのexplainノードを強化
def explain_enhanced(state: ChurnAnalysisState) -> dict:
    """SHAP分析 + 施策変換の強化版"""
    features_array = np.array(state["features"]).reshape(1, -1)

    explainer = shap.TreeExplainer(MODEL)
    shap_values = explainer.shap_values(features_array)

    if isinstance(shap_values, list):
        sv = shap_values[1][0]
    else:
        sv = shap_values[0]

    # 要因の整理
    factors = sorted(
        zip(state["feature_names"], sv, features_array[0]),
        key=lambda x: abs(x[1]),
        reverse=True
    )

    # SHAP結果
    explanations = [{
        "feature": name,
        "shap_value": round(float(shap_val), 4),
        "direction": "離反促進" if shap_val > 0 else "離反抑制",
    } for name, shap_val, _ in factors[:5]]

    # 施策変換
    actions = shap_to_business_actions(factors, state["raw_data"])

    return {
        "shap_explanations": explanations,
        "retention_actions": actions,
    }

まとめ

項目ポイント
SHAPゲーム理論に基づく公正な特徴量寄与度の計算
局所説明個別顧客の離反要因をTop5で表示
大域説明全体の特徴量重要度をSummary Plotで可視化
施策変換SHAP要因 → ルールベースで具体的アクションに変換
可視化Force Plot, Waterfall, Summary Plotの3種

チェックリスト

  • SHAP値の意味を説明できる(ベースラインからの変化量)
  • TreeExplainerで局所説明を実行できる
  • Force PlotとSummary Plotを作成できる
  • SHAP結果からビジネス施策を導出できる
  • エージェントにSHAP分析を統合できる

次のステップへ

SHAP分析の統合が完了した。次は演習で、離反分析AIエージェントを一気通貫で実装してみよう。

推定読了時間: 30分