はじめに
いきなりですが、Kaggleというデータ分析コンペティションのサイトをご存知でしょうか?
時には総額10億円を超えるほどの賞金をめぐって、腕に覚えのあるデータサイエンティスト達が切磋琢磨し日夜データ分析コンペティションに挑んでいるという、データ分析の虎の穴とでも言うべきサイトです。
こう書くと素人お断りの魔窟のような感じがしますがそんなことはなく、初学者がまずやってみるための簡単かつ実践的なお題がいくつも用意されていたり、フォーラムで熟練のデータサイエンティストに質問できたりなど、初学者がデータ分析に入門するのに非常に役に立つサイトでもあります。
そのKaggleで今もっともアツい機械学習モデルと噂されるものがあります。
流行りのディープラーニングでしょうか?実は違います。
それは、決定木です。タイトルでネタバレしてますが。
今回は、決定木を用いて熟練のKaggler達を満足させるモデルを生み出す「XGBoost」というOSS製品をご紹介します。
決定木とは
決定木とは条件分岐を木構造で表したモデルで、根ノードから条件をたどって末端まで到達すると答えが得られるというものです。
出典:決定木 – Wikipedia
見ての通り仕組みが単純なため、人間がモデルの構造を理解しやすいという利点がありますが、そのぶん複数の変数が絡み合って作用するような複雑な事象には弱いと言われています。
集団学習(アンサンブル学習)と勾配ブースティング
XGBoostは、複数の決定木を組み合わせて学習することで高い精度を実現しています。
(厳密には決定木以外に線形モデルも利用できるのですが、普通は決定木を用いるのでこの記事では決定木の利用を前提とします)
複数のモデルを組み合わせて有用なモデルを得る手法は集団学習(もしくはアンサンブル学習)と呼ばれ、そのなかでもXGBoostでは勾配ブースティングという手法が使われています。
決定木の勾配ブースティングの大まかなイメージについて説明します。
まず、学習データをもとに1本目の決定木を作ります。当然すべての学習データをきれいに説明できるものにはならず、ある程度の誤差が生じます。
2本目の決定木は、この誤差を打ち消すように作っていきます。すなわち、1本目と2本目の決定木の結果を足し合わせれば精度が上がるように2本目の決定木を作ります。
以降同様に、それまで作った木全体が生む誤差を打ち消すように新しい決定木を作っていきます。
勾配ブースティングのイメージ(出典: Zhang et al. 2018)
集団学習のなかでも、それまで作ったモデル全体を参照しつつ新たにモデルを付け加えていく手法はブースティングと呼ばれ、なかでも勾配ブースティングは勾配降下法を用いてブースティングを行うためそう呼ばれています。
XGBoostで乳がん判定器を作ってみる
そろそろ動くものが見たくなってきたのではないですか?おまたせしました、コードの時間です。
Pythonコード
以下は、公開されている乳がんのデータセットをもとに、サンプリングした細胞の統計データから乳がんかどうかを判定するモデルを得るPythonコードです。
前提として、pip等を用いてPython環境にxgboost、sklearn、pandas、matplotlib、numpy、graphvizをインストールする必要があります。
また後述する決定木の描画を行うためには、Python環境とは別に、システムコマンドとしてgraphvizコマンドをインストールする(パスを通す)必要があります。こちらはGraphvizのサイトから取得してください。
import xgboost as xgb from sklearn import datasets import sklearn import pandas as pd from matplotlib import pyplot as plt import numpy as np def main(): # 乳がんデータセットをロード dataset = datasets.load_breast_cancer() x = pd.DataFrame(dataset.data, columns=[i.replace(' ', '_') for i in dataset.feature_names]) # 簡単のため、利用する特徴量を一部に限定 x = x.loc[:, ['mean_radius', 'mean_texture', 'mean_perimeter', 'mean_area', 'mean_smoothness', 'mean_compactness', 'mean_concavity', 'mean_concave_points', 'mean_symmetry', 'mean_fractal_dimension']] y = dataset.target # データを訓練用と検証用に分割 x_train, x_test, y_train, y_test = sklearn.model_selection.train_test_split(x, y, test_size=0.2, train_size=0.8, shuffle=True, stratify=y) # ハイパーパラメータを指定して学習実行 train_params = { 'objective': 'binary:logistic', 'eval_metric': 'logloss', 'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 60, 'colsample_bytree': 0.6 } clf = xgb.XGBClassifier(**train_params) clf.fit(x_train, y_train, eval_set=[[x_test, y_test]]) # 出力(1): 検証用データに対する精度を確認 booster = clf.get_booster() dtest = xgb.DMatrix(x_test, label=y_test) y_test_pred_prob = booster.predict(dtest) y_test_pred = np.where(y_test_pred_prob > 0.5, 1, 0) acc = sklearn.metrics.accuracy_score(y_test, y_test_pred) print(acc) # 出力(2): 特徴量の重要度を描画 _, ax = plt.subplots(figsize=(12, 12)) xgb.plot_importance(booster, ax=ax, importance_type='gain',
おおまかな処理内容についてはコメントをご参照ください。
注意点として、利用するデータセットにはデータ1件あたり30種類の特徴量が含まれているのですが、今回は簡単のためそのうち10種類のみを抜き出して使用するようにしています。
また、特徴量名にスペースが含まれていると後述する決定木の描画処理がうまくいかないため、12行目でスペースをアンダースコアに変換して利用しています。
モデルの精度
上記のコードを実行したところ、まず「出力(1)」の部分で以下のような出力を得ました:
0.9473684210526315
これはモデルの精度が約94.7%であったことを示します。この精度で実用に耐えうるかはさておき、ちゃんと学習が行われていることがわかります。
また、手元の環境で学習にかかった時間は3秒ほどでした。
重要な特徴量の確認
「出力(2)」の部分では以下のような棒グラフを得ました:
少し文字が細かいですが、これは得たモデルにおける各特徴量の重要度を表したグラフです。
特に重要度が大きい上位3つの特徴量はそれぞれ「平均細胞面積」「細胞の凹んだ部分の数の平均」「平均細胞半径」を表します。
どうやら乳がんの判定には、細胞の大きさと凹んだ部分の数が重要であるようです。
このように、モデルを得たあとにそれがどのようなモデルなのかを人間がわかる形で示すことができるのがXGBoostの利点の一つです。
決定木の様子の確認
「出力(3)」の部分では以下のような図を得ました:
こちらも細かいですが、各分岐には特徴量の値による条件が書かれており、末端にはleaf=0.xxx
という形式でスコアが書かれています。
XGBoostでは決定木を複数作成し、それぞれの木のスコアを合計して出力とします。
今回のデータセットでは良性が1、悪性(=がんの疑い)が0で表現されており、末端の値が負の数であれば、それはがんの疑いを強める条件分岐であると解釈できます。
決定木を使うというと、たとえばがん診断のような複雑なタスクがYES/NOチャートのように簡単に表現できると思われがちですが、実際には上図のような決定木を何十本、何百本と組み合わせて判断するのがXGBoostですので、決定木を直接見て傾向を説明するのは現実的ではないと考えられます。
おわりに
今回は、決定木の集団学習によって複雑なモデルを構成できるXGBoostというOSS製品を紹介しました。
決定木という素朴なモデルを用いていながら、乳がんのデータセットに対して95%程度の精度が得られることを確認しました。
XGBoostのディープラーニングと比較した際の利点としては、工夫次第では少ないデータ数でも良いモデルが得られること、特徴量の重要度をグラフ化して評価できることなどが挙げられます。
Kaggle界隈の動きについては、今後も注視していきたいと思います。