「特徴量重要度」について少しだけ真剣に考えてみる

# はじめに
機械学習モデルを解釈する際には「特徴量重要度」がキーワードになってきます。**「特徴量重要度」には様々な指標があり、目的が違います。**
①feature importance、②permutation importance、③SHAP　の3つについて説明していきます。

# 結論から言うと
3つの特徴量重要度を調べて、個人的に感じた結論を以下に書きます。

- ①feature importance：予測モデルを組む際に **「モデル」が重要視する因子が分かる** 。例えば決定木を考えた際にどの因子がノードの分割に寄与するのかを評価するイメージ。  

- ②permutation importance：各特徴量が予測にどう寄与するかが分かる。**モデルの「予測精度」に影響する因子が分かる。**

- ③SHAP： **「予測結果」に対する各特徴量の寄与が分かる** 。それぞれのデータの予測にインパクトを与える因子が分かる。

# ①feature importance
ある特徴量で分割することでどれくらいジニ不純度を下げられるのかに基づいて計算される。
その特徴量の分割がターゲットの分類にどれくらい寄与しているかを測る指標。
tree系で使えるが、学習データに対する評価しかできない。モデルを作成する際の寄与度を評価するが、特徴量が予測精度にどれだけ貢献しているかは分からない。

# ②permutation importance（PFI：permutation feature importance）
元のデータと、ある特徴量の値を完全にシャッフルしたデータで、それぞれの正解データと予測データの誤差を比較してその差を特徴量の重要度とする。
特徴量をシャッフルしたことで誤差が大きくなる特徴量はモデルのパフォーマンスへの寄与が大きく、重要と見なされる。
Permutation Importanceはモデルの予測精度向上に効いている特徴量を見つける方法としては有効ですが、SHAPのように「予測がどのように成されたか？」を解釈する手法として用いることはできません。

# ③SHAP（SHapley Additive exPlanations Value）
各特徴量が個別のデータの予測にどの程度貢献しているかを示し、モデルの予測に対する全体的な理解を深めることができます。
SHAP Importanceの値は特定の特徴量の値が結果にどのように影響するかを理解するために使用できます。

# データの読み込み
今回は[こちら](https://qiita.com/oki_kosuke/items/debe4883505cb2d9809d)のページで紹介した"df.csv"というデータを用いていきます。csvファイルは[GitHun](https://github.com/okikosuke/sample/tree/main/sample_data)にも保存しております。

```python
# ライブラリーのインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
```

```python
# データセットの読み込み
df = pd.read_csv("df.csv", encoding="shift_jis")
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/d4e84652-96fa-4c29-2b3c-6675c65d2dbe.png)

```python
# データの分割
from sklearn.model_selection import train_test_split

# 説明変数の定義
X = df.iloc[:,:-1]
y = df.iloc[:,-1]

# 学習データとテストデータへの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=123)

# 学習データのサイズを確認
X_train.shape
```
```:出力
(494, 9)
```

# ランダムフォレスト回帰の実行
feature importanceはtree系にしか使えないので、今回はランダムフォレスト回帰でデータセットを分析していきます。

```python
# ランダムフォレスト回帰のインスタンスの作成
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators = 100,
                           max_features = 5,
                           random_state = 123, 
                           n_jobs = -1)
```
```python
# 学習データに対する精度
rf.fit(X_train, y_train)
rf.score(X_train, y_train)
```
```:出力
0.9897505176839843
```
```python
# テストデータに対する精度
rf.score(X_test, y_test)
```
```:出力
0.9136504687426884
```

# ①feature importanceの計算

```python
# feature importanceの計算
importances = rf.feature_importances_
# 計算結果をデータフレームに格納
df_feature_importance = pd.DataFrame(zip(X_train.columns, importances),columns=["Features","Importance"])
df_feature_importance                                                     
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/43dc88cd-166c-38bf-661b-7d14f85d2eca.png)

[このサイト](https://potesara-tips.com/lightgbm-feature-importance/)にあるように、LightGBMやXGBoostでは特徴量の重要度の計算方法を頻度（初期値）もしくはゲインから指定する必要があるので注意です。

```python
plt.figure(figsize=(10,5))
sns.barplot(x="Importance", y="Features",data = df_feature_importance, ci=None)
plt.title("Permutation Importance")
plt.tight_layout()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/72cd15ef-d52e-1f42-633e-fe0a2bbadeb2.png)

# ②permutation importanceの計算


```python
from sklearn.inspection import permutation_importance
result = permutation_importance(rf, X_test, y_test, n_repeats=10, random_state=123)
```
```python
df_importance = pd.DataFrame(zip(X_train.columns, result["importances"].mean(axis=1)),columns=["Features","Importance"])
df_importance                                                                    ```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/26146343-ef3d-cdcd-7dcf-e36811ff36bf.png)



```python
plt.figure(figsize=(10,5))
sns.barplot(x="Importance", y="Features",data=df_importance,ci=None)
plt.title("Permutation Importance")
plt.tight_layout()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/6843195f-3ca6-af5f-cedd-3dfd38fefde5.png)

①feature importanceと②permutation importanceを比較すると、x8が最重要でその次はx1である点は共通しています。それ以外の説明変数の重要度は両者で多少異なりますが、全体的な傾向としては共通していると言えます。

# ③SHAPの計算

```python
# SHAPの計算
import shap
shap.initjs() # いくつかの可視化で必要
explainer = shap.TreeExplainer(rf, X_test)
shap_values = explainer.shap_values(X_test)
```

```python
# SHAP値の確認
shap_values.shape
```
```:出力
(213, 9)
```
SHAP値は全てのデータに対して算出されます。試しに1つデータを選んで確認してみます。

```python
i = 2
shap.force_plot(explainer.expected_value, shap_values[i,:], X_test.iloc[i,:])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/3acc2a23-7197-e6f8-7b48-9fc7d37e2f58.png)

以下のコードで全体に対してのSHAPの結果を可視化できます。

```python
shap.summary_plot(shap_values, X_test)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/d7d54f93-916b-6c6b-9f8e-1bb80f570a13.png)


③SHAPの結果を見ても、x8が最重要でx1がその次に重要であることが分かります。また①feature importanceや②permutation importanceと比較すると説明変数が目的変数に正の寄与をするのか負の寄与をするのか分かる点が大きな違いです。また各データの予測に対して説明変数がどのように寄与しているのか把握することが出来ます。

## 参考

- [Permutation Importanceを使ってモデルがどの特徴量から学習したかを定量化する](https://www.datarobot.com/jp/blog/permutation-importance/)

- [Permutation Importanceを使って検証データにおける特徴量の有用性を測る](https://qiita.com/kenmatsu4/items/c49059f78c2b6fed0929)

- [特徴量の重要度評価 ~ "Feature Importance"と"Permutation Importance"の比較 ~](https://zenn.dev/yagiyuki/scraps/bd5c01e57785ce)

- [【Python覚書】LightGBM「特徴量の重要度」初期値のままではもったいない](https://potesara-tips.com/lightgbm-feature-importance/#toc2)

- [SHAP値で解釈する前にPermutation ImportanceとPDPを知る](https://research.miidas.jp/2019/11/shap%E5%80%A4%E3%81%A7%E8%A7%A3%E9%87%88%E3%81%99%E3%82%8B%E5%89%8D%E3%81%ABpermutation-importance%E3%82%92%E7%9F%A5%E3%82%8B/)

- [ランダムフォレストの各特徴量の重要度をfeature_importanceから可視化してみた](https://qiita.com/haruki_yasui/items/a83428361fce6b6fbb42)

- [【機械学習】Permutation Importanceでモデルの変数重要度を解釈する](https://www.simpletraveler.jp/2020/07/03/machinelearning-permutation-importance/)

- [SHAP(SHapley Additive exPlanations)で機械学習モデルを解釈する](https://dropout009.hatenablog.com/entry/2019/11/20/091450)