CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: XGBoost を使ってみる

XGBoost (eXtreme Gradient Boosting) は勾配ブースティング決定木 (Gradient Boosting Decision Tree) のアルゴリズムを実装したオープンソースのライブラリ。 最近は、同じ GBDT 系のライブラリである LightGBM にややお株を奪われつつあるものの、依然として機械学習コンペティションの一つである Kaggle でよく使われている。 今回は、そんな XGBoost の Python バインディングを使ってみることにする。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.2
BuildVersion:   18C54
$ python -V
Python 3.7.2

もくじ

下準備

最初に、下準備として XGBoost と必要なパッケージを一通りインストールする。

そのために、まずは gcc をインストールしておく。 これは XGBoost が並列処理に OpenMP を使っている関係で必要になる。

$ brew install gcc@7

続いて XGBoost と、それ以外で今回使うパッケージをインストールする。

$ pip install xgboost scikit-learn matplotlib

乳がんデータセットを分類してみる

まずはハローワールド的な例として乳がんデータセットを使った二値分類 (Binary classification) から始める。

以下が XGBoost を使って乳がんデータセットを二値分類するサンプルコード。 なお、モデルの検証についてはここでの本題ではないことから、交差検証ではなくホールドアウト検証にとどめている。 最終的に検証用データで精度 (Accuracy) を確認している。 コードの説明についてはコメントを参照のこと。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

"""XGBoost で二値分類するサンプルコード"""


def main():
    # 乳がんデータセットを読み込む
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target
    # データセットを学習用と検証用に分割する
    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)
    # XGBoost が扱うデータセットの形式に直す
    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)
    # 学習用のパラメータ
    xgb_params = {
        # 二値分類問題
        'objective': 'binary:logistic',
        # 評価指標
        'eval_metric': 'logloss',
    }
    # モデルを学習する
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=100,  # 学習ラウンド数は適当
                    )
    # 検証用データが各クラスに分類される確率を計算する
    y_pred_proba = bst.predict(dtest)
    # しきい値 0.5 で 0, 1 に丸める
    y_pred = np.where(y_pred_proba > 0.5, 1, 0)
    # 精度 (Accuracy) を検証する
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)


if __name__ == '__main__':
    main()

上記のサンプルコードに適当な名前をつけて保存した上で実行する。

$ python xgbhelloworld.py
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 18 extra nodes, 0 pruned nodes, max_depth=5
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 14 extra nodes, 0 pruned nodes, max_depth=4
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 14 extra nodes, 0 pruned nodes, max_depth=4
...(snip)...
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[22:52:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
Accuracy: 0.9649122807017544

ホールドアウト検証の結果、精度として 0.965 前後の値が得られた。

学習過程を可視化する

先ほどの例では、いつの間にか学習が終わってモデルができたという感じだった。 そこで、続いては学習が進む過程をグラフで可視化してみる。

次のサンプルコードでは、学習用データと検証用データに対する損失を折れ線グラフで出力する。 そのためには、まず evals オプションで学習用データと検証用データを渡す。 その上で evals_result オプションに過程を記録するための辞書を渡す。 学習ラウンド数 (num_boost_round) は先ほどよりも多く 1000 まで増やした。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot as plt

"""XGBoost で学習の履歴を可視化するサンプルコード"""


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    xgb_params = {
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
    }

    # 学習時に用いる検証用データ
    evals = [(dtrain, 'train'), (dtest, 'eval')]
    # 学習過程を記録するための辞書
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,  # ラウンド数を増やしておく
                    evals=evals,
                    evals_result=evals_result,
                    )

    y_pred_proba = bst.predict(dtest)
    y_pred = np.where(y_pred_proba > 0.5, 1, 0)
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)

    # 学習の課程を折れ線グラフとしてプロットする
    train_metric = evals_result['train']['logloss']
    plt.plot(train_metric, label='train logloss')
    eval_metric = evals_result['eval']['logloss']
    plt.plot(eval_metric, label='eval logloss')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('logloss')
    plt.show()


if __name__ == '__main__':
    main()

先ほどと同じように、適当な名前をつけて実行する。

$ python xgbhistory.py
...(snip)...
Accuracy: 0.9649122807017544

すると、次のようなグラフが得られる。

f:id:momijiame:20190129234917p:plain

学習が進むにつれて学習用データと検証用データの損失が減っていくことが分かる。 とはいえ、過学習はしていないようだけど 100 ラウンド前後で損失の減少は止まっているように見える。

損失が減らなくなったら学習を打ち切る

先ほどの例で分かる通り、損失が減らなくなったらそれ以上学習する必要はない。 それ以上回してしまうと、下手をすると損失が増える (過学習) することも考えられる。 そこで、続いては損失が減らなくなったタイミングで学習を打ち切るようにしてみる。

次のサンプルコードでは early_stopping_rounds オプションを指定することでそれを実現している。 このオプションを使うと、指定したラウンド数の間で損失が減らなかったときに学習を打ち切れる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot as plt

"""XGBoost で early_stopping_rounds を使って学習ラウンド数を最適化するサンプルコード"""


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    xgb_params = {
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
    }

    evals = [(dtrain, 'train'), (dtest, 'eval')]
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,
                    # 一定ラウンド回しても改善が見込めない場合は学習を打ち切る
                    early_stopping_rounds=10,
                    evals=evals,
                    evals_result=evals_result,
                    )

    y_pred_proba = bst.predict(dtest)
    y_pred = np.where(y_pred_proba > 0.5, 1, 0)
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)

    train_metric = evals_result['train']['logloss']
    plt.plot(train_metric, label='train logloss')
    eval_metric = evals_result['eval']['logloss']
    plt.plot(eval_metric, label='eval logloss')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('logloss')
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみよう。 ログ出力から、検証用データの損失に対して early stopping がかかるようになっていることが分かる。

$ python xgbearlystop.py
[23:05:43] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 18 extra nodes, 0 pruned nodes, max_depth=5
[0]    train-logloss:0.462396 eval-logloss:0.492899
Multiple eval metrics have been passed: 'eval-logloss' will be used for early stopping.

Will train until eval-logloss hasn't improved in 10 rounds.
[23:05:43] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 14 extra nodes, 0 pruned nodes, max_depth=4
...(snip)...
[23:05:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[100] train-logloss:0.006161  eval-logloss:0.09275
[23:05:44] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[101] train-logloss:0.00614   eval-logloss:0.093094
Stopping. Best iteration:
[91]  train-logloss:0.00637   eval-logloss:0.092265

Accuracy: 0.9649122807017544

今度は 101 ラウンドまでしか学習が進んでいない。 また、その中でも最も損失の少なかった 91 ラウンド目が結果として使われたことが分かる。

学習過程のグラフは次のようなものが得られた。

f:id:momijiame:20190129234955p:plain

たしかに 100 前後の学習ラウンドまでしか表示されていない。

scikit-learn インターフェースを使ってみる

XGBoost には、ネイティブな API の他に scikit-learn 互換の API を持ったインターフェースもある。 続いては、これを使ってみよう。

次のサンプルコードでは scikit-learn 互換の API を備えた XGBClassifier を使っている。 ただし、実現していることは先ほどと全く変わらない。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot as plt

"""XGBoost の scikit-learn インターフェースを使ったサンプルコード (二値分類)"""


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    # scikit-learn API を備えた分類器
    clf = xgb.XGBClassifier(objective='binary:logistic',
                            # 'num_boost_round' の代わり
                            # adding 1 estimator per round
                            n_estimators=1000)
    # 学習する
    evals_result = {}
    clf.fit(X_train, y_train,
            # 学習に使う評価指標
            eval_metric='logloss',
            # 学習時に用いる検証用データ
            eval_set=[
                (X_train, y_train),
                (X_test, y_test),
            ],
            early_stopping_rounds=10,
            # 学習過程の記録はコールバック API で登録する
            callbacks=[
                xgb.callback.record_evaluation(evals_result)
            ],
            )

    y_pred = clf.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)

    # 学習過程の名前は 'validation_{n}' になる
    train_metric = evals_result['validation_0']['logloss']
    plt.plot(train_metric, label='train logloss')
    eval_metric = evals_result['validation_1']['logloss']
    plt.plot(eval_metric, label='eval logloss')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('logloss')
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみよう。

$ python xgbscikit.py
[0]    validation_0-logloss:0.607962  validation_1-logloss:0.615844
Multiple eval metrics have been passed: 'validation_1-logloss' will be used for early stopping.

Will train until validation_1-logloss hasn't improved in 10 rounds.
[1]   validation_0-logloss:0.538811   validation_1-logloss:0.555234
[2]   validation_0-logloss:0.480826   validation_1-logloss:0.5015
[3]   validation_0-logloss:0.430842   validation_1-logloss:0.458286
...(snip)...
[170] validation_0-logloss:0.008248   validation_1-logloss:0.094108
Stopping. Best iteration:
[160] validation_0-logloss:0.008465   validation_1-logloss:0.094088

Accuracy: 0.9649122807017544

学習ラウンド数は先ほどと違っているものの、最終的に得られた精度は変わっていないようだ。

学習過程のグラフは次のようなものが得られた。

f:id:momijiame:20190129235009p:plain

多値分類問題を扱う

続いてはタスク設定として多値分類 (Multiclass classification) を試してみよう。 この場合、二値分類とは学習するときのパラメータが異なる。

次のサンプルコードでは XGBoost で Iris データセットを使った多値分類問題を扱っている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

from matplotlib import pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

"""XGBoost で多値分類するサンプルコード"""


def main():
    # Iris データセットを読み込む
    dataset = datasets.load_iris()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    xgb_params = {
        # 多値分類問題
        'objective': 'multi:softmax',
        # クラス数
        'num_class': 3,
        # 学習用の指標 (Multiclass logloss)
        'eval_metric': 'mlogloss',
    }
    evals = [(dtrain, 'train'), (dtest, 'eval')]
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,
                    early_stopping_rounds=10,
                    evals=evals,
                    evals_result=evals_result,
                    )

    y_pred = bst.predict(dtest)
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)

    train_metric = evals_result['train']['mlogloss']
    plt.plot(train_metric, label='train logloss')
    eval_metric = evals_result['eval']['mlogloss']
    plt.plot(eval_metric, label='eval logloss')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('logloss')
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみよう。

$ python xgbmulticlass.py
[23:14:48] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[23:14:48] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 8 extra nodes, 0 pruned nodes, max_depth=3
[23:14:48] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 4 extra nodes, 0 pruned nodes, max_depth=2
[0]    train-mlogloss:0.742287    eval-mlogloss:0.765776
Multiple eval metrics have been passed: 'eval-mlogloss' will be used for early stopping.

Will train until eval-mlogloss hasn't improved in 10 rounds.
...
Stopping. Best iteration:
[16]  train-mlogloss:0.039852 eval-mlogloss:0.195911

Accuracy: 0.9333333333333333

乳がんデータセットに比べると、単純な分だけラウンド数がかなり少ないようだ。

得られた学習過程のグラフは次の通り。 学習用データの損失は減っているものの、検証用データの損失が減らない状況が生じていることから過学習の予兆が見られる。

f:id:momijiame:20190129235026p:plain

特徴量の重要度を可視化する

XGBoost は決定木の仲間ということで特徴量の重要度 (Feature Importance) を可視化する機能を備えている。 次のサンプルコードでは、Iris データセットの分類にどの特徴量が有効だったのかを性能のゲインにもとづいて可視化している。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

from sklearn import datasets
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt

"""XGBoost で特徴量の重要度を可視化するサンプルコード"""


def main():
    dataset = datasets.load_iris()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    # 可視化のために特徴量の名前を渡しておく
    dtrain = xgb.DMatrix(X_train, label=y_train,
                         feature_names=dataset.feature_names)
    dtest = xgb.DMatrix(X_test, label=y_test,
                        feature_names=dataset.feature_names)

    xgb_params = {
        'objective': 'multi:softmax',
        'num_class': 3,
        'eval_metric': 'mlogloss',
    }

    evals = [(dtrain, 'train'), (dtest, 'eval')]
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,
                    early_stopping_rounds=10,
                    evals=evals,
                    evals_result=evals_result,
                    )

    # 性能向上に寄与する度合いで重要度をプロットする
    _, ax = plt.subplots(figsize=(12, 4))
    xgb.plot_importance(bst,
                        ax=ax,
                        importance_type='gain',
                        show_values=False)
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみよう。

$ python xgbfeatimp.py
[23:19:37] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1
[23:19:37] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 8 extra nodes, 0 pruned nodes, max_depth=3
[23:19:37] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 4 extra nodes, 0 pruned nodes, max_depth=2
[0]    train-mlogloss:0.742287    eval-mlogloss:0.765776
Multiple eval metrics have been passed: 'eval-mlogloss' will be used for early stopping.

Will train until eval-mlogloss hasn't improved in 10 rounds.
...(snip)...
Stopping. Best iteration:
[16]  train-mlogloss:0.039852 eval-mlogloss:0.195911

次のようなグラフが得られる。 どうやら petal lengthpetal width が分類する上で有効なようだ。

f:id:momijiame:20190129235040p:plain

回帰問題を扱ってみる

続いては XGBoost で回帰問題を扱ってみる。 回帰問題を扱うときは学習時のパラメータとして渡す objectivereg から始まるようになる。

次のサンプルコードでは XGBoost で Boston データセットを回帰している。 学習と検証の評価指標には RMSE (Root Mean Squared Error) を用いた。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import math

import xgboost as xgb

from matplotlib import pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

"""XGBoost で回帰するサンプルコード"""


def main():
    # Boston データセットを読み込む
    dataset = datasets.load_boston()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        )

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    xgb_params = {
        # 回帰問題
        'objective': 'reg:linear',
        # 学習用の指標 (RMSE)
        'eval_metric': 'rmse',
    }
    evals = [(dtrain, 'train'), (dtest, 'eval')]
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,
                    early_stopping_rounds=10,
                    evals=evals,
                    evals_result=evals_result,
                    )

    y_pred = bst.predict(dtest)
    mse = mean_squared_error(y_test, y_pred)
    print('RMSE:', math.sqrt(mse))

    train_metric = evals_result['train']['rmse']
    plt.plot(train_metric, label='train rmse')
    eval_metric = evals_result['eval']['rmse']
    plt.plot(eval_metric, label='eval rmse')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('rmse')
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみよう。

$ python xgbreg.py    
[23:25:09] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 22 extra nodes, 0 pruned nodes, max_depth=5
[0]    train-rmse:17.5096 eval-rmse:16.1546
Multiple eval metrics have been passed: 'eval-rmse' will be used for early stopping.

Will train until eval-rmse hasn't improved in 10 rounds.
...(snip)...
Stopping. Best iteration:
[33]  train-rmse:0.344815 eval-rmse:3.03055

RMSE: 3.0332711348600068

学習過程の損失をプロットしたグラフは次のようになった。

f:id:momijiame:20190129235054p:plain

カスタムメトリックを扱う

これまでは XGBoost に組み込みで入っていた評価指標を用いて学習の進み具合を評価していた。 続いては自分で書いたカスタムメトリックを使って学習してみる。

次のサンプルコードでは、再び乳がんデータセットを使った二値分類を扱っている。 ただし、学習を評価するメトリックとして精度 (Accuracy) を計測するカスタムメトリックを使っている。 カスタムメトリックを扱うにはオプションとして feval に自分で書いた関数を渡す。 カスタムメトリックの関数は、評価指標の名前と値をタプルまたはリストで返すように作る。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot as plt

"""XGBoost で学習時にカスタムメトリックを使ったサンプルコード"""


def feval_accuracy(pred_proba, dtrain):
    """カスタムメトリックを計算する関数"""
    # 真のデータ
    y_true = dtrain.get_label().astype(int)
    # 予測
    y_pred = np.where(pred_proba > 0.5, 1, 0)
    # Accuracy を計算する
    acc = accuracy_score(y_true, y_pred)
    # メトリックの名前と数値を返す(最小化を目指すので 1 から引く)
    return 'accuracy', 1 - acc


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                        test_size=0.3,
                                                        shuffle=True,
                                                        random_state=42,
                                                        stratify=y)

    dtrain = xgb.DMatrix(X_train, label=y_train)
    dtest = xgb.DMatrix(X_test, label=y_test)

    xgb_params = {
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
    }

    evals = [(dtrain, 'train'), (dtest, 'eval')]
    evals_result = {}
    bst = xgb.train(xgb_params,
                    dtrain,
                    num_boost_round=1000,
                    early_stopping_rounds=10,
                    evals=evals,
                    evals_result=evals_result,
                    # カスタマイズした評価関数を使う
                    feval=feval_accuracy,
                    )

    y_pred_proba = bst.predict(dtest)
    y_pred = np.where(y_pred_proba > 0.5, 1, 0)
    acc = accuracy_score(y_test, y_pred)
    print('Accuracy:', acc)

    # デフォルトのメトリック
    _, ax1 = plt.subplots(figsize=(8, 4))
    train_metric = evals_result['train']['logloss']
    ax1.plot(train_metric, label='train logloss', c='r')
    eval_metric = evals_result['eval']['logloss']
    ax1.plot(eval_metric, label='eval logloss', c='g')
    ax1.set_ylabel('logloss')
    ax1.legend()
    ax1.set_xlabel('rounds')

    # カスタムメトリック
    ax2 = ax1.twinx()
    eval_custom_metric = evals_result['eval']['accuracy']
    ax2.plot(eval_custom_metric, label='eval accuracy', c='b')
    ax2.set_ylabel('accuracy')
    ax2.legend()

    plt.grid()
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみる。

$ python xgbcustmetric.py
[23:35:00] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 18 extra nodes, 0 pruned nodes, max_depth=5
[0]    train-logloss:0.462396 eval-logloss:0.492899  train-accuracy:0.017588    eval-accuracy:0.070175
Multiple eval metrics have been passed: 'eval-accuracy' will be used for early stopping.

Will train until eval-accuracy hasn't improved in 10 rounds.
...(snip)...
Stopping. Best iteration:
[14]  train-logloss:0.029256  eval-logloss:0.112603   train-accuracy:0.005025 eval-accuracy:0.046784

Accuracy: 0.9532163742690059

得られた学習過程のグラフは次の通り。

f:id:momijiame:20190129235205p:plain

学習の評価指標として精度 (Accuracy) を使うと、早々に学習が打ち切られてしまっているようだ。 そのため、最終的に得られた精度も低いものになってしまっている。 これは、おそらく一つの要素が正しく分類できたか・できなかったに結果が大きく左右されてしまうため。 まあ、結果はそんなに良くないけど、こんな感じで書けるよという例として。

組み込みの交差検証の機能を使ってみる

XGBoost には、組み込みの交差検証 (Cross Validation) の機能がある。 正直、そんなに使いやすいものではないけど、とりあえずあるよということで紹介しておく。

次のサンプルコードでは、XGBoost に組み込みで入っている交差検証の機能を使っている。 機能は xgboost.cv() という関数が起点になる。 基本的なパラメータについては xgboost.train() で普通に学習するときとさほど変わらない。 関数から最終的に得られるのは、学習過程におけるメトリックの値の変化。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import xgboost as xgb

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from matplotlib import pyplot as plt

"""XGBoost 組み込みの交差検証を使ったサンプルコード"""

def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    # CV の中で分割するので丸ごと渡す
    dtrain = xgb.DMatrix(X, label=y)

    xgb_params = {
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
    }

    # 交差検証する
    history = xgb.cv(xgb_params,
                     dtrain,
                     num_boost_round=1000,
                     early_stopping_rounds=10,
                     nfold=10,
                     # 層化分割する
                     stratified=True,
                     # 検証の経過を出力する
                     verbose_eval=True,
                     )

    train_metric = history['train-logloss-mean']
    plt.plot(train_metric, label='train logloss')
    eval_metric = history['test-logloss-mean']
    plt.plot(eval_metric, label='eval logloss')
    plt.grid()
    plt.legend()
    plt.xlabel('rounds')
    plt.ylabel('logloss')
    plt.show()


if __name__ == '__main__':
    main()

上記を実行してみる。

$ python xgbcustmetric.py
[23:35:00] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 18 extra nodes, 0 pruned nodes, max_depth=5
[0]    train-logloss:0.462396 eval-logloss:0.492899  train-accuracy:0.017588    eval-accuracy:0.070175
Multiple eval metrics have been passed: 'eval-accuracy' will be used for early stopping.

Will train until eval-accuracy hasn't improved in 10 rounds.
...(snip)...
[23:39:47] src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 2 extra nodes, 0 pruned nodes, max_depth=1

すると、次のような学習過程のグラフが得られる。

f:id:momijiame:20190129235216p:plain

いじょう。

まとめ

今回は XGBoost の機能を色々と試してみた。

Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術