ハイパーパラメータの調整


パラメータはモデルの学習実行後に得られる値で、重みとも呼ばれます。
対してハイパーパラメータは各アルゴリズムに付随して、アルゴリズムの挙動を制御するための値です。
ハイパーパラメータを調整することで、っモデルの性能向上や過学習の抑制、効率の良い学習などが期待できます。

1. K-分割交差検証 (K-fold cross-validation)

与えられたデータセットを「学習用データセット」「テスト用データセット」に2分割することをホールドアウト法と呼びます。
ホールドアウト法では、ハイパーパラメータの調整に使うデータがありませんので、ハイパーパラメータを調整するためには「検証用データセット」を用意する必要があります。

しかし、3分割するほどデータ量がないこともあります。
その時に使うのが、K-分割交差検証 (K-fold cross-validation)です。

【手順】
1.データセットをK個に分割
2.分割したデータの1個を検証用データセットとし、残りK-1個を学習用データセットとして学習を実行
→ 1 回で学習を終わらせず、計K回の学習を行います。その際、既に検証用データセットに使ったデータを次は学習用データセットとして使用し、新たに検証用データセットを選択します。
3.各検証の結果を平均して最終的な検証結果とする

2. ハイパーパラメータの調整方法

ハイパーパラメータの調整するには、以下のような方法があります。
1.手動での調整
2.グリッドサーチ (Grid Search)
3.ランダムサーチ (Random Search)
4.ベイズ最適化 (Bayesian Optimization)

2-1. 手動での調整

DecisionTreeClassifierで決定木の実装を行う場合を考えます。

from sklearn.tree import DecisionTreeClassifier
# ハイパーパラメータ調整なし
dtree = DecisionTreeClassifier(random_state=0)
# ハイパーパラメータ調整あり
dtree2 = DecisionTreeClassifier(max_depth=10, min_samples_split=30, random_state=0)

このように、max_depthmin_samples_splitについて、自分で適当に値を入れて、数値を変えるたびに予測精度を確認するという方法が、手動での調整です。

2-2. グリッドサーチ (Grid Search)

グリッドサーチはまず、ハイパーパラメータを探索する範囲を決めます。
例えば上記のように決定木のmax_depthmin_samples_splitの値を調整したい場合、5、10、15、20、25 のように範囲をそれぞれ決めます(範囲の指定に特に決まりはありません)。
この場合のハイパーパラメータの組み合わせは 5x5=25 個になります。
この25個のハイパーパラメータの組み合わせ全てを使用して、学習・検証を行います。
そして、その結果から予測精度が最も高いハイパーパラメータを採用します。

グリッドサーチには以下の3つを準備する必要があります。

  • estimator : 学習に使用するモデル
  • param_grid : ハイパーパラメータを探索する範囲
  • cv : K-分割交差検証のKの値
# GridSearchCV クラスのインポート
from sklearn.model_selection import GridSearchCV

# 学習に使用するアルゴリズムの定義
estimator = DecisionTreeClassifier(random_state=0)

# 探索するハイパーパラメータと範囲の定義
param_grid = [{
    'max_depth': [3, 20, 50],
    'min_samples_split': [3, 20, 30]
}]

# データセット分割数を定義
cv = 5

# GridSearchCV クラスを用いたモデルの定義
tuned_model = GridSearchCV(estimator=estimator, 
                           param_grid=param_grid, 
                           cv=cv, return_train_score=False)

# モデルの学習&検証
tuned_model.fit(x_train_val, t_train_val)

# 検証結果の確認
pd.DataFrame(tuned_model.cv_results_).T

ここまで入力すると、それぞれのハイパーパラメータの値に対する予測精度などが表になって出力されます。
mean_test_scoreの値を見ると、モデルの予測精度がわかります。
その後、結果を参照して先ほどより狭い範囲でハイパーパラメータを調整します。
これを何度か繰り返すことで徐々に予測精度が高くなるハイパーパラメータへと近づけて行きます。

# 最も予測精度の高かったハイパーパラメータの確認
tuned_model.best_params_

# 最も予測精度の高かったモデルの引き継ぎ
best_model = tuned_model.best_estimator_

# モデルの検証
print(best_model.score(x_train_val, t_train_val))
print(best_model.score(x_test, t_test))

これで、最も予測精度が高かったハイパーパラメータのモデルで検証することができます。

2-3. ランダムサーチ (Random Search)

ランダムサーチは指定した範囲のハイパーパラメータをランダムに抽出し、学習・検証を行います。
グリッドサーチに比べて、より広い範囲を探索することができるので、効率的と言われています。
しかし、全てのハイパーパラメータを探索するわけではないので、本当に最適か判断が難しいと言えます。
ランダムサーチで大体のあたりをつけてから、グリッドサーチで細かく見ていくやり方だと双方のメリットが活かせます。

# RandomizedSearchCV クラスのインポート
from sklearn.model_selection import RandomizedSearchCV
# 学習に使用するアルゴリズム
estimator = DecisionTreeClassifier(random_state=0)

# ハイパーパラメータを探索する範囲の指定 range(開始値, 終了値, ステップ)
# 例えば range(1, 10, 2) の場合、1から10までの値を2刻みで獲得できる。その値をlist()でリスト化。
param_distributions = {
    'max_depth': list(range(5, 100, 2)),
    'min_samples_split': list(range(2, 50, 1))
}

# 試行回数の指定
n_iter = 100

# Kの値を指定
cv = 5

# モデルの定義
tuned_model = RandomizedSearchCV(
    estimator=estimator, 
    param_distributions=param_distributions, 
    n_iter=n_iter, cv=cv, 
    random_state=0, return_train_score=False
)

# モデルの学習&検証
tuned_model.fit(x_train_val, t_train_val)

# 学習結果の確認(スコアの高い順に表示)
pd.DataFrame(tuned_model.cv_results_).sort_values('rank_test_score').T

ここまで入力すると、それぞれのハイパーパラメータの値に対する予測精度などが表になって出力されます。

# 最も予測精度の高かったハイパーパラメータの確認
tuned_model.best_params_

# 最も予測精度の高かったモデルの引き継ぎ
best_model = tuned_model.best_estimator_

# モデルの検証
print(best_model.score(x_train_val, t_train_val))
print(best_model.score(x_test, t_test))

2-4. ベイズ最適化 (Bayesian Optimization)

数学的背景の理解が難しいのですが、ベイズ最適化では探索(この辺かな〜?と適当に入力してみる)や活用(結果をもとに、さらにこの辺かな〜?と値を更新する)で得られた情報を元にハイパーパラメータを調整していくため、予測精度が高くなるハイパーパラメータを見つけやすいそうです。
日本の Prefferd Networks 社が開発している Optuna というフレームワークを使用するときの方法を記述します。

# optuna のインストール
!pip install optuna
import optuna

Optunaでは最初に関数objectiveを定義して内部に以下の要素を関数として順に定義します。

  • 1.ハイパーパラメータごとに探索範囲を指定
  • 2.学習に使用するアルゴリズムを指定
  • 3.学習の実行、検証結果の表示

探索範囲の指定にはデフォルトで準備されているtrialクラスを使用します。
3では学習・検証を繰り返してハイパーパラメータの調整を行うのですが、その際にreturnで取得した検証結果を最小化(最大化)するように調整が進みます。
また、3でK-分割交差検証を使用するにはcross_val_scoreが必要です。

from sklearn.model_selection import cross_val_score
def objective(trial, x, t, cv):
    # 1. ハイパーパラメータごとに探索範囲を指定
    max_depth = trial.suggest_int('max_depth', 2, 100)
    min_samples_split = trial.suggest_int('min_samples_split', 2, 100)

    # 2. 学習に使用するアルゴリズムを指定
    estimator = DecisionTreeClassifier(
      max_depth = max_depth,
      min_samples_split = min_samples_split
    )

    # 3. 学習の実行、検証結果の表示
    print('Current_params : ', trial.params)
    accuracy = cross_val_score(estimator, x, t, cv=cv).mean()
    return accuracy

正解率の最大化を目的とする場合には、direction='maximize' を指定します。

#  studyオブジェクトの作成(最大化)
study = optuna.create_study(direction='maximize')

# K分割交差検証の K
cv = 5

# 目的関数の最適化
study.optimize(lambda trial: objective(trial, x_train_val, t_train_val, cv), n_trials=10)

print(study.best_trial)

学習が終了したので、最も予測精度の高かったハイパーパラメータを確認するためにstudy.best_paramsを実行します。

# 最も予測精度の高かったハイパーパラメータの確認
study.best_params

# 最適なハイパーパラメータを設定したモデルの定義
best_model = DecisionTreeClassifier(**study.best_params)

# モデルの学習
best_model.fit(x_train_val, t_train_val)

# モデルの検証
print(best_model.score(x_train_val, t_train_val))
print(best_model.score(x_test, t_test))