今回は CatBoost という、機械学習の勾配ブースティング決定木 (Gradient Boosting Decision Tree) というアルゴリズムを扱うためのフレームワークを試してみる。 CatBoost は、同じ勾配ブースティング決定木を扱うフレームワークの LightGBM や XGBoost と並んでよく用いられている。
CatBoost は学習にかかる時間が LightGBM や XGBoost に劣るものの、特にカテゴリカル変数を含むデータセットの扱いに定評がある。 ただし、今回使うデータセットはカテゴリカル変数を含まない点について先に断っておく。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.3 BuildVersion: 18D109 $ python -V Python 3.7.2
インストール
まずは pip を使って CatBoost をインストールする。 また、データセットの読み込みなどに使うため一緒に scikit-learn も入れておこう。
$ pip install catboost scikit-learn
基本的な使い方 (二値分類問題)
まずは Breast Cancer データセットを使った二値分類問題を CatBoost で処理してみよう。 以下のサンプルコードでは、CatBoost を学習させた上で汎化性能をホールドアウト検証で確認している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score 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) # CatBoost が扱うデータセットの形式に直す train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) # 学習用のパラメータ params = { # タスク設定と損失関数 'loss_function': 'Logloss', # 学習ラウンド数 'num_boost_round': 100, } # モデルを学習する model = CatBoost(params) model.fit(train_pool) # 検証用データを分類する # NOTE: 確率がほしいときは prediction_type='Probability' を使う y_pred = model.predict(test_pool, prediction_type='Class') # 精度 (Accuracy) を検証する acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) if __name__ == '__main__': main()
上記を実行してみよう。 各ラウンドでの学習データに対する損失と時間が表示される。 それぞれのラウンドでは決定木がひとつずつ作られている。
$ python helloworld.py Learning rate set to 0.100436 0: learn: 0.5477448 total: 108ms remaining: 10.6s 1: learn: 0.4414628 total: 140ms remaining: 6.85s 2: learn: 0.3561365 total: 172ms remaining: 5.55s ...(snip)... 97: learn: 0.0120305 total: 3.26s remaining: 66.6ms 98: learn: 0.0116963 total: 3.29s remaining: 33.3ms 99: learn: 0.0113142 total: 3.33s remaining: 0us Accuracy: 0.9532163742690059
最終的に精度 (Accuracy) で約 0.953 という結果が得られた。
ラウンド数を増やしてみる
先ほどの例ではラウンド数、つまり用いる決定木の数が最大で 100 本だった。 次は、試しにこの数を 1,000 まで増やしてみよう。
以下のサンプルコードではラウンド数を増やした上で、最も最適なラウンド数を用いて最終的な性能を確認している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { 'loss_function': 'Logloss', 'num_boost_round': 1000, # ラウンド数を多めにしておく } model = CatBoost(params) # 検証用データに対する損失を使って学習課程を評価する # 成果物となるモデルには、それが最も良かったものを使う model.fit(train_pool, eval_set=[test_pool], use_best_model=True) y_pred = model.predict(test_pool, prediction_type='Class') acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) if __name__ == '__main__': main()
上記を実行してみる。
$ python valset.py Learning rate set to 0.070834 0: learn: 0.5872722 test: 0.5996097 best: 0.5996097 (0) total: 109ms remaining: 1m 49s 1: learn: 0.5025692 test: 0.5240925 best: 0.5240925 (1) total: 140ms remaining: 1m 9s 2: learn: 0.4279025 test: 0.4535490 best: 0.4535490 (2) total: 172ms remaining: 57s ...(snip)... 997: learn: 0.0007559 test: 0.0891592 best: 0.0872851 (771) total: 37s remaining: 74.1ms 998: learn: 0.0007554 test: 0.0891578 best: 0.0872851 (771) total: 37s remaining: 37ms 999: learn: 0.0007543 test: 0.0891842 best: 0.0872851 (771) total: 37s remaining: 0us bestTest = 0.08728505181 bestIteration = 771 Shrink model to first 772 iterations. Accuracy: 0.9590643274853801
最終的に、学習データの損失において最も優れていたのは 771 ラウンドで、そのときの精度 (Accuracy) は約 0.959 だった。 先ほどよりも精度が 0.6 ポイント改善していることが分かる。
学習の過程を可視化する
続いては、モデルが学習する過程を可視化してみよう。 次のサンプルコードでは 1,000 ラウンド回した場合の、学習データと検証データに対する損失の変化を可視化している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { 'loss_function': 'Logloss', 'num_boost_round': 1000, } model = CatBoost(params) model.fit(train_pool, eval_set=[test_pool], use_best_model=True) y_pred = model.predict(test_pool, prediction_type='Class') acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) # メトリックの推移を取得する history = model.get_evals_result() # グラフにプロットする train_metric = history['learn']['Logloss'] plt.plot(train_metric, label='train metric') eval_metric = history['validation_0']['Logloss'] plt.plot(eval_metric, label='eval metric') plt.legend() plt.grid() plt.show() if __name__ == '__main__': main()
上記を実行してみる。
$ python visualize.py Learning rate set to 0.070834 0: learn: 0.5872722 test: 0.5996097 best: 0.5996097 (0) total: 117ms remaining: 1m 56s 1: learn: 0.5025692 test: 0.5240925 best: 0.5240925 (1) total: 150ms remaining: 1m 14s 2: learn: 0.4279025 test: 0.4535490 best: 0.4535490 (2) total: 182ms remaining: 1m ...(snip)... 997: learn: 0.0007559 test: 0.0891592 best: 0.0872851 (771) total: 36.6s remaining: 73.3ms 998: learn: 0.0007554 test: 0.0891578 best: 0.0872851 (771) total: 36.6s remaining: 36.7ms 999: learn: 0.0007543 test: 0.0891842 best: 0.0872851 (771) total: 36.7s remaining: 0us bestTest = 0.08728505181 bestIteration = 771 Shrink model to first 772 iterations. Accuracy: 0.9590643274853801
最終的な結果は先ほどと変わらない。
学習が完了すると、次のようなグラフが得られる。
上記を見ると 200 ラウンドを待たずに検証用データに対する損失は底を打っていることが分かる。
学習が進まなくなったら打ち切る
ここまで見てきた通り、CatBoost ではとりあえず多めのラウンドを回して、最終的に良かったものを使うことができる。 とはいえ、一旦学習が進まなくなると、そこからまた改善するということはそんなにない。 大抵の場合は大して改善しない状況が続くか、過学習が進みやすい。 そこで、続いては学習が進まなくなったらそこで打ち切る Early Stopping という機能を使ってみる。 この機能は LightGBM や XGBoost でも実装されており、よく用いられるものの一つとなっている。
次のサンプルコードでは Early Stopping を用いて 1,000 ラウンド回す中で、検証用データの損失が 10 ラウンド改善しなかったらそこで学習を打ち切るようになっている。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { 'loss_function': 'Logloss', 'num_boost_round': 1000, # 検証用データの損失が既定ラウンド数減らなかったら学習を打ち切る 'early_stopping_rounds': 10, } model = CatBoost(params) model.fit(train_pool, eval_set=[test_pool]) y_pred = model.predict(test_pool, prediction_type='Class') acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) if __name__ == '__main__': main()
上記を実行してみる。
$ python earlystop.py Learning rate set to 0.070834 0: learn: 0.5872722 test: 0.5996097 best: 0.5996097 (0) total: 115ms remaining: 1m 55s 1: learn: 0.5025692 test: 0.5240925 best: 0.5240925 (1) total: 150ms remaining: 1m 15s 2: learn: 0.4279025 test: 0.4535490 best: 0.4535490 (2) total: 183ms remaining: 1m ...(snip)... 136: learn: 0.0139911 test: 0.0939635 best: 0.0932902 (128) total: 5.52s remaining: 34.8s 137: learn: 0.0139071 test: 0.0936872 best: 0.0932902 (128) total: 5.55s remaining: 34.7s 138: learn: 0.0138493 test: 0.0938802 best: 0.0932902 (128) total: 5.59s remaining: 34.6s Stopped by overfitting detector (10 iterations wait) bestTest = 0.09329017842 bestIteration = 128 Shrink model to first 129 iterations. Accuracy: 0.9590643274853801
今度は 138 ラウンドで学習が止まったものの、最終的な汎化性能は先ほどと変わらないものが得られている。 このように Early Stopping は計算量を節約する上で重要な機能になっている。
scikit-learn インターフェースを使ってみる
CatBoost には scikit-learn のインターフェースもある。 続いてはそれを使ってみることにしよう。
以下のサンプルコードでは scikit-learn のインターフェースを備えた CatBoostClassifier
を使っている。
これを用いることで使い慣れた scikit-learn のオブジェクトとして CatBoost を扱うことができる。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoostClassifier from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score 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 インターフェースを備えたラッパー model = CatBoostClassifier(num_boost_round=1000, loss_function='Logloss', early_stopping_rounds=10, ) model.fit(X_train, y_train, eval_set=[(X_test, y_test)]) # 確率がほしいときは predict_proba() を使う y_pred = model.predict(X_test) acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) if __name__ == '__main__': main()
上記を実行してみる。
$ python skapi.py Learning rate set to 0.070834 0: learn: 0.5872722 test: 0.5996097 best: 0.5996097 (0) total: 112ms remaining: 1m 51s 1: learn: 0.5025692 test: 0.5240925 best: 0.5240925 (1) total: 143ms remaining: 1m 11s 2: learn: 0.4279025 test: 0.4535490 best: 0.4535490 (2) total: 176ms remaining: 58.4s ...(snip)... 136: learn: 0.0139911 test: 0.0939635 best: 0.0932902 (128) total: 5.8s remaining: 36.5s 137: learn: 0.0139071 test: 0.0936872 best: 0.0932902 (128) total: 5.83s remaining: 36.4s 138: learn: 0.0138493 test: 0.0938802 best: 0.0932902 (128) total: 5.88s remaining: 36.4s Stopped by overfitting detector (10 iterations wait) bestTest = 0.09329017842 bestIteration = 128 Shrink model to first 129 iterations. Accuracy: 0.9590643274853801
結果は先ほどと変わらない。
多値分類問題を処理してみる
ここまでは二値分類問題を扱ってきた。 続いては Iris データセットを用いて多値分類問題を解かせてみよう。
次のサンプルコードでは Iris データセットを CatBoost で分類している。 検証方法は先ほどと同じホールドアウト検証で、精度 (Accuracy) について評価している。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { # 多値分類問題 'loss_function': 'MultiClass', 'num_boost_round': 1000, 'early_stopping_rounds': 10, } model = CatBoost(params) model.fit(train_pool, eval_set=[test_pool]) y_pred = model.predict(test_pool, prediction_type='Class') acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) if __name__ == '__main__': main()
上記の実行結果は次の通り。
$ python multiclass.py 0: learn: -1.0663112 test: -1.0680680 best: -1.0680680 (0) total: 65.3ms remaining: 1m 5s 1: learn: -1.0369337 test: -1.0404371 best: -1.0404371 (1) total: 74.7ms remaining: 37.3s 2: learn: -1.0094257 test: -1.0172382 best: -1.0172382 (2) total: 78.9ms remaining: 26.2s ...(snip)... 268: learn: -0.0561336 test: -0.1770632 best: -0.1768477 (260) total: 986ms remaining: 2.68s 269: learn: -0.0557735 test: -0.1773530 best: -0.1768477 (260) total: 991ms remaining: 2.68s 270: learn: -0.0556859 test: -0.1772400 best: -0.1768477 (260) total: 994ms remaining: 2.67s Stopped by overfitting detector (10 iterations wait) bestTest = -0.1768476949 bestIteration = 260 Shrink model to first 261 iterations. Accuracy: 0.9111111111111111
特徴量の重要度を可視化する
CatBoost が採用している勾配ブースティング決定木は名前の通り決定木の仲間なので特徴量の重要度を取得できる。 続いては特徴量の重要度を可視化してみよう。
以下のサンプルコードでは、先ほどと同じ条件で学習したモデルから特徴量の重要度を取得してグラフにプロットしている。
#!/usr/bin/env python # -*- coding: utf-8 -*- from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from matplotlib import pyplot as plt 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { 'loss_function': 'MultiClass', 'num_boost_round': 1000, 'early_stopping_rounds': 10, } model = CatBoost(params) model.fit(train_pool, eval_set=[test_pool]) y_pred = model.predict(test_pool, prediction_type='Class') acc = accuracy_score(y_test, y_pred) print('Accuracy:', acc) # 特徴量の重要度を取得する feature_importance = model.get_feature_importance() # 棒グラフとしてプロットする plt.figure(figsize=(12, 4)) plt.barh(range(len(feature_importance)), feature_importance, tick_label=dataset.feature_names) plt.xlabel('importance') plt.ylabel('features') plt.grid() plt.show() if __name__ == '__main__': main()
上記を実行してみる。
$ python featimp.py 0: learn: -1.0663112 test: -1.0680680 best: -1.0680680 (0) total: 61.7ms remaining: 1m 1s 1: learn: -1.0369337 test: -1.0404371 best: -1.0404371 (1) total: 67.2ms remaining: 33.6s 2: learn: -1.0094257 test: -1.0172382 best: -1.0172382 (2) total: 71ms remaining: 23.6s ...(snip)... 268: learn: -0.0561336 test: -0.1770632 best: -0.1768477 (260) total: 961ms remaining: 2.61s 269: learn: -0.0557735 test: -0.1773530 best: -0.1768477 (260) total: 964ms remaining: 2.61s 270: learn: -0.0556859 test: -0.1772400 best: -0.1768477 (260) total: 967ms remaining: 2.6s Stopped by overfitting detector (10 iterations wait) bestTest = -0.1768476949 bestIteration = 260 Shrink model to first 261 iterations. Accuracy: 0.9111111111111111
得られたグラフは次の通り。
上記から Petal length と Petal width が分類において有効に作用していたことが確認できる。
回帰問題を処理してみる
続いては回帰問題を扱ってみる。 以下のサンプルコードでは Boston データセットを CatBoost で回帰している。 汎化性能の確認してはホールドアウト検証で RMSE (Root Mean Squared Error) を評価している。
#!/usr/bin/env python # -*- coding: utf-8 -*- import math from catboost import CatBoost from catboost import Pool from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error 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) train_pool = Pool(X_train, label=y_train) test_pool = Pool(X_test, label=y_test) params = { # 損失関数に RMSE を使う 'loss_function': 'RMSE', 'num_boost_round': 1000, 'early_stopping_rounds': 10, } model = CatBoost(params) model.fit(train_pool, eval_set=[test_pool]) y_pred = model.predict(test_pool) # 最終的なモデルの RMSE を計算する mse = mean_squared_error(y_test, y_pred) print('RMSE:', math.sqrt(mse)) if __name__ == '__main__': main()
上記を実行してみる。
$ python regress.py 0: learn: 24.2681654 test: 22.5038434 best: 22.5038434 (0) total: 75.5ms remaining: 1m 15s 1: learn: 23.6876829 test: 21.9483221 best: 21.9483221 (1) total: 84.6ms remaining: 42.2s 2: learn: 23.1173979 test: 21.3856082 best: 21.3856082 (2) total: 92.1ms remaining: 30.6s ...(snip)... 466: learn: 2.4850147 test: 3.3697155 best: 3.3675366 (458) total: 4.11s remaining: 4.69s 467: learn: 2.4833511 test: 3.3700655 best: 3.3675366 (458) total: 4.13s remaining: 4.69s 468: learn: 2.4828831 test: 3.3701698 best: 3.3675366 (458) total: 4.14s remaining: 4.69s Stopped by overfitting detector (10 iterations wait) bestTest = 3.36753664 bestIteration = 458 Shrink model to first 459 iterations. RMSE: 3.367536638941265
いじょう。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る