• トップ
  • ブログ一覧
  • 【機械学習】SHAPを用いた木構造モデルの解釈
  • 【機械学習】SHAPを用いた木構造モデルの解釈

    広告メディア事業部広告メディア事業部
    2020.03.03

    IT技術

    SHAPを用いた木構造モデルの解釈

    機械学習モデルの解釈性については、しばしば問題になります。

    「モデルの精度が高いが、なぜモデルが、その予測を行ったのかを説明できず、実用に至れなかった…。」

    といった事を、多くの方が経験しているのではないでしょうか?

    今回は、複雑なモデルの解釈を行うための手法「SHAP(SHapley Additive exPlanations)」を紹介します。

    そして、実際に「木構造モデル」に適用したいと思います。

    この記事で紹介すること

    1. 複雑なモデルにおいて、各特徴量の寄与の解釈を行うモデル「SHAP」を解説。
    2. 「SHAP」を、実際に「木構造モデル」に適用してみた結果を、特徴量重要度の指標と比較する。

    モデルの解釈性について

    まず、機械学習モデルの解釈性について説明します。

    一般的に、「モデルの複雑性」と「解釈性」は、トレードオフの関係にあります。

    例えば、

    「重回帰モデル」のような、単純なモデルにおいては、各特徴量の寄与を解釈することは簡単です。

    「ニューラルネットワーク」のような、複雑なモデルでは、特徴量間の複雑な関係性を考慮できる反面、各特徴量が「どのようにモデルの予測に寄与しているか?」を解釈することは、非常に難しいです。

    この性質は、実務に機械学習モデルを適用する際に、しばしば問題になります。

    ローン可否の判定を行うモデルを作成する場合

    例えば、銀行の「ローン可否の判定を行うモデル」を作成する場合で考えてみましょう。

    銀行には、判定結果の根拠を説明する必要性があります。

    この際、解釈性の低いモデルでは、説明ができないという自体に陥ってしまいます。

    その解決策として、解釈性の高い、単純なモデルを利用することが考えられます。

    ですが、単純なモデルでは、高い精度を達成できないことが多いのです。

    そこで、複雑なモデルの解釈を行うために考案されたのが、「SHapley Additive exPlanations(SHAP)」[※1]です。

    [※1]【参考文献】
    S. M. Lundberg and S. Lee. A unified approach to interpreting model predictions. In I. Guyon, U. V. Luxburg, S. Bengio, H. Wallach, R. Fergus, S. Vishwanathan, and R. Garnett, editors, Advances in Neural Information Processing Systems 30, pp. 4765–4774. Curran Associates, Inc., 2017.

    では、具体的に SHAP とは、どのようなものなのでしょうか?

    SHAPとは?

    SHAP とは、学習済みモデルにおいて、「それぞれの特徴量」が「そのモデルの予測値」に「どのような影響を与えたか」を算出するモデルです。

    SHAPにより、ある特徴量の値の増減が、モデルの予測値にどう影響したかを可視化することが出来ます。

    SHAPは、「ある入力 x=[x1,,xp]x=[x1, …, xp] 」と、「学習済みモデル ƒƒ 」が与えられた時に、ƒƒ を各特徴量の寄与が説明しやすい「簡単なモデル gg 」で近似します。

    この「簡単なモデル gg 」は、次の式によって定式化されます。

    g(z)=ϕ0+i=1pϕizig(z) = \phi_0 + \sum_{i=1}^p{\phi_i z_i}

    ここで、zi(i=1,,p)z_i (i=1,…,p) は、「各入力 xix_i 」を単純化したものを表します。

    具体的には、ziz_i は、「特徴量 ii 」が「モデル ƒƒ 」の予測に使用された場合は「1」、されなかった場合は「0」を取ります

    複雑なモデルを、簡単なモデルで近似するためには、gg(もといϕ\phi)は、いくつかの性質を満たす必要があります。

    Local accuracy

    Local accuracy は、「入力 xx 」に対して、「学習済みモデル ƒƒ 」を適用した ƒ(x)ƒ(x) と、「入力 zz 」に対して、「モデル g(z)g(z) 」を適用した g(z)g(z) は、一致しなければならないという性質です。

    この性質は、次の式によって定式化されます。

    f(x)=g(z)=ϕ0+i=1pϕizif(x) = g(z) = \phi_0 + \sum_{i=1}^p{\phi_i z_i}

    Missingness

    Missingness は、モデルの予測に使用されなかった特徴量の寄与は「0」となる、という性質です。

    すなわち、zi=0z_i = 0 の特徴量については、ϕi=0\phi_i = 0 となります。

    Consistency

    Consistency は、「異なるモデル g(z)g'(z)g(z)g(z) 」において、「ある特徴量 ii 」を、除いた際の影響の大小の関係性と、寄与 ϕi(g,x)ϕi(g,x)\phi_i(g',x)、\phi_i(g,x) の大小の関係性を保つという性質です。

    この性質は、すべての i=1,...,pi=1,...,p について、

    g(z)g(zi)g(z)g(zi)g'(z) - g'(z \setminus i) \ge g(z) - g(z \setminus i)

    であるときに、

    ϕi(g,x)ϕi(g,x)\phi_i '(g',x) \ge \phi_i(g,x)

    となれば成立します。

    なおB\Aは、集合Bから、集合Aを引いた差集合を表します。

    Shapley値と等価

    詳細は省きますが、これらの性質を満たす ϕ\phi は、次の式によって、一意に定まる事が知られています。

    ϕi(f,x)=zxz!(pz1)!p![f(z)f(zi)]\phi_i(f,x) = \sum_{z\subseteq x}\frac{|z|!(p - |z| - 1)!}{p!}[f(z) - f(z\setminus i)]

    なお、この値は、ゲーム理論の分野において、「Shapley値」と呼ばれるものと等価です。

    Shapley値は、複数プレイヤーがいるゲームで、プレイヤー同士が連携した際に各プレイヤーの寄与を表すものです。

    つまり SHAP は、学習済みモデルにおいて、各特徴量をプレイヤーとした際の、Shapley値を求めていることになります。

    実際に SHAP を、複雑なモデルの一つである「木構造モデル」に適用していきたいと思います!

    木構造モデルとは?

    木構造モデルとは、その名の通り、木の構造をしている機械学習モデルです。

    これは、「決定木」または、決定木を弱学習器としてアンサンブル学習を行う、「ランダムフォレスト」や「勾配ブースティング木」のことを指します。

    決定木とは?

    まず、基礎となる「決定木」について簡単に説明します。

    決定木とは、特徴量の値を元に、データを次々に分割していき、段階的に予測を行うモデルです。

    決定木は、単純なモデルであるため、単一で用いる場合には、解釈性が高いです。

    一方、決定木を組み合わせる手法の「ランダムフォレスト」や「勾配ブースティング木」は、表データなどの構造化データにおいて、学習が速い上に高い精度を達成できることが知られています。

    ただ、解釈性は損なわれてしまいます。

    関連記事

    featureImg2019.08.08トライ木で高速な辞書を作ってみた!トライ木で高速な辞書を作りたい!トライ木(英: trie)というデータ構造を知ってますか?トライ木はテキストを扱う際に...

    featureImg2019.10.01トライ木をビットスライスとパトリシア木でコンパクトにしてみた!「ビットスライス」「パトリシア木」を用いて、トライ木の問題点を解決したい!前回の記事では、トライ木というデータ構造を紹...

    木構造モデルにおける特徴量重要度

    木構造モデルにおいて、各特徴量の重要度を表す指標が、大きく2種類あります。

    1. その特徴量が分割に登場した回数
    2. その特徴量を分割に使用する事で増加した、情報利得(分割後のデータのまとまり具合のようなもの)の値

    これらの指標は、しばしば「各特徴量の寄与を表す物」のように扱われています。

    ですが、実は Consistency を満たしていない事が知られています。

    そのため、特徴量重要度をそのまま、そのモデルの解釈として捉えることは危険です。

    また、これらの指標は、各特徴量の重要度と思われるものを算出しているに過ぎないため、モデルの予測がどのように行われたかを解釈することは困難です。

    SHAPでモデルの予測がどのように行われたか解釈する

    この問題点を解決することができるのが、SHAPです。

    SHAP を用いることで、「モデルの予測がどのように行われたのか?」を解釈することが可能になります。

    また、特徴量選択に有効と考えられている「Permutation Importance」は、SHAPと関連があります。

    Permutation Importanceとは?

    Permutation Importance は、学習済みモデルにおいて各特徴量の重要度を測るための手法です。

    SHAP 同様、学習済みモデルを用いるため、検証データにも適用することができ、汎用的な観点で、特徴量重要度を計測することができます

    【具体的には?】
    学習済みモデルを用いて、検証データを予測する際に、ある特徴量の値をシャッフルします。
    そして、シャッフルする以前と精度を比較し、その変化分を、シャッフルした特徴量の重要度として扱います。

    精度向上に効いている特徴量を見つけるのに有効

    Permutation Importance は、精度向上に効いている特徴量を見つける方法としては有効です。

    ですが、SHAP のように「予測がどのように成されたか?」を解釈する手法として用いることはできません。

    以下の実装例では、「SHAPの結果」と、「木構造モデルにおける特徴量重要度」、「Permutation Importance」を比較します。

    木構造モデルへの実装例

    いよいよ、木構造モデルに実装していきたいと思います!

    今回は、「scikit-learn」にデフォルトで整備されている、ワインのデータセットを例に実装を行います。

    このデータセットは、3種類のワインの分類のため、178のワインについて、13個の特徴量が記載されているものです。

    なお、用いる機械学習モデルは、勾配ブースティング木の実装の一つである「lightGBM」です。

    実行環境

    今回の実行環境はコチラ!

    osmacOS Catalina 10.15.2
    python3.7.4
    numpy1.17.2
    scikit-learn0.22.1
    lightgbm2.3.1
    shap0.30.0

    実装準備

    実装と言っても、SHAP のライブラリは、論文の著者により、Pythonのライブラリ化されていますので、そちらを利用していきます。

    【SHAPのライブラリ:Pythonのライブラリ化】
    https://github.com/slundberg/shap

    pipコマンドが使える方は、

    1pip install shap

    をターミナルで入力するのみです。

    実装

    それでは、実装をしていきましょう!

    まずは、必要なライブラリをインポートしていきます。

    1# 基礎ライブラリ
    2import numpy as np
    3import pandas as pd
    4import matplotlib.pyplot as plt
    5
    6# 学習モデル
    7import lightgbm
    8
    9# データセット
    10from sklearn.datasets import load_wine
    11
    12# SHAP
    13import shap
    14
    15# Permutation Importance
    16from sklearn.inspection import permutation_importance
    17
    18# その他
    19from sklearn.metrics import accuracy_score, make_scorer
    20from sklearn.model_selection import train_test_split

    random_stateなどの設定

    続いて、random_state などの設定をしておきます。

    それぞれで設定すると、コードが汚くなるので、最初に一括で設定します。

    1# 変数の設定
    2random_state = 42
    3train_test_size = 0.33

    データ読み込みと、データと検証データの分割

    次に、データを読み込んで行きます。

    今回扱うデータは、scikit-learn にデフォルトで含まれているデータなので、load_wine で読み込んでいます。

    「訓練」、また「データ」と「検証データ」の分割も、ここでやってしまいます。

    1# データの読み込み
    2wine = load_wine()
    3
    4X = wine.data
    5y = wine.target
    6
    7X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=train_test_size, random_state=random_state)

    ワインの13種類の特徴量

    目的変数は「ワインの品種」で、13種類の特徴量の詳細は、以下のようになっております。

    alcoholアルコール濃度
    malic_acidリンゴ酸
    ash
    alcalinity_of_ash灰のアルカリ性
    magnesiumマグネシウム含有量
    total_phenols総フェノール類含有量
    flavanoidsフラボノイド含有量
    nonflavanoid_phenols非フラボノイドフェノール類含有量
    proanthocyaninsプロアントシアニジン含有量
    color_intensity色の強さ
    hue色合い
    od280/od315_of_diluted_wines希釈度合い
    prolineプロリン含有量

    モデルの構築

    本来は、データをもっと細かく見ていかなければいけませんが、今回は早速モデルの構築に移っていきます。

    1# モデルの学習
    2lgbm = lightgbm.LGBMClassifier(random_state=random_state)
    3lgbm.fit(y=y_train, X=X_train, feature_name=wine.feature_names)

    精度の算出

    一応、精度を算出しておきます。

    1# 精度の算出
    2# 0.966
    3accuracy_score(lgbm.predict(X=X_test),  y_test)

    0.966」という結果に、非常に高いですね。

    SHAPの算出

    では、SHAP の算出を行っていきます。

    まずは、可視化の準備のために「JS visualization」をロードします。

    よくわからないかもしれませんが、とりあえず実行しておけば問題ありません。

    1# 可視化の準備
    2shap.initjs()

    学習済みモデルを用いて、SHAP の算出を行います。

    ここでは、検証データについて SHAP を算出していますが、もちろん学習データについて SHAP を算出することもできます。

    1# SHAPの算出
    2explainer = shap.TreeExplainer(lgbm)
    3shap_values = explainer.shap_values(X_test)

    SHAP値の可視化

    単純な SHAP値の可視化は、次のコードで行うことができます。

    1# SHAP値の可視化
    2shap.summary_plot(shap_values, X, plot_type="bar", feature_names=wine.feature_names)

    結果

    結果は以下になります。

    「flavanoids」が、一番寄与の高い特徴量であることがわかりますね。

    その他の特徴量重要度も可視化

    その他の特徴量重要度も、可視化をしていきます。

    1# 分割に登場した回数を元に特徴量重要度を算出
    2lightgbm.plot_importance(lgbm,importance_type='split', figsize=(5,9))
    3plt.show()
    1# 情報利得を元に特徴量重要度を算出
    2lightgbm.plot_importance(lgbm,importance_type='gain', figsize=(5,9))
    3plt.show()
    1# permutation importanceを算出
    2result = permutation_importance(lgbm, X_test, y_test, scoring=make_scorer(accuracy_score), n_repeats=100, random_state=random_state)
    3
    4pd.Series(result['importances_mean'], index=wine.feature_names).sort_values().plot.barh(figsize=(5,9))
    5plt.ylabel('Features')
    6plt.xlabel('Permutation importance')
    7plt.vlines(x=0, ymin=-100, ymax=100, linewidth=0.6)
    8plt.grid(alpha=0.7)
    9plt.show()

    それぞれの、上位3項目は同じですが、順番が異なっています

    SHAP では、「どのクラスに対する寄与が大きかったのか」まで判別することができます。

    また、SHAPと比較して、「情報利得を元にした特徴量重要度」と「Permutation Importance」は、重要と判断している特徴量が似ています

    これは、SHAPが特徴量重要度の指標として優れていることを示しています。

    hueについて

    一方で、「hue」についてですが、SHAP値が高いものの、「Permutation Importanceの結果から抜いた方が良い」という結果がでています。

    実際に、hueを抜いて学習を行った結果、精度は「0.983」まで上昇しました。

    これは、「SHAP はあくまで解釈性に特化している」ことを意味しています。

    SHAPでどのように予測をしたか可視化する

    SHAP は、実際に各データにおいて、どのようにして予測を行ったかを可視化することができます。

    1# 各データの予測に対する各特徴量の寄与
    2i = 0
    3display(shap.force_plot(explainer.expected_value[0], shap_values[0][i], X_test[i], feature_names=wine.feature_names))
    4display(shap.force_plot(explainer.expected_value[1], shap_values[1][i], X_test[i], feature_names=wine.feature_names))
    5display(shap.force_plot(explainer.expected_value[2], shap_values[2][i], X_test[i], feature_names=wine.feature_names))


    (クリックすると別ウィンドウで画像を開きます)

    画像の1段目が「クラス0」、2段目が「クラス1」、3段目が「クラス2」の予測値に、それぞれ対応しています。

    このデータでは、「0」と予測していることがわかりますね。

    このようにSHAPでは、学習済みモデルがどのように予測を行ったかを解釈することができます

    さいごに

    いかがだったでしょうか。

    今回は、たびたび問題になる「複雑なモデルにおける解釈性」を解決するための手法として、SHAP を紹介しました。

    SHAP は、木構造アルゴリズムだけではなく、ニューラルネットワークにも適用できます。

    是非、一度試してみて下さい。

    こちらの記事もオススメ!

    featureImg2020.07.28機械学習 特集知識編人工知能・機械学習でよく使われるワード徹底まとめ!機械学習の元祖「パーセプトロン」とは?【人工知能】ニューラルネ...

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事