機械学習モデルによる予測結果の解釈(Shap値の使い方)
なぜ予測結果の解釈が必要か
機械学習というか広義のAIは、予測や認識を高い精度で実施することは得意です。
しかし、その演算過程がブラックボックスになりがちで、人間がその結果の背景にある根拠や判断理由を理解することが困難という課題が存在します。
いくら予測精度が高くても、人間がその結果をどう解釈し、どう次のアクションにつなげていけば良いかがわからなければ、人間がAIを使いこなすことはできないでしょう。
Shap値を使えば何がわかるか
そもそも機械学習モデルとは、目的変数を特徴量(学習の入力に使う測定可能な特性)データで予測することで成り立っています。
そのため特徴量が予測結果に大きな影響を与えるはずで、どの特徴量がどのように結果へ影響したかを示すことは、極めて重要です。
ちなみにXGBoostやlightGBMにもFeature Importanceという特徴量の算出ができるメソッドが備わっており、どの特徴量がモデルの予測結果へ寄与しているかを出力することはできるのですが、効いている方向がプラスなのかマイナスなのかがわからないという課題があります。
今回取り扱うShap(SHapley Additive exPlanationsの略)値とは、まさに各特徴量が予測結果にどのような影響を与えたかを、正負の方向を含めて、定量的に示すものを指します。*1*2
Shap値計算の実行例
pipでインストールしたら、ライブラリを読み込むことで使用可能です。
import shap # Jupyterで表示させるために、JSを読み込む shap.initjs()
まずは必要なデータセットを読み込み、XGBoost, LightGBM, CatBoost等の勾配ブースティングモデルで学習させます。
次にTreeExplainerという勾配ブースティング用のインスタンスを使って学習したモデルを読み込み、特徴量データからShap値を計算します。
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100) explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X)
特徴量の貢献度を可視化
用いるデータセットは面白そうなものであれば何でも良いのですが、ここではKaggleで公開されている以下の2018 FIFAワールドカップ ロシア大会のデータセットを使います。
www.kaggle.com
一試合における選手のプレーデータ(得点数、ボール保持率、反則の数等)から、その選手が試合の最優秀選手となったかどうかを予測(結果を0〜1のレンジで提示)するものです。
summary_plot
まずはsummary_plotを使って特徴量全体の寄与度を示します。
shap.summary_plot(shap_values, X)
上から貢献度の高い順に並んでおり、Goal Scored(得点ゴール数)、Off-Target(ゴールから外れたシュートの数)、Corners(コーナーキックの数)がトップ3です。
横軸はShap値と目的変数への貢献度です。
少し複雑ですが、赤色、青色で特徴量の大小が表現されており、例えばGoal Scoredで見ると、赤色は得点が多いことを示しており、赤色の分布がShap値プラス(右)寄りとなっているため、目的変数と正の相関(得点数が多いと最優秀選手になりやすい)があると言えます。
反対に、青色は得点が少ないことを示しており、青色の分布がShap値マイナス(左)寄りとなっているため、目的変数とは負の相関(得点が少ないと最優秀選手になりにくい)があります。
force_plot
force_plotを使うと、サンプルごとの特徴量の貢献度を可視化することができるので、先程計算したShap値から一番はじめのサンプルを可視化すると以下のようになります。
shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:])
スコアは0.76であり、正解ラベル最優秀選手のサンプルですが、得点数=5、コーナーキック=6というのがスコアの押し上げに効いているのがとても良くわかる例と言えます。
次は全データにforce_plotを適用した例です。
shap.force_plot(explainer.expected_value, shap_values, X)
左から右に全128サンプルを予測スコアの高い順に並べ替えた結果が以下の図になります。
予測スコア結果に対して、特にGoal Scoredの寄与度が大きいことが、その面積の大きさに表されていると言えます。
なお、赤色と青色のちょうど狭間に引かれている白い線が、予測スコアに該当します。
左端から70番目くらいまでのサンプル(スコアが0.5〜0.8)は赤いGoal Scored部分の面積が目立っており、得点が高いため予測スコアが高くなっている選手が多いと推測できます。
一方、70番目から128番目までのサンプル(スコアが0.2〜0.5)は青いGoal Scored部分の面積が目立っており、先ほどとは反対に、得点が低いことが予測スコアに影響していると考えられます。
まとめ
このように目的変数と特徴量との相関関係を可視化して明らかにすることで、機械学習モデルに対する理解を一層深めることができます。
解釈結果を新たな特徴量づくりに活かして、さらなるモデル精度向上を狙うこともできますし、ビジネスシーンでは、何かしらの打ち手につながるきっかけにすることもできます。
*1:ShapのベースとなったLIMEも同様
*2:今回Shapについて主に参考にしたのは次の4記事。 Shapを用いた機械学習モデルの解釈説明 - Qiita 機械学習モデルを解釈する指標SHAPについて – 戦略コンサルで働くデータサイエンティストのブログ SHAPでモデルの予測結果を説明する | orizuru 続・機械学習モデルを解釈する方法 SHAP value - 子供の落書き帳 Renaissance
機械学習に関する、読んだ/読みかけの論文集
Deep Learningに関する無料のオンライン学習コンテンツ、fast.aiを受講して以来、理解ができるかどうかはさておき、重要な論文にも目を通すよう心がけています。
学生時代の専攻が化学系だった自分にとって、馴染みの薄いアルゴリズムの部分等は正直読み解くのが辛いですが、少しでもエッセンスを汲み取ることを意識しています。
論文の検索&管理に使っているのは、arXiv(最近「アーカイブ」と読むことを知った)とMendeley(こちらは「メンデレー」)の組み合わせ。
学生の頃の研究分野では、論文誌を購読していなければ論文の中身を読むことができなかったため、このように無料で大量の論文にいつでもどこでもアクセスできるというのは非常に便利です。
あとは、以下の記事を参考にして読むべき論文を探したり(更新が滞っているので、2016年以前の論文ばかりですが)。
github.com
ということで今回は、これまでに読んだ、あるいは読みかけの論文を通じて学んだことを記してみたいと思います。*1
画像系(CNN)
- ImageNet classification with deep convolutional neural networks (2012), A. Krizhevsky et al.
- 2012年のImageNet ILSVRCコンペで圧勝した、AlexNetに関する論文。
- 活性化関数にReLU (Rectified Linear Unit) を用いたり、GPUを用いた演算処理を実施したり、当時としては画期的な技術が導入された。
- 今日のAI、Deep Learningブームにつながるきっかけを作ったことから、非常に重要な論文と言える。
- Very deep convolutional networks for large-scale image recognition (2014), K. Simonyan and A. Zisserman
- AlexNetをより深くし改良されたVGG16、VGG19に関する論文。なお、VGGはオックスフォード大学の研究チーム、Visual Geometry Groupの頭文字。
- 2014年のILSVRCにおいて2位という好成績をおさめた。1位はGoogLeNetであったが、モデルのアーキテクチャが比較的シンプルなので、VGGのほうがとっつきやすい印象。
- レイヤーの数によってモデルの名称も使い分ける。16層(畳み込み13 + 全結合3)ならVGG16、19層(畳み込み16 + 全結合3)ならVGG19、といった具合。
- 初期のfast.ai(2016年)では、KerasでVGG16を使ったモデルがベースケースとして用いられていた。
- Deep residual learning for image recognition (2016), K. He et al.
- Microsoftの研究チームによる、ResNetに関する論文。
- 先のVGGと同様に、レイヤーの数によってResNet34、ResNet50と、モデルの呼称が異なる。
画像系(GAN)
- Generative adversarial nets (2014), I. Goodfellow et al.
- GAN (Generative Adversarial Networks、敵対的生成ネットワークと訳される) について書かれた論文。
- 有名なDeep Learning本*2の著者でもあるGoodfellowが本論文の著者。
- generatorとdiscriminatorは、犯罪者とそれを取り締まる警察に例えられることが多い。
- 犯罪者は本物に近い偽札を作り、警察がそれを見分ける、といったことが繰り返され、モデルの学習が進んでいく。
- Unsupervised representation learning with deep convolutional generative adversarial networks (2015), A. Radford et al.
- GANの登場から約1年後に発表された、DCGAN (Deep Convolutional GAN) に関する論文。
- なお、この論文に限らずGANには多数の派生系が存在する*3。
- DCGANの特徴は、画像分類で用いられてきた畳み込み層を取り入れたこと。
- Batch Normalization(各層でのデータ分布を正規化する手法)をgeneratorおよびdiscriminatorに適用したり、活性化関数にReLUを用いたりと、CNNの手法を応用していることが論文内で強調されている。
- 結果として高解像度の画像をうまく扱うことに成功しており、本物と見分けの付かないレベルのベッドルーム画像を生成している。
余談
GANの技術は扱っていて面白いなと思うものの、私の発想力が乏しいがために、ビジネスでの活用機会があまり思い浮かびません。
芸術やデザイン、エンターテインメントの分野では重宝されるのでしょうか。
例えば、ラフスケッチさえ描けばGAN技術が高精度な画像に仕上げてくれるため、漫画家やアニメーターの作業が大幅に軽減される、とか、
損傷した芸術作品の復旧が容易になる、とか。
もう少し勉強しよう。
*1:形から入るタイプなので、気になったものにはとりあえずブックマークし、pdfファイルをダウンロードしておいて、あとで読む(結局読まない)スタイルになりがちなのが玉に瑕。
*2: Deep Learning (Adaptive Computation and Machine Learning series)
機械学習モデルのBlendingに対する重みの最適化手法について
Kaggleなどの機械学習コンペにおいて、複数のモデルを平均等により組み合わせることで、新たな推論結果を作る手法(blendingと呼ばれる)があります。
平均を取る際にモデルの重み付けを行いますが、均等に振られることもあれば、軽重をつけることもあります。
(例えばモデルが2つであれば、前者は0.5ずつ、後者は0.7と0.3、といった具合です)
ここでは、複数のモデルに対する最適な重みを探す手法について、SciPyのminimize関数を使った例を取り上げたいと思います。
アンサンブル学習
Blendingはアンサンブル学習の一つであり、モデルがN個あれば、0〜1の値を持つN個の重みを各モデルの予測結果に掛け合わせて、最終的な予測結果を得ます。
アンサンブル学習には他にも、平均以外に多数決といった簡単な手法や、out-of-foldの予測結果を用いて高次元での学習をおこなうStackingと呼ばれる手法もあります。
英文ですが、以下の記事が有名なのでリンクを貼っておきます。
Kaggle Ensembling Guide | MLWave
日本語ですと、以下の記事でアンサンブル学習に関する紹介が少しなされています。 『Kaggle Ensembling Guide』はいいぞ【kaggle Advent Calendar 7日目】 - u++の備忘録
なお、Kaggle等の機械学習コンペでは、複数のKernelから結果(提出ファイル)だけを取ってblendしたものが高スコアkernelとして公開され、LBが掻き乱されることもしばしば生じます。
コンペ終盤でこうしたKernelが公開されると、それまでの努力が不意になってしまうこともあり、様々なコンペでその扱いについて議論がなされていることも、念のため触れておきます。
NO MORE BLENDING IN KERNALS PLEASE | Kaggle
Blendingに対する重みの最適化事例
Blendingにおける最適な重みを探すには、まず訓練データをK-Fold等の交差検証(Cross Validation)で学習させ、out-of-foldの予測結果を得る必要があります。
次にout-of-foldの予測結果を説明変数とし、目的変数に対して学習をさせて、最適な重みを探します。
具体的な最適化手法として、Kaggleでしばしば登場する、SciPyのoptimize.minimize関数を使った手法を取り上げます。
今更ながらJupyter Notebookファイルをはてなブログへ貼り付ける方法(Gistへ登録した上ではてなへ連携)を知ったので、早速活用してみます。
gistef1aac638e7dbc82bdcdc85abb208874
上記の例では評価指標にMSE(Mean Squared Error)を用いており、単一モデルではMSE = 0.33程度だったものが、5つのモデルを組み合わせることで、MSE = 0.27へと精度が向上する結果となり、Blendingの効果が見て取れます。
なお今回の実験には、適当なデータセットを用いておらず、乱数により発生させた、ランダムな説明変数(out-of-foldの予測結果を模擬)とランダムな目的変数を使っています。
一般的にBlendingは、モデル間の相関が高い場合には有効でなく、相関が低いものをたくさん組み合わせたほうが効果が出ると言われていますが、今回はこの点はあまり考慮せず、minimize関数の使い方にフォーカスしているのであしからず。
The Good, the Bad and the Blended | Kaggle
様々な最適化手法
最適化手法について、上記の例では、便宜上Kaggle Kernel上で使われていたSLSQPとL-BFGS-Bの2つを比較しました。
SciPyのドキュメントを見ると、minimizeのメソッドとして選択可能な最適化手法は他にもいくつか存在することがわかります。
ここでは、下記のブログで紹介されているものを引用する形で、簡単に触れておきます。 非線形最適化関数 — 機械学習の Python との出会い
- 勾配ベクトルやヘシアンが不要
- Nelder-Mead :Nelder-Mead法
- Powell :Powell法
- 勾配ベクトルのみが必要
- CG :共役勾配法 (conjugate gradient method)
- BFGS :BFGS法 (Broyden–Fletcher–Goldfarb–Shanno method)
- 勾配ベクトルとヘシアンの両方が必要
- パラメータの範囲に制約がある場合
- パラメータの範囲の制約に加えて,等式・不等式制約がある場合
- COBYLA :COBYLA法 (constrained optimization by linear approximation method)
- SLSQP :sequential least squares programming
まとめ
SciPyのoptimize.minimize関数を使い、Blendingで用いる重みを最適化する手法についてまとめました。
黒魔術的な要素が強い手法であり、コンペでは、つい他人の褌で相撲を取りたくなってしまいます。
既存のモデルを参考にする際には、少なくともモデルの再現とout-of-foldの予測をおこなった上で、Blendingを含む高次元のアンサンブル学習に取り組んでいきたいです。
Dive Into Deep Learning
UCバークレーの講義から派生したプロジェクトとして、「Dive Into Deep Learning(通称 D2L)」の執筆が進められているようです。
ちらっと覗いてみましたが、数式だけでなく、コードや図を使ってわかりやすいものに仕上げていくという姿勢が感じられます。
ベーシックなNNに始まり、CNN、RNNやAttentionといったモデルの解説、
更にはコンピュータービジョン(画像からピカチュウを検出するという興味深いタスクを含む)や自然言語解析といった応用例までがカバーされているので、
比較的スムーズに読み進められそうな印象を受けました。
ただ、使われているフレームワークがKerasやPyTorchではなくMXNetなので、読む人を選びそうな気もします。
(執筆者の方々がAWSの関係者のようなので、その成り立ちを考えると当然のことなのかもしれませんが)
日本語への翻訳活動も始まっているようです。
github.com
Jupyter NotebookからJupyterLabへの移行
Jupyter Notebookにはその前身のIpython Notebook時代からお世話になっています。
そのJupyter Notebookの後継と言われるJupyterLabを今更ながらインストールし、少し試してみました。
JupyterLabとはなんぞや
Jupyterの開発をおこなっているProject Jupyterが2018年2月にBlog記事を投稿し、一般ユーザー向けにJupyterLabを公開したことを発表しました。
当時、この記事を目にした記憶はあるのですが、
- どうせまだbeta版だし、ver1.0がリリースされたら使い始めよう
- 下手に環境をいじって他のパッケージが使えなくなったら困る
といった理由で導入を見送り、大々的にリリースされるのを楽しみに待っていました。
では、なぜ今なのか
上述の通り、首を長くしてJupyterLabの続報を待っていたのですが、待てど暮せどアップデートはなく、バージョンは0.35のまま。
そんな時、Variable Inspectorという拡張機能の使用例が紹介されている下記の記事を見つけ、これは使ってみたい!と思うようになりました。
後でも述べますが、変数や型といった、ノートブック上で扱っているデータに関する情報が常に一覧できる点に強い魅力を感じました。
使ってみた感想
IDEとしての使い勝手が向上
<見た目>
使ってみてまず驚いたのがレイアウトです。
ファイルエクスプローラーが画面の左側に備わっていること、開いているノートブックのタブとしてWebブラウザのタブとは別のものが設けられていること、等。
一点目について、Jupyter Notebookではファイルやフォルダの整理専用にファイル構成が見える画面をノートブックとは別のタブで開いておき、作業しているノートブックと行ったり来たりすることでファイルの管理をしていました。
しかしJupyterLabのファイルエクスプローラーによって、そのようなことをする手間や煩雑さが省かれ、作業効率が向上することが期待されます。
二点目について、Jupyter NotebookのレイアウトではWebブラウザで開いている他のコンテンツ(例えばGmailとかAmazonとか)とノートブックが混在する形となってしまい、
- タブ数がどんどん増えていくため見辛い
- 他のコンテンツの中に埋もれてしまったが最後、必要なノートブックを探し出すのが大変
といった課題があると感じていました。
これに対しJupyterLabでは、Webブラウザのタブと異なるタブが存在することで他のコンテンツと混在することはありませんし、上述したファイルエクスプローラーを使えばどのファイルを開いているのかも一目瞭然です。
敢えて使いづらさを述べるとすると、ctrl + tabキーで実行していたタブの切替操作ができなくなったことでしょうか。
JupyterLabのデフォルトでは、ctrl + shift + [(又は ] )でタブ切替ができるので、触っていたらそのうち慣れてくることでしょう。
慣れなければショートカットを編集して別のコマンドに変更してしまえばOKです。
<機能>
コードを書いてみてまず気づいたのが、補完機能です。
Jupyter Notebookでもtabを押すと候補が表示されていたので、その点はJupyterLabも同じです。
追加されたのは、Atomなんかにも備わっている、その項目が関数なのか変数なのか等を表示している機能で、個人的にはかなり見やすい印象を受けました。
また、pythonのメソッドや関数に関する解説を参照する際に使っていたshift + tabコマンドは健在で、Jupyter Notebookでは詳細なドキュメントを閲覧するのにこのショートカットを2回入力する必要があったのですが、JupyterLabでは一発で表示されるような仕様となっており、使い勝手が向上したと感じています。
拡張機能を導入するとより便利に
拡張機能はまだVariable Inspector以外試せていないので、今回触れるのはこの一点です。
インストールはコマンドラインを呼び出して以下を実行するだけで完了。
$ jupyter labextension install @lckr/jupyterlab_variableinspector
Variable Inspectorを作業しているノートブックと別タブで開き、ノートブックのドラッグ&ドロップ機能を使って作業ブックの右隣にでも配置しておけばOK。
RStudioを使っていると、現在扱っているデータのサイズや変数といった基本情報を同一画面上で確認することができるので、pythonのIDEでも似たようなことができたら便利なのに、と常々思っていました。
AnacondaやRodeoといったIDEでも似たようなことはできるのですが、個人的にはJupyter Notebookの手軽さに勝るものではなく、Variable Inspector的機能は諦めていました。
拡張機能としてJupyterLabに導入できればまさに鬼に金棒。
まとめ
JupyterLab、すごく便利です。
2018年中にリリースされる予定だったver1.0が未だリリースされていないのは気になりますが、気長に待ちつつ、使い倒していきたいと思います!
プログラミング教育ブームについて感じること
プログラミング教育ブームの到来
2020年度から小学校でのプログラミング教育が必修化されるといいます。
デパート等のおもちゃ売り場へ行けば、論理的思考力を鍛える多種多様な知育玩具が並んでおり、インターネット上では、関連ニュースや記事を見かけることが多くなりました。
「プログラミングは現代のそろばん」と銘打った寺子屋というか学習塾も非常に人気を博しているそうで、この流れに乗り遅れないようにと、熱の入った親御さんが多いことに感心しています。
また、史上最年少で将棋のプロ入りを果たした藤井聡太さんや、囲碁の世界では史上最年少の10才でプロ棋士と仲邑菫さん等、天才少年少女の登場も、論理的思考力を育むことへ人々が関心を寄せるきっかけをつくっているようにも感じます。
かくいう私はというと、ITリテラシーは現代社会を生き抜く上での必須スキルだと思っているし、子供の頃からどっぷりプログラミングに触れておけばよかったと後悔したりもするので、自分の子には幼いうちから各種デジタル技術に慣れ親しんで欲しいと思っています。
そうした意味では、早期から義務教育にプログラミングを取り入れるという文科省の方針は私の考えに沿っており、私の場合は4歳の長男には使わなくなったノートPCにEdubuntuをインストールしたものを与えることから始めていたりします。
(Edubuntuについては以下の記事を参照。今後このブログでもまとめてみるかもしれません。)
プログラミング教育に対する(ありがちな?)勘違い
因みに私も、つい最近まで勘違いしていたのですが、何も小学生がパソコンやタブレットに向かって特定のプログラミング言語の文法を学んだり、出されたお題に対してコーディングしていく訳ではないらしいです。
確かに、国語、算数から英語、音楽、体育まで、ただでさえいろんな科目を一人で子どもたちに教えなければならない小学校の先生にプログラミング言語まで指導させるのは荷が重い話です。
学校でのIT環境を整えることもさることながら、教える側にとっての負担の大きさについても十分議論されるべきと思います。
では子どもたちは、一体何を教わるのでしょうか。
それは「プログラミング的思考」というもので、物事は手順を踏んで進めることでうまく解決できる、というふうに、筋道を立てて論理的に考えていく力のことを指します。
物事をうまく解決するために、ときには繰り返しや条件分岐といった方法を使いながら、最適な手順を探ります。
ビジネスの世界でも、「仮設構築」とか「ロジカルシンキング」とかいう言葉が持て囃されますが、聞き手を説得させる上で、理路整然とした論理展開を行うことは非常に重要なので、こうした能力の土台作りが初等教育から開始されるというのは、好ましいことだと思います。
プログラミング教育に対する批判や課題
最近目にした記事で興味深かったのが以下の記事で、「実用偏重教育に潜む大きな危険性」について触れられています。
“How to use”、つまりスマホといったツールをいかにうまく使うか、というスキルではなく、“How to see”の能力を備えた人材を育てるべきだ、とあります。
現代教育は「実用」に重きを置きすぎていて、いろんな事柄を見極めたり追求したりする力を養う機会が減っている、というのが筆者の主張です。
※ただしこの記事のタイトルはややミスリードを招くように感じます。 筆者は、子どもたちが本を読む機会が減っており、プログラミング教育という科目が増えることで人文科学への比重がさらに軽減されることに異を唱えていますが、おそらくプログラミング教育が「パソコンを使わせるようにするための教育」と勘違いされているのでは、推察します。
この記事の中で強調されている"How to see"は次のように定義されています。
例えばスマホ社会や今後のネット社会、さらにはAI等の進化をどのように捉えて、それが人々の生活やものの考え方にいかなる影響を与えてゆくかというテーマをじっくりと見つめること
確かに、日本の教育課程では、「ルールやパターンに沿って問題を解き、一義的な答えを得る」、という場面に出くわすことが多く、"How to use"のアプローチに順応しがちということも頷けます。
また、ベストセラーとなっている「AI vs. 教科書が読めない子どもたち」でも子どもの読解力低下が指摘されており、表面的なスキルだけ身につけても意味はなく、課題設定や定義付けをする能力があってこそだと論じられているようです。
※私は書店で眺め読みした程度で、まだ精読はできていません
- 作者: 新井紀子
- 出版社/メーカー: 東洋経済新報社
- 発売日: 2018/02/02
- メディア: 単行本
- この商品を含むブログ (32件) を見る
先述したプログラミング教育も、単にツールを使いこなすだけのカリキュラムに成り下がることなく、物事を抽象化して本質を見抜く論理的思考回路を子どもたちに身につけさせる良いきっかけとなればいいなと期待しています。
Pythonで実行した処理の終了を通知してくれるツール
大規模データの処理やら機械学習やらで、演算に時間がかかるときがあります。
放ったらかしにしつつも、都度パソコンの前まで行って進捗を確認するのが面倒で、なんとかできないかと思っていたところ、以下の記事を見つけました。
時間のかかるデータ処理や機械学習処理が終わったら通知してくれるツールをつくった! - 焼肉が食べたい
なんと、メソッドの前に一行加えるだけでSlackに通知してくれるという便利もの。
他にもいくつか、別の人が作ったツールがあるようですが、設定含め、上記のツールが使いやすい印象です。
ただし、Gmailへ通知を飛ばすのはうまくいかず。セキュリティが理由でブロックされたみたい。
Slackが使えればそれで十分ですが、いずれ原因究明します。
あとはJupyter Notebook上で使うときに、マジックコマンドでも利用できたらベターなのですが。。。
他のツールを探すか、自分で作るか。