モラルAIデザイン実践

AIモデルの「なぜ?」を解き明かす:LIMEとSHAPによる説明可能性実践ガイド

Tags: AI倫理, 説明可能性, XAI, LIME, SHAP, Python

はじめに

近年、AIシステムの社会実装が進むにつれて、その意思決定プロセスに対する説明可能性(Explainability)の重要性が増しています。特に医療、金融、採用などの分野では、AIの予測結果だけでなく、「なぜそのような予測に至ったのか」という根拠を人間が理解できる形で示すことが、信頼性の向上、責任の所在の明確化、そして法規制への対応のために不可欠となっています。

自律システムにおいて、予測や判断の背景にある論理が不透明であることは、不公平な結果の発生やシステム障害時の原因特定を困難にし、ユーザーからの信頼を得られない要因となります。この課題に対処するための技術分野がExplainable AI (XAI) です。XAIの目的は、複雑なAIモデルの内部動作や予測結果の根拠を、人間が理解可能な形で提供することにあります。

本記事では、数あるXAI技術の中から、モデルの種類に依存しない(Model-agnostic)代表的な手法であるLIME(Local Interpretable Model-agnostic Explanations)とSHAP(SHapley Additive exPlanations)に焦点を当てます。これらの技術の原理、実装方法、そして実践上の注意点について、AIエンジニアの視点から詳細に解説します。

説明可能性技術の分類

説明可能性技術は、大きく以下の観点から分類されます。

LIMEとSHAPは、主にモデル非依存の技術であり、ローカルな説明を提供する強力な手法として広く利用されています。

LIME (Local Interpretable Model-agnostic Explanations)

LIMEは、Christoph Molnar氏の書籍『Interpretable Machine Learning』でも詳しく解説されているように、特定のデータインスタンスの周囲でモデルの振る舞いを線形モデルで近似することで、そのインスタンスに対する予測がどのように生成されたかを説明する手法です。

原理

LIMEの基本的なアイデアは、説明したいインスタンス(対象インスタンス)の「近く」にあるデータをいくつか生成し、それらのデータに対する対象モデルの予測結果を取得します。次に、対象インスタンスからの距離に応じてデータの重み付けを行い、その重み付きデータセット上で線形モデルなどの解釈可能なモデルを学習します。この線形モデルの係数を見ることで、対象インスタンスの予測に各特徴量が局所的にどのように影響しているかを理解できます。

アルゴリズムの概要

  1. 説明したいインスタンス $x$ と、そのインスタンスに対するブラックボックスモデル $f$ の予測 $f(x)$ があります。
  2. インスタンス $x$ をわずかに摂動させた(ランダムに変更を加えた)近傍のデータインスタンス $x'$ をいくつか生成します。
  3. 生成した各 $x'$ に対して、ブラックボックスモデル $f$ を用いて予測 $f(x')$ を行います。
  4. 各 $x'$ に、対象インスタンス $x$ からの距離に基づいた重み $w(x, x')$ を割り当てます。$x'$ が $x$ に近いほど重みは大きくなります。
  5. 生成したデータ ${ (x', f(x')) }$ と重み ${ w(x, x') }$ を用いて、線形モデル $g$ などの解釈可能なモデルを学習します。学習の目的は、局所的に $f$ を近似することです。 $$ \min_g \sum_{x' \in \Omega} w(x, x') (f(x') - g(x'))^2 $$ ここで $\Omega$ は生成された近傍データセットです。
  6. 学習した解釈可能なモデル $g$ (例: 線形モデルの係数)を用いて、インスタンス $x$ の予測に対する説明を生成します。

LIMEはモデルの種類に依存しないため、ニューラルネットワーク、勾配ブースティング、サポートベクターマシンなど、あらゆるモデルに適用できます。また、表データ、テキスト、画像など、様々なデータ形式に対応しています。テキストデータの場合は単語の有無、画像データの場合はSuperpixelの有無などが特徴量として扱われます。

PythonによるLIMEの実装例(表データ)

ここでは、limeライブラリを使用して、回帰モデルに対する予測を説明する例を示します。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from lime import lime_tabular

# サンプルデータの生成 (Boston Housingデータセットを模倣)
# scikit-learn 1.2以降ではload_bostonが削除されたため、np.randomを使用
np.random.seed(42)
n_samples = 500
n_features = 5
X = np.random.rand(n_samples, n_features) * 10 # 5つの特徴量
y = 2*X[:, 0] + 1.5*X[:, 1] - 3*X[:, 2] + np.random.randn(n_samples) * 5 # 線形関係+ノイズ

feature_names = [f'feature_{i}' for i in range(n_features)]
X_df = pd.DataFrame(X, columns=feature_names)
y_df = pd.Series(y, name='target')

# モデルの学習 (ここではランダムフォレスト回帰を使用)
X_train, X_test, y_train, y_test = train_test_split(X_df, y_df, test_size=0.2, random_state=42)
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# LIME Explainerの初期化
# training_data, feature_names, class_names (分類の場合) が必要
# training_dataは説明器の学習に使用されるデータであり、モデル学習時のデータとは異なる場合もある
explainer = lime_tabular.LimeTabularExplainer(
    training_data=X_train.values,
    feature_names=feature_names,
    class_names=['target'], # 回帰でもリスト形式
    mode='regression' # 'regression' または 'classification'
)

# 特定のインスタンスに対する説明の生成
instance_idx = 0 # 説明したいテストセットの最初のインスタンス
instance = X_test.iloc[instance_idx]
print(f"説明対象インスタンス:\n{instance}\n")
print(f"モデル予測値: {model.predict(instance.values.reshape(1, -1))[0]:.2f}\n")

explanation = explainer.explain_instance(
    data_row=instance.values,
    predict_fn=model.predict,
    num_features=len(feature_names) # 表示する特徴量の数
)

# 説明結果の表示 (テキスト形式とHTML形式)
print("LIMEによる説明 (テキスト):")
for explanation_tuple in explanation.as_list():
    print(f"  {explanation_tuple[0]}: {explanation_tuple[1]:.4f}")

# HTML形式で保存して可視化
# explanation.show_in_notebook() # Jupyter Notebookなどで表示
# explanation.save_to_file('lime_explanation.html') # HTMLファイルとして保存

この例では、explain_instance メソッドで、指定したインスタンス (data_row) に対して、モデルの予測関数 (predict_fn) を使って説明を生成しています。num_features で、説明に含める最も貢献度の高い特徴量の数を指定します。出力される説明は、各特徴量が予測値に対してどの程度、どのような向き(正または負)に影響しているかを示すリストとして得られます。

SHAP (SHapley Additive exPlanations)

SHAPは、ゲーム理論におけるシャープレイ値(Shapley value)の概念に基づき、各特徴量が予測にどれだけ貢献したかを公平に分配することで説明を提供する手法です。LIMEと同様に、ローカルな説明を生成できますが、グローバルな説明や特徴量間の相互作用の分析にも応用可能です。

原理

シャープレイ値は、協調ゲームにおいて各プレイヤーの貢献度を計算するための公平な方法として提案されました。SHAPはこのアイデアを機械学習モデルの特徴量に適用します。ある特徴量のSHAP値は、その特徴量を「持っている」連合と「持っていない」連合の間で、すべての可能な特徴量の順列において予測値がどれだけ変化するかの平均として定義されます。

数学的には、インスタンス $x$ に対するモデル $f$ の予測 $f(x)$ を、ベースライン(平均的な予測など)からの差分として表現する加法モデルとして定義されます。 $$ f(x) = \phi_0 + \sum_{j=1}^M \phi_j x_j $$ ここで $\phi_0$ はベースラインの予測値、$\phi_j$ は特徴量 $j$ のSHAP値、$M$ は特徴量の総数です。SHAP値 $\phi_j$ は、以下のシャープレイ値の式に基づいて計算されます。 $$ \phi_j = \sum_{S \subseteq {1, \dots, M} \setminus {j}} \frac{|S|!(M - |S| - 1)!}{M!} [f_x(S \cup {j}) - f_x(S)] $$ ここで $S$ は特徴量 $j$ を含まない他の特徴量の任意のサブセット、$f_x(S)$ は特徴量 $S$ のみを使用してインスタンス $x$ に対する予測を行った場合の予測値です。これは概念的な定義であり、実際にすべてのサブセットを計算することは計算量的に困難です。

SHAPは、このシャープレイ値の概念を効率的に計算するための様々な実装(KernelSHAP, TreeSHAP, DeepSHAPなど)を提供しており、モデルの種類やデータ形式に応じて使い分けることが可能です。KernelSHAPはモデル非依存、TreeSHAPはツリーベースモデル(決定木、ランダムフォレスト、勾配ブースティング)に特化しており高速、DeepSHAPは深層学習モデルに特化しています。

PythonによるSHAPの実装例(表データ)

ここでは、shapライブラリを使用して、ランダムフォレスト回帰モデルに対する予測を説明する例を示します。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
import shap

# サンプルデータの生成 (LIMEの例と同じ)
np.random.seed(42)
n_samples = 500
n_features = 5
X = np.random.rand(n_samples, n_features) * 10
y = 2*X[:, 0] + 1.5*X[:, 1] - 3*X[:, 2] + np.random.randn(n_samples) * 5

feature_names = [f'feature_{i}' for i in range(n_features)]
X_df = pd.DataFrame(X, columns=feature_names)
y_df = pd.Series(y, name='target')

# モデルの学習 (ランダムフォレスト回帰)
X_train, X_test, y_train, y_test = train_test_split(X_df, y_df, test_size=0.2, random_state=42)
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# SHAP Explainerの初期化
# TreeSHAPはツリーベースモデルに対して最適化されており高速
explainer = shap.TreeExplainer(model)

# テストセット全体または一部のインスタンスに対するSHAP値を計算
# shap_valuesは NumPy配列: (num_instances, num_features)
# インスタンスごとの各特徴量の貢献度を表す
shap_values = explainer.shap_values(X_test)

# ベースライン値(通常は学習データの平均的な予測値)
expected_value = explainer.expected_value

# 特定のインスタンスに対する説明の可視化 (Force Plot)
instance_idx = 0 # 説明したいテストセットの最初のインスタンス
print(f"説明対象インスタンス (Force Plot):\n{X_test.iloc[instance_idx]}\n")
print(f"ベースライン値 (Expected Value): {expected_value:.2f}")
print(f"モデル予測値 (Actual Value): {model.predict(X_test.iloc[instance_idx].values.reshape(1, -1))[0]:.2f}\n")

# Force Plotによるローカルな説明
# shap.initjs() # Jupyter NotebookでJSを有効にする場合
# shap.force_plot(expected_value, shap_values[instance_idx, :], X_test.iloc[instance_idx, :])

# Summary Plotによるグローバルな特徴量重要度
# shap.summary_plot(shap_values, X_test)

# Dependence Plotによる特徴量間の相互作用分析
# shap.dependence_plot("feature_0", shap_values, X_test, interaction_index=None) # 相互作用なし
# shap.dependence_plot("feature_0", shap_values, X_test, interaction_index="feature_1") # feature_0とfeature_1の相互作用

SHAPライブラリでは、モデルの種類に応じて shap.TreeExplainer, shap.KernelExplainer, shap.DeepExplainer などを使い分けます。計算されたSHAP値は、特定のインスタンスに対する各特徴量の予測値への貢献度を示します。SHAP値の合計にベースライン値を加えると、そのインスタンスに対するモデルの予測値と等しくなります。

SHAPライブラリは豊富な可視化ツールを提供しています。Force Plotは個々の予測がどのように形成されたかを、各特徴量の貢献度を積み上げて表示します。Summary Plotはデータセット全体での各特徴量の重要度と影響の方向性を一目で把握できます。Dependence Plotは特定の特徴量と予測値の関係、および他の特徴量との相互作用を分析できます。

LIMEとSHAPの比較

LIMEとSHAPはどちらもモデル非依存のローカルな説明を提供する強力な手法ですが、原理と特性に違いがあります。

| 特徴 | LIME | SHAP | | :------------ | :--------------------------------------- | :------------------------------------------------------------------- | | 原理 | 局所的な線形モデル近似 | ゲーム理論のシャープレイ値に基づいた貢献度分配 | | 局所的な説明 | 摂動させた近傍データでの線形モデル係数 | シャープレイ値(ベースラインからの予測値への貢献度) | | グローバルな説明 | 個別説明の集計で近似的に可能 | Summary Plotなどで体系的に提供可能 | | 計算コスト| インスタンスごとにモデルの予測が多数必要 | シャープレイ値計算は原理的に高コスト、各種Explainerで効率化 | | 数学的根拠| 局所的な近似 | 強固な理論的根拠(公平性、一意性、加法性などの望ましい性質を持つ) | | 安定性 | サンプリングや近傍の定義に依存し、不安定になることがある | シャープレイ値の定義は安定しているが、KernelSHAPなどは計算上の近似に依存 | | 実装 | limeライブラリ | shapライブラリ |

使い分けの考慮事項:

実践上の注意点

LIMEやSHAPといった説明可能性技術を実践に導入する際には、いくつかの注意点があります。

まとめ

本記事では、自律システムの倫理設計において重要な要素である説明可能性を実現するための技術として、LIMEとSHAPの原理、Pythonでの実装例、そして両者の比較と実践上の注意点について解説しました。

LIMEとSHAPは、モデルの種類を問わず、個々の予測に対する特徴量の貢献度を理解するための強力なツールです。これらの技術を活用することで、ブラックボックス化しがちなAIモデルの判断根拠をより深く理解し、モデルのデバッグ、改善、そしてユーザーへの説明責任を果たすための第一歩を踏み出すことができます。

しかし、これらの技術も万能ではありません。生成される説明の限界を理解し、人間の解釈や文脈への配慮と組み合わせることが重要です。AIエンジニアとしては、これらの技術を適切に使いこなし、倫理的に堅牢で信頼性の高いAIシステムを構築していくことが求められます。今後は、これらのローカルな説明だけでなく、モデル全体の振る舞いを理解するためのグローバルな説明技術や、因果推論に基づく説明可能性技術なども重要なテーマとなってくるでしょう。


参考文献

```