見出し画像

SHAPを使ったモデル解釈


問題設定

前回の記事でlightGBMがどのように予測を行ったのかという根拠を可視化する方法を説明しました。しかし、データがどのようになれば予測が変わるのかを説明するのは難しそうです。

どういうことかというと、例えば、「購買予測をする際にユーザーにどのようなアプローチをかければ、購買行動を起こしやすくなるのか?」という疑問に対する回答はモデルの予測根拠を提示しても得られない。

今回はこの疑問に回答するためにSHAPを使ってみようと思います。
前回、lightGBMでポケモンのステータスからそのポケモンがLegendaryかどうかを判定するモデルを作成しました。このモデルでテスト用データを予測させたとき、誤ってLegendaryであるポケモンがNon Legendaryと予測される場合があります。
このポケモンのステータスがどのようになっていたら、よりLegendaryらしくなるのかをSHAPによって分析します。

使用するデータ

使用するデータは前回と同様のポケモンデータを使用する。

SHAPによる分析

準備

保存したlightGBMモデルを読み込み、SHAP分析の準備を行う。

import lightgbm as lgb
import polars as pl
import shap

test_size = 250
feature_cols = ['hp', 'attack', 'defense', 'sp_attack', 'sp_defense', 'speed']
target_col = ['legendary']
filename = './models/model.txt'

df = pl.read_csv('Pokemon.csv')
df = df.sample(fraction=1, shuffle=True, seed=43)
test, train = df.head(test_size), df.tail(-test_size)

X_train = train.select(feature_cols).cast(pl.Float32).to_pandas()
y_train = train.select(target_col).cast(pl.Float32).to_pandas()
X_test = test.select(feature_cols).cast(pl.Float32).to_pandas()
y_test = test.select(target_col).cast(pl.Float32).to_pandas()

bst = lgb.Booster(model_file=filename)
pred = bst.predict(X_test, num_iteration=bst.best_iteration)

explainer = shap.Explainer(bst)
shap_values = explainer(X_test)

waterfallプロット

waterfallプロットは、予測に対して各特徴量がどのように貢献したかを可視化するグラフである。
ここでは、正解はLegendaryであるが予測がNon Legendaryとなったデータをピックアップして、waterfallプロットを表示する。該当するデータとして、ID245のポケモンをターゲットとして分析を行う。

target_record_index = 245
shap.plots.waterfall(shap_values[target_record_index])
ID245のポケモンのwaterfallプロット

このデータではspeedとattackがLegendary判に大きく貢献しているが、defenceとhpがNon Legendary判定に貢献している。

貢献度がマイナスの特徴量を変更

そこでdefenceとhpを変更して、判定が変わるかどうかを確認してみる。defenceを+10し、hpを+1してみる。

X = X_test.iloc[target_record_index].copy()
X['defense'] = X['defense'] + 10
X['hp'] = X['hp'] + 1

X = X.to_frame().T
pred = bst.predict(X)
print(pred)
shap_values_ = explainer(X)
shap.plots.waterfall(shap_values_[0])

予測値:0.53573043となり、waterfallプロットは以下。

defenceとhpを変更した結果

閾値を0.5としているとき、このステータスを変更したポケモンはLegendaryと判定される。

貢献度がプラスの特徴量を変更

一方で、貢献度が既に高いステータスをより大きくしてみるとどうなるかを確認する。speedを+10し、attackを+5してみる。

X = X_test.iloc[target_record_index].copy()
X['speed'] = X['speed'] + 10
X['attack'] = X['attack'] + 5

X = X.to_frame().T
pred = bst.predict(X)
print(pred)
shap_values_ = explainer(X)
shap.plots.waterfall(shap_values_[0])

予測値:0.34884457となり、waterfallプロットは以下。

speedとattackを変更した結果

閾値が0.5のとき、依然としてこのポケモンはNon Legendaryと判定される。

結論

ここまでの議論で、判定をNon Legendary(0)からLegendary(1)に変えるためには「貢献度が高い特徴量よりも貢献度がマイナスの特徴量の方が少ない変化で判定を変えることができる」ということがわかる。

まとめ

「データがどのように変化すれば予測が変わるのか?」という問題に対して、SHAPのwaterfallプロットを使って一つの回答を得ることができた。

いいなと思ったら応援しよう!