調整しよう!ランダムフォレストのハイパーパラメータ

前回は、銀行の顧客ターゲティングを機械学習でやってみるにあたり、まずは最低限の前処理で、どれくらいの精度が出るのかを、図ってみました。

今回は、「ハイパーパラメータ」を調整し、精度の向上に挑戦してみたいと、思います。

ハイパーパラメータって何?

ハイパーパラメータと聞くと、なんだかとっても凄そうな名前ですよね。イメージだけで言うと、これを調節すれば、ものすごい精度があがりそうです。が、実際には、そんなに御大層なものでも(?)ありません。

機械学習において、入力データと出力データの関係から、機械が自動的に学習して、値を調節する変数がパラメータ。機械をうまく学習させるために、人間がマニュアルで調節する必要がある変数を、ハイパーパラメータという、みたいです*1

今回はこの「ハイパーパラメータ」の調節に、チャレンジしてみたいと思います。

ランダムフォレストとハイパーパラメータ

一般に、ランダムフォレストは調整すべきハイパーパラメータが少ないと、言われているみたいです。が、scikit-learnのドキュメンテーションを見る限り、素人目には、それなりに数がありるように、見えます。やみくもに調整するのも大変なので、まずは、ランダムフォレストの仕組みに立ち戻って、考えてみたいと思います。

そもそも、ランダムフォレストとは、このあたりこのあたりでも触れましたが、決定木/回帰木を弱学習器として利用した、バギングによる、アンサンブル機械学習のはずです。

ということは、まず重要なハイパーパラメータは、「n_estimators」だと、思われます。これは、ベースとして利用する決定木/回帰木の数で、理論上、数が多ければ多いほど精度は上がりそうです。が、計算時間も、うなぎ上りに、なりそうです。デフォルトは、10みたいです。

次に目に付くのが、「max_depth」です。これは、先のn_estimatorsで指定した決定木/回帰木の深さを、どこまでにするかを、指定します。「木の深さ」については、このあたりのイメージを、ご覧ください。木を深くすればするほど、複雑な関数を近似できるようになります。が、学習データに過剰にフィットしすぎて、肝心かなめの未知のデータに対して精度が悪くなる、いわゆる過学習が起きる可能性が、あります。デフォルトはNone。この場合、=∞つまり、どこまででも木を深くする、みたいです。

続いては、「max_features」。個々の決定木に、どれくらいの特徴量を使用するかを、表します。特徴量とは、=入力データの種類。銀行の顧客ターゲティングにおいては、このあたりでまとめてみましたが、「age」~「poutcom」までの、16項目。経験則的に、入力項目数の平方根、今回の場合は16^(1/2)を指定すると、精度が上がることが多いみたいですが、log2(入力項目数)とかも、慣例的に用いられる、そうです。デフォルトはautoになっているみたいですが、auto=sqrt、即ち、デフォルトだと、入力項目数の平方根が、max_featuresに用いられるそうです。

気配的に、このあたりがランダムフォレストの三大パラメータの気がするので、まずはこの3つを、調節してみたいと、思います。

グリッドサーチによるハイパーパラメータの調整

ハイパーパラメータを調整する方法として、1個1個手動で調整する方法と、組み合わせを総当たりで調整する方法が、あります。正直、総当たりはとても計算時間がかかるし、あらかじめ当たりがついていないと組み合わせ候補も作れないため、個人的には手動であたりをつけたいところなんですが・・・今回はパラメータが3つなので、総当たりやってみたいと思います。

パラメータ 値候補
n_estimators 10, 100, 200, 300
max_depth 5, 10, 50, None
max_features 'sqrt', 'log2', None

n_estimatorsとmax_depthは、とりあえずオーダー感にあたりをつけるため、ざっくり増やしていきます。余裕があれば、もっとも精度がよくなった近辺をもう少し探ってみる、方針です。

続いて、総当たりの方法ですが・・・グリッドサーチという手法で、やってみたいと思います。以下の図を、ご覧ください。

f:id:tatsu_mk2:20190504171824p:plain
図1 グリッドサーチイメージ
 
前回の「ホールドアウト法」では、入力データを「学習データ」と「検証データ」に2分割しましたが、グリッドサーチでは、3以上に分割します。図の場合、3分割したので、学習データと検証データの組み合わせが、3つあります。これに対して、パラメータを総当たりで組み合わせを検証するため・・・4(n_estimators)×4(max_depth)×3(max_features)×3(学習データと検証データの組み合わせ)=144通りになります。うーん、ディープラーニングとかだと、とてもじゃないけどやってられなさそうな数字ですね。図は、簡単なように3分割で書いてみましたが、ここでは5分割でやってみようと、思います。つまり、240通りです。うげっ・・・

GridSearchCVによるグリッドサーチ

Pythonには、グリッドサーチを行うためのライブラリとして、その名もずばりGridSearchCVというライブラリがあるため、今回はこれを使ってやってみたいと、思います。

まずは、前回に倣い、最低限の前処理を行うまでを、一気にやります。

# まずはGoogle Colabのお約束、Googleドライブをマウント
from google.colab import drive
drive.mount('/content/gdrive/')

# 作業フォルダへ移動
%cd ./gdrive/My\ Drive/colab/bank

# pandasをインポート
import pandas as pd

# 銀行の顧客データを読み込み、正しく読み込めていることを確認
train = pd.read_csv("./dataset/train.csv")
train.head()

# ダミーコーディング実行
train_data = pd.get_dummies(data=train, columns=['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'poutcome', 'month'], drop_first=True)
train_data.columns

# 目的変数の定義
target_col = 'y'


# 説明変数に使用しない列の定義
exclude_cols = [target_col, 'id']


# 説明変数の定義
feature_cols = [col for col in train_data.columns if col not in exclude_cols]


# 目的変数と説明変数を分割
y = train_data[target_col]
x = train_data[feature_cols]
x.head()


そしてここからが今日の本題。まずは、ハイパーパラメータの値候補を、ディクショナリ型で、定義します。

# ディクショナリ型でハイパーパラメータの値候補を定義
params = {"n_estimators": [10, 100, 200, 300], "max_depth": [5, 10, 50, None], "max_features": ["sqrt", "log2", None]}

続いて、ランダムフォレストとグリッドサーチ用のライブラリを、インポートします。

# 学習用ライブラリをインポート
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV

GridSearchCVですが、情報によっては「from sklearn.grid_search」となっているものもあるみたいですが、私の環境だと、「from sklearn.model_selection」にいました。場所が変わったようです。

続いて、いよいよランダムフォレストで、GridSearchCVを実行します。基本的に、GridSearchCVをインスタンス化して、fit()関数を実行すれば、グリッドサーチが実行されます。インスタンス化する際の第一引数が、グリッドサーチしたいモデルのインスタンス(この場合ランダムフォレスト)、param_gridに先ほど定義したディクショナリ型の値候補を、cvに分割数(この場合、5)、scoringに評価指標(この場合、AUC)を、指定します。scoringに指定できる指標値は、scikit-learnのドキュメンテーションのこのあたりに、あります。

# グリッドサーチ実行
rf = RandomForestClassifier(random_state=1234)
gscv = GridSearchCV(rf, param_grid=params, cv=5, scoring="roc_auc", verbose=1)
gscv.fit(x, y)


案の定めっちゃ時間かかりました

優に2,30分はかかりましたでしょうか・・・終わってよかったです。では、ベストな組み合わせの確認。

# ベストパラメータの確認
gscv.best_params_

{'max_depth': 50, 'max_features': 'sqrt', 'n_estimators': 300}

n_estimatorsは最大の300がベストになっているので、まだまだ増やせる余地があるかもしれません。一方、max_depthは、Noneよりも50の方がベストになっているので、もう少しは増やせるかもしれませんが、無限にするのはやりすぎみたいです。そして、max_featuresは、ちまたの噂通り、sqrtでした。指標確認してみたいと思います。

# scoring(今回の場合、roc_auc)で指定した指標の確認
gscv.best_score_

0.9275434071386318

だいぶスコアがあがりました!

前回が0.889くらいだったのに対し、0.928くらいでしょうか。SIGNATEのランキングで言うと、370/5115位(2019年5月6日現在)くらいになりました。

改めてホールドアウト法で確認

グリッドサーチで求めたパラメータをベースに、もう少しパラメータを調整しつつ、改めてホールドアウト法で、他の指標の値も確認してみたいと、思います。

# データ分割用の関数をインポート
from sklearn.model_selection import train_test_split

# 分割実行
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.1, random_state=1234)

# 学習を実行
rcf = RandomForestClassifier(n_estimators=1000, max_depth=50, max_features="sqrt", random_state=1234)
rcf.fit(x_train, y_train)

# 分類結果を取得
y_pred = rcf.predict(x_test)

# 確率を取得
y_pred_proba = rcf.predict_proba(x_test)[:, 1]

# 精度測定用関数のインポート
from sklearn.metrics import roc_auc_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import recall_score
from sklearn.metrics import precision_score

# 精度測定
auc = roc_auc_score(y_test, y_pred_proba)
print('AUC: ', auc)
acy = accuracy_score(y_test, y_pred)
print('Accuracy: ', acy)
rc = recall_score(y_test, y_pred)
print('Recall: ', rc)
pr = precision_score(y_test, y_pred)
print('Precision: ', pr)

AUC: 0.9315272819303317
Accuracy: 0.9100626612605971
Recall: 0.3948220064724919
Precision: 0.6815642458100558

max_depthの値は、50のほかに、改めて60, 70, 80も試してみましたが、小数点のかなり下の方の値がちょっぴり上がるだけだったので、計算時間も考慮し、50に据え置きました。n_estimatorsは、250, 350, 400を試してみたところ、逆にAUCが下がりましたが、1000を試してみたところ、0.0005くらいは上がったので、採用してみました。

グリッドサーチに比べて少し精度が上がっているように見えますが・・・これをどう見るかは、少し難しいところです。先のグリッドサーチは、データを5分割し、4つを学習データ・1つを検証データに使用しました。つまり、学習データ:検証データの割合は、8:2です。一方、ホールドアウト法は9:1で分割したため、学習データが多い分、精度が上がった可能性も、あります。

一方、グリッドサーチは、学習データと検証データの組み合わせを5つ作成し、その平均を取ったのに対し、ホールドアウト法は1回しか実行していません。たまたまホールドアウト法の組み合わせにうまくフィットし、精度が上がった可能性もありますが、それが実データ(未知のデータ)の分布と近いのか離れているのかは、分からないので、いいことか悪いことかは、なかなか判別しかねるところです。

ちなみに、0.932ですと・・・330位/5115位くらいでしょうか。

終わりに

いかがだったでしょうか。上の方では、「ハイパーパラメータは名前は御大層でも、名前ほど御大層なものではない」なんて書いてしまいましたが・・・初心者でも、モデルとハイパーパラメータの選択を誤らなければ、そこそこの精度は出せそうなことが、分かりました。このあたりが、「ランダムフォレストは安定した精度が出る」なんて言われる所以かな、なんて気が、します。深い業務知識と職人芸的なテクニックが必要そうな、特徴量設計*2に比べると、誰でもある程度精度をあげることが、出来そうです。

というわけで、今回はこのあたりにして、次回は、ランダムフォレストの残りのハイパーパラメータについても、掘り下げてみたいと思います。最後まで読んでいただいて、ありがとうございました。

参考資料

フリーライブラリで学ぶ機械学習入門

フリーライブラリで学ぶ機械学習入門

*1:ベイズ最適化という、ハイパーパラメータ含め最適化する手法も、あるみたいですが。

*2:ざっくり言えば、入力データをそのまま機械学習に使うのではなく、より精度が上がりそうな加工を施して、取捨選択して機械学習に使用する手法とでも、言えましょうか?