はじめに
前回の記事に引き続き、機械学習モデルと予測結果を解釈する話。
Kaggleで公開されている講座: Use Cases for Model Insightsをやったので、概訳と実行結果をまとめる。
個人的には非常に参考になった講座だったので、もし興味を持たれた方はぜひkaggleの記事も見ていただきたいと思います。
Use Cases for Model Insights
機械学習モデルはブラックボックスで、よい結果を得ることができても、その背景にあるロジックを理解することはできない。
- どの特徴量が最も重要か
- 特徴量が全体の予測結果にどの程度影響を与えるか
- 特徴量がある個別のデータの予測に対してどのように寄与するか
の観点で、予測結果を解釈するテクニックを学ぶ。
予測結果を解釈することは、下記のような観点でも役に立つ。
Debugging
前提として機械学習アルゴリズムにデータを投入する際は、前処理を行う必要がある。しかし、この前処理を行う過程で、様々なエラーが混在してしまう。ターゲットのリークが発生することもある。
デバッグは、これらを防ぐのに最も重要なスキルのひとつ。結果を解釈することは、データの理解が不足している場合にも、バグを発見するのに役に立つ。
Informing Feature Engineering
特徴量生成は精度向上において特に重要。特徴量の数が少なかったり、データの理解ができていたりすると、直感で生成できるが、そうでない場合は他の方法で生成する。
特に名前や意味が不明な特徴量がある場合、ここから有用な特徴量を生成するためには、まず既存の特徴量のうちでどれが重要なのかを調査する必要がある。結果解釈は、この重要度調査に役立つ。
Directing Future Data Collection
分析の過程で、外部データを利用する機会がでてくるかもしれない。結果を解釈していると、現在のモデルに対して、その外部データが有効かどうかを判断するのに役立つ。
Informing Human Decision-Making
意思決定を行うのは人間である。意思決定のためには、モデルに対する洞察が必要である。
Building Trust
多くの人々が、重要な決定が必要な場面で、理解なくモデルを信頼できるとは思っていない。これは予測のエラーを考えると合理的な判断である。問題にあった結果の洞察を示すことは、データサイエンスの理解を持たない人々と信頼関係を築くことにも役立つ。
Permutation Importance
どんなことに役立つか?
どの特徴量が最も重要かを解釈するときに役立つ。
どんな手法か?
モデル生成後、評価データを評価するときに、ある一つの特徴量のレコードの順序をシャッフルする。このとき他の特徴量やターゲットの順序は変更しない。
順序変更後の精度が、順序変更前の精度に比べて下がっていれば、これは現実で起こり得るデータとはかけ離れているということになる。
モデルの精度に影響を与えている特徴量ほど、順序入れ替えによる影響が強く見られる。
これを全特徴量に対して行うことで、各特徴量の重要度を測ることができる。
実行例
sklearnやxgboost, lightgbm等をサポートしている。前記事で書いたLIMEを使ってテキスト分類器にも使えるらしい。
ここでは、FIFAでどのチームがMan of the Match(試合中で最も活躍した選手に送られる賞)を獲得できるか を、ランダムフォレストの分類器で予測してみる。
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
DATA = '../data/FIFA 2018 Statistics.csv'
data = pd.read_csv(DATA)
y = (data['Man of the Match'] == "Yes") # Convert from string "Yes"/"No" to binary
feature_names = [i for i in data.columns if data[i].dtype in [np.int64]]
X = data[feature_names]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)
rfc = RandomForestClassifier(random_state=0).fit(train_X, train_y)
import eli5
from eli5.sklearn import PermutationImportance
# permutation importance
perm = PermutationImportance(rfc, random_state=0).fit(X_test, y_test)
eli5.show_weights(perm, feature_names=X_test.columns.tolist())
RandomForestで分類器をつくり、分類器と評価データをELI5のPermutationImportanceに与えている。
得られるのが上図。
Weightが分類器のパフォーマンス(ここではAccuracy)がどの程度低下したかを、Featureが特徴量の名前を表している。Weightが負の値だと、分類器のパフォーマンスが向上したということ。
ここでは、Goal Scoredの順序を入れ替えたときに、最もパフォーマンスの低下が見られた。逆に、Ball Possession % を入れ替えたときに、最もパフォーマンスの向上が見られた。
これはつまり、Goal Scoredの重要度が最も高く、Ball Possession %の重要度が最も低い(またはノイズ)と考えられる。ただし特に後者に関して、データのシャッフルはランダムに行われるので、小さいデータセットの場合にはランダム性の影響が現れている可能性がもある。
Partial Plot
どんなことに役立つか?
各特徴量が、ターゲットの予測にどの程度影響するかを解釈するのに役立つ。
どんな手法か?
ある特徴量の重要度を調べたいとする。ある個別のサンプルに注目して、その特徴量の値だけを変化させる。このとき他の特徴量の値は変化させない。
このとき、ターゲットの変化量が大きいほど、ターゲットの予測により強い影響を与える特徴量だと考えることができる。
実行例
今はsklearnのアルゴリズムのにサポートしている。
Permutation Importanceのときと同じく、FIFAデータを使ってMan of the Matchを獲得できるかどうかを予測する。まずは決定木で分類器を使ってモデルを生成する。
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
DATA = '../data/FIFA 2018 Statistics.csv'
SEED = 1
data = pd.read_csv(DATA)
feats = [i for i in data.columns if data[i].dtype in [np.int64]]
X = data[feats]
y = (data['Man of the Match'] == "Yes")
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=SEED)
dtc = DecisionTreeClassifier(
random_state=SEED, max_depth=5, min_samples_split=5).fit(X_train, y_train)
### decision tree
### decision tree
from sklearn import tree
import graphviz
def convert_decision_tree_to_ipython_image(clf,
feature_names=None,
class_names=None,
image_filename=None,
tmp_dir=None):
""" plotting and saving png file decision tree """
dot_filename = os.path.basename(image_filename).split(".")[0] + ".dot"
tree.export_graphviz(
clf,
out_file=dot_filename,
feature_names=feature_names,
class_names=class_names,
filled=True,
rounded=True,
node_ids=True,
special_characters=False)
from IPython.display import Image
image_filename = image_filename or ('%s.png' % dot_filename)
subprocess.call(
('dot -Tpng -o %s %s' % (image_filename, dot_filename)).split(' '))
image = Image(filename=image_filename)
return image
convert_decision_tree_to_ipython_image(dtc, feature_names=X_train.columns, image_filename='dtc.png')
次に、PDPboxを使って、Goal Scoredの値を変化させたときのターゲットの変化を見てみる。
# PDPbox
from matplotlib import pyplot as plt
from pdpbox import pdp, get_dataset, info_plots
# Change "Goal Scored" and plot
pdp_goals = pdp.pdp_isolate(
model=dtc, dataset=X_test, model_features=feats, feature='Goal Scored')
pdp.pdp_plot(pdp_goals, 'Goal Scored')
plt.show()
x軸がGoal Scoredの値、y軸がターゲットの値、薄い青の区間はターゲットの信頼区間を表す。●はプロット点。
特徴量の値を、どんな範囲で、何回変化させるかというのは、pdp_isolate
のオプションで指定できる。デフォルトだと10回、パーセンタイルで変化させる設定になっている。
これを見ると、Goal Scoredが~0.9、つまり1回ゴールしたかどうかでターゲットの値に差が見られる。しかしGoal Scoredの値がそれ以上大きくなっても、ターゲットには影響しないことがわかる。
次に、別の特徴量の影響を見てみる。
feature_to_plot = 'Distance Covered (Kms)'
pdp_dist = pdp.pdp_isolate(
model=dtc, dataset=X_test, model_features=feats, feature=feature_to_plot)
pdp.pdp_plot(pdp_dist, feature_to_plot)
plt.show()
ここでは、 Distance Covered (Kms) の値を変化させている。
Distance Covered (Kms) の値が~101になると、ターゲットの値が変化する。しかしターゲットの変化量は、Goal Scoredを変化させたときのターゲットの変化量に比べて小さい。このことから、よりDistance Coverd (Kms)の影響よりも、Goal Scoredの影響が大きいと考えられる。
ここまでは決定木の分類器を使ってきたが、ランダムフォレストの分類器にするとどうなるかを見てみる。
### random forest
rfc = RandomForestClassifier(random_state=SEED).fit(X_train, y_train)
pdp_dist = pdp.pdp_isolate(
model=rfc, dataset=X_test, model_features=feats, feature=feature_to_plot)
pdp.pdp_plot(pdp_dist, feature_to_plot)
plt.show()
同じくDistance Covered (Kms)の値を変化させたときの結果だが、決定木のときとは大きく変わっている。
Distance Covered (Kms)が~100のときにターゲットの値が最も大きく、Distance Covered (Kms)の値が大きくなるにつれてターゲットの値は小さくなっていることがわかる。
ここまで、単一の特徴量のみを変化させてきたが、2つの特徴量を変化させたときの影響も見ることができる。
Goal Scored と Distance Covered (Kms) を変化させてみる。
ここでは決定木の分類器を使う。
feature_to_plot = ['Goal Scored', 'Distance Covered (Kms)']
inter = pdp.pdp_interact(
model=dtc, dataset=X_test, model_features=feats, features=feature_to_plot)
pdp.pdp_interact_plot(
pdp_interact_out=inter, feature_names=feature_to_plot, plot_type='contour')
plt.show()
x軸が Goal Scored、y軸がDistance Covered (Kms)、カラーバーがターゲットの値を表している。
この場合だと、少なくとも1ゴールし、かつ走った距離が100km程度を達成した場合に、最もMan of the Matchを獲得する確率が高い。
これは、決定木を可視化した結果からは読み取ることができない。
SHAP Values
どんなことに役立つか?
特徴量がある個別のデータの予測に対してどのように寄与するかを解釈するのに役立つ。
どんな手法か?
SHAPというライブラリを使う。
各特徴量が、個別のサンプルの予測にどの程度寄与しているかを測る。
そもそもSHAPとは、A Unified Approach to Interpreting Model Predictionsで提案された手法。
あらゆる機械学習モデルを解釈する手法のひとつで、前のLIMEなんかもこれで統一化できるらしい。ゲーム理論のshapley valueに基づいている。
論文の内容についてはこの資料を読んだ。もっと噛み砕いた内容だとInterpreting complex models with SHAP valuesがわかりやすいと思った。
端的に言うと、予測値$p$が与えられたときの、ある特徴量$i$のShapley valueを求めて、これを寄与度とする。
\phi_{i}(p)=\sum_{S \supseteq N/i}\frac{|S|!(n-|S|-1)!}{n!} (p(S \cup i)-p(S))
$\phi_{i}(p)$は予測値$p$が与えられたときのある特徴量$i$のShapley value、$S$は着目している特徴量、$n$は全特徴量の数、$p(S \cup i)$は着目している特徴量が与えられているときの予測値の平均、$p(S)$は着目している特徴量が与えられなかったときの予測値を表す。
さらにこれをもっと単純に言い換えると、
Importance_{i} = p_{i}-p'_{i}
となる。つまり、特徴量$i$の重要度$Importance_{i}$は、特徴量$i$が与えられているときの予測値$p_{i}$と、与えられなかったときの予測値$p'_{i}$の差だと言える。
実行例
同じくFIFAのデータを使う。ランダムフォレストの分類器を、SHAPに与えて、特徴量の寄与を求めている。!
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
SEED = 1
data = pd.read_csv('../data/FIFA 2018 Statistics.csv')
feats = data.select_dtypes(include=[np.int64]).columns
X = data[feats]
y = (data['Man of the Match']=='Yes')
X_train, X_test, y_train, y_test = train_test_split(X, y , random_state=SEED)
rfc = RandomForestClassifier(random_state=SEED).fit(X_train, y_train)
import shap
### Shapley value
row = 5
data_for_prediction = X_test.iloc[row]
data_for_prediction_array = data_for_prediction.values.reshape(1, -1)
explainer = shap.TreeExplainer(rfc)
shap_vals = explainer.shap_values(data_for_prediction)
shap.initjs()
shap.force_plot(explainer.expected_value[1], shap_vals[1], data_for_prediction)
個別のサンプルに対してShapley valueを求めた結果を可視化している。
output value
は得られた予測値、base value
は全特徴量が与えられなかったときの予測値(つまり単に平均をとった値と思われる)、赤いバーが予測値に対して正に寄与した特徴量、青いバーが予測値に対して負に寄与した特徴量を表している。
まとめ
機械学習モデルと予測結果を解釈する手法として、
- どの特徴量が最も重要か:Permutation Importance
- 特徴量が全体の予測結果にどの程度影響を与えるか:Partial Plot
- 特徴量がある個別のデータの予測に対してどのように寄与するか:SHAP value
を実行した。
結局解釈するのは人なので、自分自身も誤った解釈をしないためにもう少し理論を学ばないといけないし、説明するときにも誤解を与えないようにしないといけないが、これらの手法はその手段としてかなり有用ではないかと思った。どんどん活用していきたいお気持ち。
特にSHAPのところとか理解が怪しいので、間違っていたらコメント等でご指摘ください