LIMEを用いて機械学習（回帰モデル）の予測結果を解釈してみた

* 製造業出身のデータサイエンティストがお送りする記事
* 今回はLIMEを用いて機械学習（回帰モデル）の予測結果を解釈してみました。

##はじめに
前回、[機械学習の予測モデルをscikit-learnを活用して実装](https://qiita.com/DS27/items/aa3f6d0f03a8053e5810)してみました。また、構築したモデルは[評価指標](https://qiita.com/DS27/items/2204b10888a19869e083)を用いてモデルを評価します。
しかし、評価指標だけでモデルの良し悪しを判断するのは危険であり、構築したモデルが実態と乖離している場合があります。つまり、汎化能力が低いモデルである可能性があるということです。

汎化能力を高める方法は多々ありますが、製造現場では構築したモデルの解釈性を求められることが多いです。実際は、回帰モデル系であれば各説明変数の回帰係数の正負や標準偏回帰係数で変数間の影響度を見て固有技術と合致しているかを見極めたりします。また、決定木系のモデルであれば変数重要度を見て判断をします。

しかし、決定木系のモデル（RandomForest、GBDT、等）は各変数が目的変数へ与える影響の正負を判断することができません。また、SVRではカーネルをlinear以外を選択すると回帰係数も変数重要度も算出することができません（※scikit-learnのライブラリーに限った話です）。

そこで、今回は上記のような課題を解決する手段の一つとして、LIMEを用いて、予測した値に対して、「各変数がどのような影響を与えたのか？」を可視化する技術を整理しました。

ちなみに過去に他の手法として[SHAP](https://qiita.com/DS27/items/9059667acc580ca9bbb7)についても整理しております。

##LIMEの実装
今回はUCI Machine Learning Repositoryで公開されているボストン住宅の価格データを用いて予測モデルを構築します。

|項目|概要|
|:---|:---|
|データセット|・boston house-price　|
|サンプル数|・506個　|
|カラム数|・14個　|

pythonのコードは下記の通りです。

```php
# ライブラリーのインポート
import pandas as pd
import numpy as np
from sklearn.datasets import load_boston

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from lime.lime_tabular import LimeTabularExplainer

# データセットの読込み
boston = load_boston()

# データフレームの作成
# 説明変数の格納
df = pd.DataFrame(boston.data, columns = boston.feature_names)

# 目的変数の追加
df['MEDV'] = boston.target

# カラム名
feature_names = np.array(df.drop('MEDV', axis=1).columns)

# データの中身を確認
df.head()
```

![スクリーンショット 2020-11-09 20.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/2ffa1a41-98dd-2798-1f6d-8f3385bf5f75.png)

各カラム名の説明は省略します。
 ・説明変数：13個
 ・目的変数：1個（MEDV）

次に、予測モデルを構築します。今回は、RandomForest回帰を活用します。

```php
# ライブラリーのインポート
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor

# 学習データと評価データを作成
x_train, x_test, y_train, y_test = train_test_split(df.iloc[:, 0:13], df.iloc[:, 13],
                                                    test_size=0.2, random_state=1)

# モデルの学習
RF = RandomForestRegressor()
RF.fit(x_train, y_train)
```

##LIMEで結果を解釈する
今回は、線形モデルを使用して局所的な解釈を得ます。

```php
# 学習データやタスクの内容といった情報を渡してインスタンスを作成
explainer = LimeTabularExplainer(training_data=np.array(x_train),
                                 feature_names=feature_names,
                                 training_labels=np.array(y_train),
                                 discretize_continuous=True,
                                 mode='regression',
                                 verbose=True,
                                )

# 予測値を返す関数の作成
predict_proba = lambda x: np.array(list(zip(1-RF.predict(x), RF.predict(x))))

# 線形モデルを使って近似させたときの切片と予測値、本来のモデルの予測値が出力
exp = explainer.explain_instance(
    x_train.iloc[0], 
    predict_proba, 
    num_features=x_train.columns.shape[0]
)
```

下記のように線形モデルを使って近似させたときの切片と予測値、そして本来のモデルの予測値が出力されます。

```
Intercept -20.775610936690455
Prediction_local [-26.36992196]
Right: -24.018000000000008
```

最後に結果をJupyter Notebook上で確認します。

```php
# Notebook 上で視覚的に確認
exp.show_in_notebook(show_all=False)
```

![スクリーンショット 2021-03-21 14.32.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/e5914a5e-e9ef-82cb-49ab-6128e917b688.png)

##さいごに
最後まで読んで頂き、ありがとうございました。
今回は機械モデルの結果を解釈する手法としてLIMEを実装してみました。
使い勝手的には過去に紹介したSHAPの方が使いやすそうです。

訂正要望がありましたら、ご連絡頂けますと幸いです。