【scikit-learn】グリッドサーチとランダムサーチでハイパーパラメータをチューニングする方法(GridSearchCV, RandomizedSearchCV)
.jpg)
Python の機械学習ライブラリである scikit-learn では、ハイパーパラメータをチューニングする方法としてグリッドサーチ (GridSearchCV) とランダムサーチ(RandomizedSearchCV) が用意されています。それぞれを使ったパラメータチューニングの方法について解説します。
目次
ハイパーパラメータのチューニング
機械学習の分野では、学習データから各種モデルの構築を行いますが、適用するモデルの種類により、人手により調整しなければならないパラメータが存在します。そういったパラメータの事をハイパーパラメータと言います。
このハイパーパラメータの値によりモデルの精度が変化するため、ハイパーパラメータを変更しつつ学習、評価を繰り返す必要があり、非常に時間と手間がかかります。このような作業をハイパーパラメータチューニングと言います。
手作業によるチューニングは負担が大きいため、自動でパラメータチューニングする方法があり、主な方法として グリッドサーチとランダムサーチの 2 つがあります。
scikit-learn では、以下のようにそれぞれ用意されています。
- グリッドサーチ:
GridSearchCV - ランダムサーチ:
RandomizedSearchCV
この記事では、グリッドサーチ、ランダムサーチそれぞれの方法の概要と違いをまず説明します。その後、scikit-learn の GridSearchCV、RandomizedSearchCV の使用例を紹介していきたいと思います。
グリッドサーチ
グリッドサーチは、調整したいハイパーパラメータの値の候補を明示的に複数指定してハイパーパラメータのセットを作成します。例えば以下のような感じです。
- パラメータ 1:
[1, 2, 3, 4, 5] - パラメータ 2:
[True, False] - パラメータ 3:
[0, 0.5, 1.0]
このように、事前に用意しておいたパラメータに対してグリッドサーチでは全ての組み合わせに対して学習と評価を繰り返し、最もよいパラメータを選びます。
上記の例だと、パラメータ 1 が 5 個、パラメータ 2 が 2 個、パラメータ 3 が 3 個の値を持っているので、5 × 2 × 3 = 30 通りの組み合わせで評価されます。
この方法は、パラメータの値にある程度見当がついている場合に向いています。また、計算量が多くなるため大きなデータセットに対して適用することはあまりなく後述するランダムサーチの方が使われることが多いです。
ランダムサーチ
ランダムサーチは、調整したいハイパーパラメータが取り得る値の範囲を指定します。範囲指定のパラメータでは、確率で決定されたパラメータを用いるので、グリッドサーチは離散値ですが、ランダムサーチでは連続値をとることができるのが特徴です。
範囲指定をする場合には、値を選択するための確率関数を指定することになります。後ほど scikit-learn を用いたランダムサーチの例を紹介しますが、確率関数としてはscipy.stats のモジュールがよく使用されます。
グリッドサーチとランダムサーチの特徴の違い
グリッドサーチとランダムサーチの特徴の違いを簡単に整理しておきます。
| 手法 | 特徴 |
|---|---|
| グリッドサーチ | ・True or False等の連続ではない値の組み合わせの探索に向いている。 ・調整する値に対してある程度見当がついている場合に向いている。 ・モデルの訓練回数が増えるので計算コストが高い。 |
| ランダムサーチ | ・連続な値に対しての探索にも対応できる。 ・調整する値に見当がつかない場合でも範囲をとって探索ができる。 ・ランダムに組み合わせを決めるのでグリッドサーチよりも計算コストは低い。 ・確率的な探索のため適切なパラメータが見つけられない可能性がある。 |
グリッドサーチ (GridSearchCV) と
ランダムサーチ (RandomizedSearchCV) の使い方
グリッドサーチ (GridSearchCV) の使い方
実装例
scikit-learn の load_digits(手書き文字)データセットを SVM で分類する例を使って、グリッドサーチを実行する GridSearchCV の使い方を紹介します。以下の例は処理時間がかかるので注意してください。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
def main():
"""メイン関数"""
seed = 0
np.random.seed(seed)
# ===== データの生成
data = load_digits()
# 学習データとテストデータに分割
train_data, test_data, train_label, test_label = train_test_split(
data.data, data.target, random_state=seed
)
# ===== モデルのハイパーパラメータ候補値を設定する
model_param_set = {
SVC(): {
"kernel": ["linear", "poly", "rbf", "sigmoid"],
"C": [10**i for i in range(-5, 5)],
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
}
}
# ===== グリッドサーチ
max_score = 0
best_param = None
for model, param in model_param_set.items():
# グリッドサーチを生成
clf = GridSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])
print(f"ハイパーパラメータ: {best_param}")
print(f"最高スコア: {max_score}")
if __name__ == "__main__":
main()【実行結果】
ハイパーパラメータ: {'C': 10, 'decision_function_shape': 'ovo', 'kernel': 'rbf', 'random_state': 0}
最高スコア: 0.9911111111111112実装内容の解説
上記で紹介した実装例の各部分ごとに内容を解説していきます。
必要モジュールのインポート
from sklearn.model_selection import GridSearchCV
グリッドサーチを使用するには sklearn.model_selection から GridSearchCV をインポートします。他にも numpy 等インポートしていますがそちらの説明は省略します。
データセットの準備
# ===== データの生成
data = load_digits()
# 学習データとテストデータに分割
train_data, test_data, train_label, test_label = train_test_split(
data.data, data.target, random_state=seed
)データは load_digits() により読み込みます。学習データとテスト用のデータを分割するために train_test_split を使用しています。
ハイパーパラメータセットの設定
# ===== モデルのハイパーパラメータ候補値を設定する
model_param_set = {
SVC(): {
"kernel": ["linear", "poly", "rbf", "sigmoid"],
"C": [10**i for i in range(-5, 5)],
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
}
}モデルとパラメーターの組み合わせを指定するための辞書を準備します。モデルのクラスインスタンスをキーにして、パラメータのキーとリストを持つ辞書を値として指定しています。
今回は、SVM のモデルである SVC() を指定しています。パラメータとしては、kernel、C 等の値をリストで指定している形です。
グリッドサーチの実行
# ===== グリッドサーチ
max_score = 0
best_param = None
for model, param in model_param_set.items():
# グリッドサーチを生成
clf = GridSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_['params'])
print(f"ハイパーパラメータ: {best_param}")
print(f"最高スコア: {max_score}")上記の部分でグリッドサーチを実行しています。先ほど指定したパラメータセットから順にモデル・パラメータを取り出します。
グリッドサーチを実行するには GridSearchCV に対象とするモデルとパラメータ辞書をGridSearchCV(model, param) のように指定します。後は scikit-learn の訓練のメソッドである fit を実行すると、param に指定されているパラメータセットを利用してベストなパラメータの探索を行いつつ学習します。
predict メソッドは、GridSearchCV が見つけたベストパラメータを使って予測をします。また、予測の正解率 (accuracy)を accuracy_score を利用して計算し、score に格納しています。
max_score を更新する部分がありますが、ここは後述する複数モデルの比較にそのまま使うことを意識して作成しています。(上記例ではモデルは SVC() の 1 つなので for 文は 1 回しかループしません)
【補足:パラメータの組み合わせの確認】
実際にどのようなパラメータセットで評価されているのかを確認しましょう。実行時の情報などは cv_results_ の中に入っています。以下の部分をコメントアウトしていますが、コメントアウトを外して実行してみてください。
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])実行すると以下のような辞書のリストが取得できます。
[
{'C': 1e-05, 'decision_function_shape': 'ovo', 'kernel': 'linear', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovo', 'kernel': 'poly', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovo', 'kernel': 'rbf', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovo', 'kernel': 'sigmoid', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovr', 'kernel': 'linear', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0},
{'C': 1e-05, 'decision_function_shape': 'ovr', 'kernel': 'sigmoid', 'random_state': 0},
{'C': 0.0001, 'decision_function_shape': 'ovo', 'kernel': 'linear', 'random_state': 0},
{'C': 0.0001, 'decision_function_shape': 'ovo', 'kernel': 'poly', 'random_state': 0},
・・・途中略・・・
{'C': 1000, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0},
{'C': 1000, 'decision_function_shape': 'ovr', 'kernel': 'sigmoid', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovo', 'kernel': 'linear', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovo', 'kernel': 'poly', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovo', 'kernel': 'rbf', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovo', 'kernel': 'sigmoid', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovr', 'kernel': 'linear', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0},
{'C': 10000, 'decision_function_shape': 'ovr', 'kernel': 'sigmoid', 'random_state': 0}
]今回は 'kernel' が 4 個、'C' が 10 個、'decision_function_shape' が 2 個、random_state が 1 個のパラメータ数なので、4 × 10 × 2× 1 = 80 通りのパラメータの組み合わせが使われます。このようにグリッドサーチでは、全ての指定したパラメータの組み合わせが使用されることになるので、実行時間が長くなります。
cv_results_ は辞書になっていて、今回は param キーの値を取り出しました。他にも色々と結果情報がありますので、公式ドキュメントを確認しつつ結果の中身を見てみると面白いかと思います。
ランダムサーチ (RandomizedSearchCV) の使い方
実装例
同様に load_digits(手書き文字)データセットを SVM で分類する例を使って、ランダムサーチを実行する RandomizedSearchCV の使い方を紹介します。
import numpy as np
import scipy.stats
from sklearn.datasets import load_digits
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
def main():
"""メイン関数"""
seed = 0
np.random.seed(seed)
# ===== データの生成
data = load_digits()
# 学習データとテストデータに分割
train_data, test_data, train_label, test_label = train_test_split(
data.data, data.target, random_state=seed
)
# ===== モデルのハイパーパラメータ候補値を設定する
model_param_set = {
SVC(): {
"kernel": ["linear", "poly", "rbf", "sigmoid"],
"C": scipy.stats.uniform(0.00001, 10000),
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
}
}
# ===== ランダムサーチ
max_score = 0
best_param = None
for model, param in model_param_set.items():
# ランダムサーチを生成
clf = RandomizedSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])
print(f"ハイパーパラメータ: {best_param}")
print(f"最高スコア: {max_score}")
if __name__ == "__main__":
main()【実行結果】
ハイパーパラメータ: {'C': 4236.548003389047, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0}
最高スコア: 0.9911111111111112実装内容の解説
上記で紹介した実装例の各部分ごとに内容を解説していきます。グリッドサーチと重複する部分の説明については省略します。
上記で紹介した実装例の各部分ごとに内容を紹介していきます。
必要モジュールのインポート
from sklearn.model_selection import RandomizedSearchCV
ランダムサーチを使用するには、sklearn.model_selection から RandomizedSearchCV をインポートします。
ハイパーパラメータセットの設定
# ===== モデルのハイパーパラメータ候補値を設定する
model_param_set = {
SVC(): {
"kernel": ["linear", "poly", "rbf", "sigmoid"],
"C": scipy.stats.uniform(0.00001, 10000),
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
}
}ハイパーパラメータを設定している部分で、グリッドサーチと少し異なっているのは C の指定部分です。
ここでは、scipy.stats の uniform を使って 0.00001 ~ 10000 の範囲での一様分布を指定しています。このように確率分布を指定することで、RandomizedSearchCV は、分布に従うパラメータをサンプルしてくれます。もちろん他の確率分布を指定することも可能です。
ランダムサーチの実行
# ===== ランダムサーチ
max_score = 0
best_param = None
for model, param in model_param_set.items():
# ランダムサーチを生成
clf = RandomizedSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])
print(f"ハイパーパラメータ: {best_param}")
print(f"最高スコア: {max_score}")RandomizedSearchCV を使っているという点以外は、グリッドサーチの例と同じです。
【補足:パラメータの組み合わせの確認】
実行してみるとグリッドサーチに比べてすごく早いことに気づきます。ランダムサーチで確認したように比較したパラメータを表示してみましょう。以下のコメントアウトされている部分を外して実行してみてください。
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])以下のようなパラメータの組み合わせが表示されるかと思います。
[
{'C': 5488.135049273247, 'decision_function_shape': 'ovr', 'kernel': 'linear', 'random_state': 0},
{'C': 6027.633770716438, 'decision_function_shape': 'ovr', 'kernel': 'sigmoid', 'random_state': 0},
{'C': 4236.548003389047, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0},
{'C': 4375.8721226269245, 'decision_function_shape': 'ovo', 'kernel': 'linear', 'random_state': 0},
{'C': 9636.627615010293, 'decision_function_shape': 'ovo', 'kernel': 'poly', 'random_state': 0},
{'C': 7917.250390826645, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0},
{'C': 5680.445620939323, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 0},
{'C': 710.3605919788694, 'decision_function_shape': 'ovr', 'kernel': 'linear', 'random_state': 0},
{'C': 202.1839844032572, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 0},
{'C': 7781.567519498504, 'decision_function_shape': 'ovr', 'kernel': 'linear', 'random_state': 0}
]組み合わせの種類としては 10 通り出てくるのではないかと思います。この 10 というのは、RandomizedSearchCV の n_iter 引数のデフォルトで指定されている数字です。公式ドキュメントの引数を確認してみてください。
もし、試す組み合わせの数を増減させる場合には、以下のよう n_iter を明示的に指定して RandomizedSearchCV をインスタンス化してください。
# ランダムサーチを生成
clf = RandomizedSearchCV(model, param, n_iter=20)複数モデルを比較する方法
上記例では、グリッドサーチ、ランダムサーチのイメージをつかんでもらうために 1 つのモデルに対してパラメータを変えて実行してきましたが、複数のモデルとパラメータを試して最もよいモデルとパラメータを探索することもできます。
ここでは、その実装例を紹介します。今回はランダムサーチの例ですが、グリッドサーチでも同じように実装できます。
実装例
以下の実装例は、ランダムサーチを使って、非線形SVM、決定木、ランダムフォレストを使用する例です。
import numpy as np
import scipy.stats
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
def main():
"""メイン関数"""
seed = 0
np.random.seed(seed)
# ===== データの生成
data = load_digits()
# 学習データとテストデータに分割
train_data, test_data, train_label, test_label = train_test_split(
data.data, data.target, random_state=seed
)
# ===== モデルのハイパーパラメータ候補情報
model_param_set = {
# 非線形SVM
SVC(): {
"C": scipy.stats.uniform(0, 100000),
"kernel": ["rbf", "poly"],
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
},
# 決定木
DecisionTreeClassifier(): {
"max_depth": [i for i in range(1, 21)],
"random_state": [seed],
},
# ランダムフォレスト
RandomForestClassifier(): {
"n_estimators": [i for i in range(1, 51)],
"max_depth": [i for i in range(1, 21)],
"random_state": [seed],
},
}
# ===== ランダムサーチで最適モデルを調べる
max_score = 0
best_method = None
best_param = None
for model, param in model_param_set.items():
# print(model, param)
method = model.__class__.__name__
# ランダムサーチを生成
clf = RandomizedSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_method = method
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])
print("==========")
print(f"学習モデル: {best_method}")
print(f"パラメーター: {best_param}")
print(f"最高スコア: {max_score}")
print("==========")
if __name__ == "__main__":
main()【実行結果】
==========
学習モデル: SVC
パラメーター: {'C': 54881.35039273248, 'decision_function_shape': 'ovr', 'kernel': 'rbf', 'random_state': 0}
最高スコア: 0.9911111111111112
==========実装内容の解説
上記で紹介した実装例の各部分ごとに内容を解説していきます。これまでの説明と重複する部分については省略します。
ハイパーパラメータセットの設定
# ===== モデルのハイパーパラメータ候補情報
model_param_set = {
# 非線形SVM
SVC(): {
"C": scipy.stats.uniform(0, 100000),
"kernel": ["rbf", "poly"],
"decision_function_shape": ["ovo", "ovr"],
"random_state": [seed],
},
# 決定木
DecisionTreeClassifier(): {
"max_depth": [i for i in range(1, 21)],
"random_state": [seed],
},
# ランダムフォレスト
RandomForestClassifier(): {
"n_estimators": [i for i in range(1, 51)],
"max_depth": [i for i in range(1, 21)],
"random_state": [seed],
},
}パラメータの辞書を用意するときに、複数のモデルインスタンスをキーに設定します。今回の例では SVC()、DecisionTreeClassifier()、RandomForestClassifier() をキーとして設定し、それぞれのパラメータ辞書を値としてセットしています。
今回は 3 つのモデルですが、ロジスティック回帰や K 近傍法等他のモデルを同じように追加していけば比較の対象を増やしていくことができます。
ランダムサーチの実行
# ===== ランダムサーチで最適モデルを調べる
max_score = 0
best_method = None
best_param = None
for model, param in model_param_set.items():
# print(model, param)
method = model.__class__.__name__
# ランダムサーチを生成
clf = RandomizedSearchCV(model, param)
# 訓練の実行
clf.fit(train_data, train_label)
# データの予測と正解率の計算
pred = clf.predict(test_data)
score = accuracy_score(test_label, pred)
# 最高スコアの更新
if max_score < score:
max_score = score
best_method = method
best_param = clf.best_params_
# # パラメータの組み合わせを確認
# print(clf.cv_results_["params"])
print("==========")
print(f"学習モデル: {best_method}")
print(f"パラメーター: {best_param}")
print(f"最高スコア: {max_score}")
print("==========")上記の部分については、後でベストだったモデル名を取得できるように method に実行モデル名を格納している以外はこれまでのソースと同じです。
今回の結果としては非線形 SVM が最もよいモデルであるということが分かりました。
このように、グリッドサーチやランダムサーチをうまく使うと複数モデルに対して各パラメータの探索をしつつモデル評価をすることができ、非常に便利です。是非グリッドサーチ、ランダムサーチの使い方を覚えてみてください。
まとめ
機械学習のハイパーパラメータチューニングを自動で行う方法としてグリッドサーチとランダムサーチという方法について概要を説明しました。
また、scikit-learn で自動でハイパーパラメータチューニングができる GridSearchCV と RondomizedSearchCV の使い方を紹介しました。
グリッドサーチは、ハイパーパラメータにある程度見当がついているときやパラメータ数が少ない場合には適していますが、計算コストが高くなる特徴があります。
一方、ランダムサーチは値の範囲を指定して確率的に値をとるため、値に見当がついていない場合など範囲をとって確認ができ、計算コストもグリッドサーチよりは少なくなります。ただし、確率的に探索するため、運任せな部分があり適切なパラメータを見つけられない可能性もあります。
適用するモデルのハイパーパラメータの種類、データセットのサイズ、計算機の性能などを考慮して、どちらを使うかを検討してみてください。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。


の使用方法.jpg)
とランダムフォレスト(RandomForest)の使用方法.jpg)
を用いたデータ分類方法.jpg)


