SHAPによる機械学習モデルの局所説明

# はじめに
こんにちは、@sasshi_iです。

今回はSHAP(Shapley Additive explanations)について記事を書きたいと思います。

SHAPは**局所説明**の手法で、機械学習モデルの個々の予測結果を説明します。

局所説明が何かわからない方は過去の記事をご覧ください。

https://qiita.com/sasshi_i/items/400b7672d8b80bfc3a28

それではSHAPについて説明していきます。

# SHAPとは?
SHAPは機械学習モデルの局所説明の手法で、ゲーム理論における `シャープレイ値`をベースとしています。シャープレイ値は多人数参加の協力ゲームにおいて、各プレイヤーの貢献度を公平に割り当てるための方法です。

SHAPはシャープレイ値を機械学習モデルに適用した手法で、モデルの各特徴が予測に与える影響の大きさを公平かつ正確に割り当てることを目的としています。

まずはシャープレイ値について説明していきます。

## シャープレイ値の算出方法
上述の通り、シャープレイ値は多人数参加の協力ゲームにおいて、各プレイヤーの貢献度を公平に割り当てることを目的としています。

全プレイヤー集合を$N$, 任意のプレイヤー集合を$S$, あるプレイヤー集合から得られる報酬を計算する関数を$v$とすると、シャープレイ値は下記の流れで計算されます。

* **プレイヤー$i$を含まない任意のプレイヤー集合の報酬を計算**: プレイヤー$i$を含まないプレイヤー集合の報酬を計算($v(S)$)
* **$i$を含めたプレイヤー集合の報酬を計算**: 任意のプレイヤー集合にプレイヤー$i$を含めた場合の報酬を計算($v(S \cup \lbrace i \rbrace)$)
* **プレイヤーiの貢献度の計算**:　プレイヤー$i$を含めた場合の報酬から含めていない場合の報酬を引いてプレイヤー$i$の貢献度を算出する($v(S \cup \lbrace i \rbrace) - v(S)$)
* **Sの定義による重みづけ**: (ある任意のプレイヤー集合$S$の順列) * (全プレイヤー数から$i$と$S$を除いた集合の順列)で、ある集合$S$を定義した時のプレイヤーの参加順の組み合わせを算出することができます。それを全プレイヤー集合の順列で割ることで、ある集合$S$を定義した時のプレイヤーの参加順の組み合わせが全プレイヤーの組み合わせの中でどれくらいの割合を占めるか計算できます。($\frac{|S|!(|N|-|S|-1)!}{|N|!}$)
* **$i$を含めない全てのプレイヤー集合において、重みづけした貢献度を算出し、総和を計算**:　全プレイヤー集合$N$において、$i$を含めない任意のプレイヤー集合$S$は何パターンか存在します。すべてのパターンにおいてプレイヤー$i$の貢献度を算出し、総和を出します。($\sum_{S \subseteq N \backslash i} \frac{|S|!(|N|-|S|-1)!}{|N|!} v(S \cup \lbrace i \rbrace) - v(S)$) 

以上の流れから、あるプレイヤー$i$のシャープレイ値$\phi_i$は下式で算出できます。

$$
\phi_i = \sum_{S \subseteq N \backslash i} \frac{|S|!(|N|-|S|-1)!}{|N|!} (v(S \cup \lbrace i \rbrace) - v(S))
$$


直感的には、シャープレイ値は**全プレイヤー一人ずつ加えていくすべての並べ方を考えた時、$i$を加えた時に得られる報酬の増分の平均**と考えられます


## シャープレイ値の特徴
シャープレイ値にはいくつか特徴があります。それらの特徴について下記に示します。

* **全体合理性**: 全プレイヤーによって生み出された報酬が、個々のプレイヤーに配分されるシャープレイ値の合計に等しい($\sum_{i \in N}\phi_i = v(N)$)
* **対称性**: 同じ働きをするプレイヤーには同じシャープレイ値が配分される
* **ダミープレイヤーのゼロ報酬**: 働きをしないプレイヤーにはシャープレイ値0が割り当てられる
* **加法性**: $v$とは別の報酬$w$を与えた時、2つの報酬の和に対して算出されたシャープレイ値は、$v$に対するシャープレイ値と$w$に対するシャープレイ値の和と一致する($\phi_i(v+w)=\phi_i(v)+\phi_i(w)$)

## シャープレイ値の機械学習への適用
シャープレイ値はどのように機械学習モデルの説明に適用されるのでしょうか。

シャープレイ値はプレイヤーの報酬への寄与度を算出していました。

これを、**機械学習予測に与える各特徴量の寄与度**と読み替えれば機械学習モデルの説明に使えそうです。

しかし、シャープレイ値を算出する時に計算した **任意のプレイヤー集合$S$での報酬 = いくつかの特徴量が欠損した状態の機械学習モデルの予測値** を求めるのが難しそうです。

この問題に対して、**周辺化**という考え方を用います。
具体的な流れは次章で説明します。

## SHAPによる機械学習モデルの説明
SHAPでは**ある特徴量が加わった時、平均的な予測値からの変動量をその特徴量の貢献度**とすることを前提とします。


平均的な予測値は周辺化を行い、算出されます。


つまり、着目していない特徴量については取り得るすべての値で予測を行い、その予測値の平均を取ることで平均的な予測値としています。

例えば、特徴量A, B, Cを持つ機械学習モデルがあるとします。A=20, B=30, C=40の時の各特徴量の寄与度を算出したい場合、

* A, B, Cの取り得る値すべてで予測値を算出し(全ての特徴量を周辺化)、平均を取る(ベースライン)
* A=20と固定し、B, Cを周辺化して予測値を算出。予測値からベースラインを差し引くことでA=20を加えた時の変動を算出
* A=20, B=30と固定し、Cを周辺化して予測値を算出。予測値からベースラインとA=20の時の貢献度を差し引くことで、B=30を加えた時の変動を算出
* A=20, B=30, C=40と固定して予測値を算出。予測値からベースライン、A, Bの貢献度を差し引くことでC=40を加えた時の変動を算出

このようにして、各特徴量の予測への寄与度を算出します。

DataRobotの記事でわかりやすい図があったのでよかったらこちらを参照してみてください。

https://www.datarobot.com/jp/blog/explain-machine-learning-models-using-shap/

実際にはこの方法だと計算量が膨大になるので、SHAPのライブラリでは**KernelSHAP**や**TreeSHAP**といった方法を用いて計算が行われます。

# SHAPを用いた局所説明実装
それでは実際にSHAPのライブラリを使って機械学習モデルの説明を実装してみましょう。

今回使用するデータセットは、[kaggleのタイタニックのデータ](https://www.kaggle.com/competitions/titanic)です。
機械学習モデルはLightGBMを使い、分類予測を行いたいと思います。

```python
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import shap
from lightgbm import LGBMClassifier
from sklearn.model_selection import train_test_split

# 前処理
df = pd.read_csv("train.csv")
drop_columns = ["PassengerId", "Ticket", "Cabin", "Name"]
cotegory_coulms = ["Embarked", "Sex"]
df = df.dropna(subset=["Embarked"])
df = df.drop(columns=drop_columns)
df["Age"] = df["Age"].fillna(df["Age"].median())
df = pd.get_dummies(df, columns=cotegory_coulms)

# 訓練データ、テストデータ分割
df_train, df_test = train_test_split(df, test_size=0.2, stratify=df["Survived"], random_state=99)

# モデル訓練
X_train = df_train.drop("Survived", axis=1)
y_train = df_train["Survived"]
X_test = df_test.drop("Survived", axis=1)
y_test = df_test["Survived"]

model = LGBMClassifier(
    max_depth=4,
    colsample_bytree=0.5,
    reg_lambda=0.5,
    reg_alpha=0.5,
    importance_type="gain",
    random_state=100
)
model.fit(
    X_train,
    y_train,
    eval_sample_weight=0.2,
)
```
モデルの訓練ができたら、SHAPで予特徴量の測に対する寄与度を可視化します。


LightGBMは決定木ベースの分類木なので、SHAPのTreeExplainerを使用します。

`decision_plot`を用いることで、特定のデータに対する特徴量の影響を可視化できます。

```python
exp = shap.TreeExplainer(model)
sv_test = exp.shap_values(X_test)

shap.decision_plot(exp.expected_value, sv_test[0], X_test.iloc[0])
```
![スクリーンショット 2024-04-03 165240.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324032/d93f99b1-3fe2-4651-872c-b811f1903fed.png)

`decision_plot`は一番下を出発点（=予測値の平均値）として、下から順に特徴量の影響を加えていって、最終的な予測値に至るまでの経路を表示します。

今回の例では、AgeやFareが予測に対してプラスに寄与しており、Sex_maleがマイナスに寄与していることがわかります。

また、全体のデータセットに対して特徴量の効き方を俯瞰したい時は`summary_plot`を使用することができます。

```python
shap.summary_plot(sv_test, X_test)
```
![スクリーンショット 2024-04-03 165804.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324032/dc784045-0b06-6454-7fae-da9e62803981.png)

`summary_plot`で全データに対するSHAP値の散布図を見ることができます。

# 終わりに
今回はSHAPについて説明させていただきました。LIMEに続き、局所説明手法の解説になりました。LIMEも局所説明の代表的な手法なので興味のある方は下記の記事をご覧ください。

https://qiita.com/sasshi_i/items/a1cb8339e668f5fd4e19

また、記事が役に立ったという方は**いいね**や**ストック**をしていただけると嬉しいです。

# 参考文献
https://www.datarobot.com/jp/blog/explain-machine-learning-models-using-shap/

[XAI(説明可能なAI)--そのとき人工知能はどう考えたのか?](https://amzn.to/4cHP2bA)

# 関連記事
https://qiita.com/sasshi_i/items/400b7672d8b80bfc3a28

https://qiita.com/sasshi_i/items/aeacb849db5bdc444a85