結論

ハイパーパラメータとは、学習を始める前に人が決める設定値(SVM の C・決定木の深さ・学習率など)で、チューニングとは、その値の組み合わせを交差検証のスコアで比べて選ぶ作業です。探し方は「グリッドサーチ(全組み合わせ)」「ランダムサーチ(無作為に選ぶ)」「Optuna などのベイズ最適化(結果を見て次を選ぶ)」の3つが基本で、数が2つまでならどれでも差は小さく、3つ以上ならランダムか Optuna が有利です。どの方法でも、探索範囲を対数スケールにすることと、評価用のデータを探索に使わないことが、方法の違いより効きます。

この記事でわかること

  • グリッド・ランダム・Optuna(TPE)を同じ試行回数で5回ずつ比べた最良スコア・テストの正解率・時間
  • グリッドサーチとランダムサーチの候補選びを自作し、GridSearchCV・RandomizedSearchCV と誤差0で一致させた結果
  • 探索で良く見えたスコアが本当の性能より高くなる実例と、入れ子の交差検証・Successive Halving・枝刈りの実測

2026年11月5日更新|Python 3.13.18・scikit-learn 1.9.1・Optuna 5.0.0 で実行確認

この記事で実際に動かしたもの:scikit-learn 同梱の load_digits(手書き数字)・load_breast_cancer(乳がんの診断データ)・load_iris と、numpy で作った架空のデータを使い、SVM・HistGradientBoostingClassifier・決定木・SGDClassifier のハイパーパラメータを GridSearchCV・RandomizedSearchCV・HalvingRandomSearchCV・Optuna 5.0.0 で探して、スコアと時間を測りました。

機械学習のモデルは、同じデータ・同じアルゴリズムでも、設定値ひとつで正解率が0.16から0.98まで変わります(下の実測)。その設定値がハイパーパラメータです。この記事では、図と1行の答えから入り、後半で3つの探し方を同じ条件で比べた数値、自作のコード、評価の落とし穴、どれを使うかの判断表まで進みます。

flowchart TD
    A[データを開発用と<br>評価用に分ける] --> B[探す範囲を決める<br>桁で変わる値は対数]
    B --> C{ハイパーパラメータ<br>の数}
    C -->|1〜2個| G[グリッドか<br>ランダム]
    C -->|3個以上| R[ランダムか<br>Optuna]
    G --> V[交差検証の<br>スコアで選ぶ]
    R --> V
    V --> T[評価用データで<br>最後に1回測る]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
ハイパーパラメータとは 学習の前に人が決める設定値。学習で決まる重み・係数(パラメータ)とは別物 ハイパーパラメータとは
チューニングの方法 グリッドサーチ・ランダムサーチ・ベイズ最適化(Optuna の TPE など)。早期打ち切りを組み合わせる 同じ予算で比較
グリッドサーチとランダムサーチの違い グリッドは決めた候補の全組み合わせ、ランダムは範囲から決めた回数だけ無作為に選ぶ ランダムサーチが効く理由
どれがいちばん良いか 2つなら差は誤差の範囲。5つでは Optuna の交差検証スコアが最も高かったが、テストの正解率は差が出なかった 同じ予算で比較
Optuna とは 結果を見ながら次の候補を選ぶ Python のライブラリ。既定は TPE で、最初の10回はランダム Optuna の仕組みと使い方
探索範囲の決め方 C・gamma・学習率・正則化のように桁で効く値は対数スケール(loguniform・log=True) 探索範囲の決め方
best_score_ は信じてよいか 選んだ時点で少し高めに出る。性能の報告は評価用データか入れ子の交差検証で 入れ子の交差検証
時間がかかりすぎる Successive Halving や Optuna の枝刈りで、見込みの無い候補を早く打ち切る 早期打ち切り
Invalid parameter と出る Pipeline の中は「工程名__名前」で指定する(svc__C) よくあるエラー

確認環境:Ubuntu 24.04.5 LTS(CPU 2コア・メモリ8GB、ほかの作業と同時に動いている共有のマシン)の上で、Python 3.13.18、scikit-learn 1.9.1、numpy 2.5.3、scipy 1.18.1、pandas と、作業用の仮想環境に pip で入れた Optuna 5.0.0 を使いました。BLAS のスレッド数は1に固定し(OPENBLAS_NUM_THREADS=1)、並列実行はせず(n_jobs=1)、1回の計算は60秒以内に収めています。時間の数値は共有のマシンで測ったため、同じ処理でも2〜3割ぶれます(比べるときは同じ条件の中で比べています)。データは scikit-learn 同梱のデータセットと numpy で作った架空のデータだけで、ネットからのデータ取得はしていません。GridSearchCV・RandomizedSearchCV・Successive Halving の仕様は scikit-learn 1.9.1 の公式ドキュメント「Tuning the hyper-parameters of an estimator」、Optuna の既定値は Optuna 5.0.0 の公式ドキュメントと手元のソースコード、ランダムサーチの根拠は Bergstra と Bengio の論文で確かめました。図は mermaid で描画を確かめています(確認日 2026年11月5日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ハイパーパラメータチューニングとは|グリッド・ランダム・Optunaを同予算で比較」

ハイパーパラメータとは(パラメータとの違い)

この節では、ハイパーパラメータの意味と、学習で決まるパラメータとの違い、モデルごとの代表的なハイパーパラメータが分かります。

ハイパーパラメータとは、モデルが学習を始める前に、人(またはチューニングの仕組み)が決めておく設定値です。線形回帰の係数やニューラルネットワークの重みは、データから自動で決まる「パラメータ」です。一方、正則化の強さ・木の深さ・学習率のように、学習のやり方そのものを決める値は、データからは直接決まりません。scikit-learn のドキュメントも、ハイパーパラメータを「推定器の中で直接は学習されないパラメータ」で、コンストラクタの引数として渡すもの、と説明しています。

パラメータ

学習でデータから決まる

例:回帰の係数・切片、ニューラルネットワークの重み、SVM のサポートベクター

scikit-learn では学習後に coef_ のように末尾に _ が付く属性

ハイパーパラメータ

学習の前に人が決める

例:SVM の C と gamma、決定木の max_depth、学習率、木の本数

scikit-learn では SVC(C=10) のようにコンストラクタに渡す

同じ SVM でも、ハイパーパラメータで成績が大きく変わります。load_digits に SVM を当てて、C と gamma だけを変えて5分割の交差検証で測った結果です。

C gamma 交差検証の正解率 1回の交差検証の時間
10.010.97370.30秒
100.0010.97220.18秒
100010.15750.75秒
0.010.00010.24970.62秒

10クラスの分類なので、当てずっぽうの正解率は約0.1です。設定を誤ると、それに近いところまで落ちます。逆に、良い範囲に入ってしまえば、その中での差は小さくなります。この「良い範囲を早く見つけ、その中で欲張りすぎない」がチューニングの考え方の芯です。

モデルごとに、まず触るハイパーパラメータは決まっています。

1

SVM(SVC)

C(誤分類への罰の強さ)と gamma(RBF カーネルの効く範囲)。どちらも桁で効く。

対数スケールで2つを同時に

2

勾配ブースティング

learning_rate・木の大きさ(max_leaf_nodes・max_depth)・min_samples_leaf・正則化・木の本数。

数が多いのでランダムか Optuna

3

ロジスティック回帰・Ridge

正則化の強さ(C または alpha)がほぼ1つだけ。

LogisticRegressionCV などの専用クラスが速い

4

ニューラルネットワーク

学習率・層の大きさ・バッチサイズ・エポック数・正則化。1回の学習が重い。

枝刈り(早期打ち切り)と組み合わせる

学習率やエポック数が学習の中でどう効くかは、関連記事「ニューラルネットワークとは|仕組み・学習・AttentionをNumPyで実演」で扱っています。

最初の5分:GridSearchCVでハイパーパラメータを探す

この節では、scikit-learn の GridSearchCV で C と gamma の9通りを探し、結果の表(cv_results_)を読むまでの最小の手順が分かります。

手書き数字のデータを、開発用(70%)と評価用(30%)に分けます。探索は開発用のデータだけで行い、評価用のデータは最後に1回だけ使います。scikit-learn の公式ドキュメントも、データを「開発用」と「評価用」に分けることを勧めています。

import pandas as pd
from sklearn.datasets import load_digits
from sklearn.model_selection import GridSearchCV, StratifiedKFold, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

X, y = load_digits(return_X_y=True)              # 手書き数字 1,797件・64列
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=0)

pipe = make_pipeline(StandardScaler(), SVC())    # 標準化 → SVM
grid = {"svc__C": [0.1, 1, 10], "svc__gamma": [0.001, 0.01, 0.1]}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)

search = GridSearchCV(pipe, grid, cv=cv, scoring="accuracy")
search.fit(X_tr, y_tr)                           # 9通り × 5分割 = 45回の学習 + 最後に1回

print("best_params_:", search.best_params_)
print("best_score_ :", round(search.best_score_, 4))
print("test score  :", round(search.score(X_te, y_te), 4))
r = pd.DataFrame(search.cv_results_)
print(r[["param_svc__C", "param_svc__gamma", "mean_test_score",
         "std_test_score", "rank_test_score", "mean_fit_time"]].round(4).to_string(index=False))

実行結果です。

best_params_: {'svc__C': 10, 'svc__gamma': 0.01}
best_score_ : 0.9769
test score  : 0.9833
 param_svc__C  param_svc__gamma  mean_test_score  std_test_score  rank_test_score  mean_fit_time
          0.1             0.001           0.3779          0.0740                8         0.0548
          0.1             0.010           0.9268          0.0132                7         0.0408
          0.1             0.100           0.3237          0.0620                9         0.0609
          1.0             0.001           0.9356          0.0095                4         0.0301
          1.0             0.010           0.9737          0.0020                2         0.0235
          1.0             0.100           0.9276          0.0248                6         0.0735
         10.0             0.001           0.9722          0.0067                3         0.0285
         10.0             0.010           0.9769          0.0046                1         0.0256
         10.0             0.100           0.9300          0.0248                5         0.0775

読み方は次のとおりです。

  • best_params_:交差検証の平均スコアが最も高かった組み合わせ。C=10・gamma=0.01。
  • best_score_:その組み合わせの交差検証の平均(0.9769)。探索に使ったスコアなので、本当の性能より少し高めに出ます(後の節で実測します)。
  • search.score(X_te, y_te):既定の refit=True により、最良の組み合わせで開発用データ全体を学習し直したモデルの、評価用データでの正解率(0.9833)。
  • std_test_score:5つの分割のばらつき。1位(0.9769)と2位(0.9737)の差0.0032は、標準偏差0.0046より小さく、この2つの優劣ははっきりしません。

Pipeline に入れた SVC のハイパーパラメータは、svc__C のように「工程名+アンダースコア2つ+名前」で指定します。make_pipeline は工程名をクラス名の小文字(svc・standardscaler)にします。標準化を Pipeline の中に入れておくと、交差検証の分割ごとに訓練側だけで平均と標準偏差を計算するので、検証側の情報が漏れません。

グリッドサーチを自作してGridSearchCVと一致させる

この節では、グリッドサーチとランダムサーチの中身を numpy と for 文で書き、scikit-learn の結果と誤差0で一致させることで、何をしているかが分かります。

グリッドサーチがしていることは、「候補の全組み合わせ × 交差検証の分割」の二重ループで、毎回新しいモデルを学習してスコアの平均を取るだけです。

import itertools
import numpy as np
from sklearn.base import clone

def my_grid_search(est, grid, X, y, cv):
    keys = sorted(grid)                       # 名前順に並べる(ParameterGrid と同じ順)
    folds = list(cv.split(X, y))
    results = []
    for values in itertools.product(*(grid[k] for k in keys)):
        params = dict(zip(keys, values))
        scores = []
        for tr, va in folds:
            m = clone(est).set_params(**params).fit(X[tr], y[tr])
            scores.append(np.mean(m.predict(X[va]) == y[va]))   # 正解率
        results.append((params, np.mean(scores), np.std(scores)))
    best = max(range(len(results)), key=lambda i: results[i][1])  # 同点なら先のもの
    return results, best

上の「最初の5分」と同じデータ・同じ分割・同じ9通りで、GridSearchCV と比べました。

候補の順番が同じ: True
平均スコアの最大差: 0.0
標準偏差の最大差: 0.0
自作の最良: {'svc__C': 10, 'svc__gamma': 0.01} 0.9769
sklearn の最良: {'svc__C': 10, 'svc__gamma': 0.01} 0.9769
時間 自作 3.72s / GridSearchCV 4.13s(refit 込み)

9通りすべての平均と標準偏差が、浮動小数点の誤差もなく一致しました。GridSearchCV の価値は計算の中身ではなく、分割の管理・Pipeline との組み合わせ・並列化(n_jobs)・失敗した組み合わせの扱い(error_score)・最良での学習し直し(refit)をまとめて正しく行うことにあります。

ランダムサーチの候補選びも自作して一致させる

RandomizedSearchCV の候補は ParameterSampler が作ります。scikit-learn 1.9.1 のソースでは、乱数生成器を1つ作り、候補ごとに名前順に分布から1つずつ値を引いています。対数一様分布(loguniform)は「対数を取った値が一様」な分布なので、numpy では exp(uniform(log(下限), log(上限))) で同じ値が出せます。

import numpy as np
from scipy.stats import loguniform
from sklearn.model_selection import ParameterSampler

dist = {"svc__gamma": loguniform(1e-4, 1), "svc__C": loguniform(1e-2, 1e3)}
sk = list(ParameterSampler(dist, n_iter=5, random_state=0))

rng = np.random.RandomState(0)
mine = []
for _ in range(5):
    p = {}
    for k, (lo, hi) in sorted({"svc__gamma": (1e-4, 1), "svc__C": (1e-2, 1e3)}.items()):
        p[k] = float(np.exp(rng.uniform(np.log(lo), np.log(hi))))
    mine.append(p)
{'svc__C': 5.547119, 'svc__gamma': 0.07257} {'svc__C': 5.547119, 'svc__gamma': 0.07257}
{'svc__C': 10.32326, 'svc__gamma': 0.015119} {'svc__C': 10.32326, 'svc__gamma': 0.015119}
{'svc__C': 1.313028, 'svc__gamma': 0.038333} {'svc__C': 1.313028, 'svc__gamma': 0.038333}
{'svc__C': 1.541473, 'svc__gamma': 0.369056} {'svc__C': 1.541473, 'svc__gamma': 0.369056}
{'svc__C': 658.133204, 'svc__gamma': 0.003418} {'svc__C': 658.133204, 'svc__gamma': 0.003418}

左が scikit-learn、右が自作で、5候補とも一致しました。辞書を書いた順(gamma が先)ではなく名前順(C が先)に値が引かれる点に注意します。後の節で、Optuna の最初の10回もこれと同じ値になることを確かめます。

グリッド・ランダム・Optunaを同じ予算で比較

この節では、3つのチューニング方法を同じ試行回数で比べた最良スコア・評価用データでの正解率・時間が分かります。

比べ方をそろえるため、どの方法も同じ開発用データ・同じ5分割(StratifiedKFold、shuffle=True、random_state=0)・同じ試行回数(予算)で探し、最良の組み合わせで学習し直したモデルを評価用データで1回だけ測りました。ランダムと Optuna は乱数の種(seed)を0〜4の5通りで回し、運の良し悪しも見ています。グリッドは乱数を使わないので1回です。

ハイパーパラメータが2つの場合(SVM・load_digits・25回)

C は 0.01〜1000、gamma は 0.0001〜1 の範囲を対数スケールで探しました。グリッドはそれぞれ5点(5×5=25通り)、ランダムは loguniform から25回、Optuna は suggest_float(..., log=True) の TPE で25回です。

方法(25回) 交差検証の最良(5回の平均・幅) 評価用データの正解率(平均) 時間(平均)
グリッドサーチ(5×5)0.97850.983320.1秒
ランダムサーチ0.9788(0.9777〜0.9801)0.984425.1秒
Optuna(TPE)0.9795(0.9785〜0.9801)0.983313.2秒

交差検証の最良の差は最大でも0.0010です。開発用データは1,257件なので、これは1〜2件の正解の差で、分割のばらつき(標準偏差0.005前後)より小さい差です。評価用データ(540件)でも、3つとも0.980〜0.985の中に入りました。ハイパーパラメータが2つで範囲が適切なら、方法の違いはほとんど出ません。Optuna の時間が短いのは、後半で良い範囲(学習が速い組み合わせ)を集中して試したためで、方法そのものの速さではありません。

ハイパーパラメータが5つの場合(勾配ブースティング・load_breast_cancer・32回)

HistGradientBoostingClassifier(木の本数60で固定)の learning_rate・max_leaf_nodes・min_samples_leaf・l2_regularization・max_features の5つを探しました。グリッドは各2点(2の5乗=32通り)、ランダムと Optuna は同じ範囲から32回です。

方法(32回) 交差検証の最良(5回の平均・幅) 評価用データの正解率(平均・幅) 時間(平均)
グリッドサーチ(2⁵)0.97240.96498.2秒
ランダムサーチ0.9739(0.9724〜0.9749)0.9497(0.9415〜0.9591)9.6秒
Optuna(TPE)0.9789(0.9748〜0.9824)0.9485(0.9415〜0.9591)6.6秒
0.9789Optuna の交差検証の最良(平均)ランダムより0.005、グリッドより0.0065高い
0.9485Optuna の評価用データの正解率(平均)ランダム0.9497・グリッド0.9649と差なし
0.0058評価用データ171件での1件ぶんの正解率差の大半は数件の当たり外れ

5つになると、Optuna は交差検証のスコアをはっきり高くしました。5回とも、ランダムの最良(平均)以上です。ところが評価用データでは、Optuna もランダムもグリッドより低く出ました。これは Optuna が悪いのではなく、交差検証のスコアを上げるほど、その398件の分け方にたまたま合う組み合わせも選ばれやすくなるためです。評価用データが171件と少なく、1件の当たり外れで正解率が0.0058動くことも効いています。この「探索で良く見えたスコアは割り引いて読む」は、後の入れ子の交差検証の節で、答えのない乱数データを使ってはっきり見せます。

何回目で最良に届いたか

各方法の「その回までの最良」を、5回の平均で並べました(ハイパーパラメータ5つの場合)。

試行回数 ランダムサーチ Optuna(TPE)
5回0.96880.9688
10回0.97230.9698
15回0.97230.9709
20回0.97340.9734
25回0.97390.9784
30回0.97390.9789

Optuna の TPE は、既定で最初の10回をランダムに選び、11回目から過去の結果を使います(n_startup_trials=10)。差が開いたのは20回を過ぎてからです。試行回数が10〜20回しか取れないなら、Optuna とランダムの差はほぼ出ません。実は今回の条件では、SVM の場合、Optuna の最初の10回の候補は RandomizedSearchCV と同じ seed で完全に同じ値でした(手元で12回分を照合し、1〜10回目が一致、11回目から分かれた)。どちらも numpy の RandomState で、名前順に対数一様分布から引いているためです。

ランダムサーチが効く理由(効くハイパーパラメータは少ない)

この節では、同じ回数ならグリッドよりランダムが有利になりやすい理由を、数値の実験と論文の根拠で確かめます。

Bergstra と Bengio の論文「Random Search for Hyper-Parameter Optimization」(JMLR、2012年)は、「多くのデータセットでは、本当に効くハイパーパラメータは少数」で、どれが効くかはデータセットごとに違う、と報告しています。グリッドは、効かない軸にも同じだけ点を置くため、効く軸の値を少ししか試せません。scikit-learn のドキュメントも、ランダムサーチの利点として「予算をパラメータの数と独立に決められる」「性能に影響しないパラメータを足しても効率が落ちない」の2点を挙げています。

これを、答えが分かっている関数で確かめました。4つの値(どれも0〜1)のうち、x0 だけが強く効き(0.37付近に細い山)、x1 が少し効き、x2・x3 は無関係な関数です。予算は81回で、グリッドは各3点(3の4乗=81)、ランダムと Optuna は20通りの seed で回しました。

def f(x):  # x0 だけが強く効き、x1 は少し、x2 x3 は無関係(最大は約1.05)
    return np.exp(-((x[0] - 0.37) / 0.05) ** 2) + 0.05 * np.exp(-((x[1] - 0.8) / 0.3) ** 2)
グリッド 81点: x0 の値の種類 3  最良 0.0332
random: 平均 1.0121 最小 0.9691 最大 1.0479  0.9以上の割合 1.00
tpe: 平均 1.0342 最小 0.9999 最大 1.0495  0.9以上の割合 1.00
ランダムの x0 の値の種類 {81}

1グリッドは効く軸を3通りしか試せない

81点のうち、x0 の値は 0・0.5・1 の3種類だけ。山(0.37)を挟んで外し、最良は0.0332でした。残りの78点は、無関係な x2・x3 を変えていただけです。

2ランダムは全部の軸を毎回違う値で試す

81回で x0 の値が81種類。20回すべてで0.9以上(山の上)に届きました。どの軸が効くかを事前に知らなくてよいのが強みです。

3TPE は山を見つけた後に詰める

平均1.0342、最悪でも0.9999。ランダムで山を見つけた後、その周りを集中して試すので、最後のひと詰めが速い。

4グリッドの利点もある

結果が再現しやすく、表に並べて傾向を読みやすい。ハイパーパラメータが1〜2個で、候補が数通りに決まっているときは今も有力です。

この関数は差を見やすくするために作ったもので、山がこれほど細いことは実際のモデルでは多くありません。ただ、前の節の5つの例でも、グリッドの最良(0.9724)は5回のランダム(0.9724〜0.9749)の最低と同じでした。グリッドを各2点で組むと、learning_rate を 0.03 と 0.3 の2通りしか試していないことになります。

Optunaの仕組みと使い方(TPEでハイパーパラメータを探す)

この節では、Optuna の最小のコード、TPE が次の候補を選ぶ考え方、結果の保存と再開、重要度の出し方が分かります。

Optuna は、Preferred Networks が開発した、ハイパーパラメータの自動最適化のための Python のライブラリです。scikit-learn の SearchCV と違い、「1回の試行でスコアを返す関数(目的関数)」を書き、その中で trial.suggest_* を呼んで値を受け取ります。条件分岐(モデルの種類によって探す値を変える)も、普通の if 文で書けます。

import optuna
from sklearn.datasets import load_digits
from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

X, y = load_digits(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=0)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)

def objective(trial):
    C = trial.suggest_float("C", 1e-2, 1e3, log=True)        # 対数スケール
    gamma = trial.suggest_float("gamma", 1e-4, 1, log=True)
    model = make_pipeline(StandardScaler(), SVC(C=C, gamma=gamma))
    return cross_val_score(model, X_tr, y_tr, cv=cv).mean()  # 大きいほど良い値を返す

optuna.logging.set_verbosity(optuna.logging.WARNING)
study = optuna.create_study(direction="maximize",
                            sampler=optuna.samplers.TPESampler(seed=0))
study.optimize(objective, n_trials=25)

print("best_value :", round(study.best_value, 4))
print("best_params:", {k: round(v, 4) for k, v in study.best_params.items()})
best = make_pipeline(StandardScaler(), SVC(**study.best_params)).fit(X_tr, y_tr)
print("test score :", round(best.score(X_te, y_te), 4))
df = study.trials_dataframe()
print(df[["number", "value", "params_C", "params_gamma"]].round(4).tail(5).to_string(index=False))
best_value : 0.9801
best_params: {'C': 6.3614, 'gamma': 0.0037}
test score : 0.9833
 number  value  params_C  params_gamma
     20 0.9753   15.5425        0.0012
     21 0.9427    0.2249        0.0409
     22 0.9785   19.2528        0.0040
     23 0.8878    0.2827        0.0012
     24 0.9690   49.1878        0.0001

Optuna は GridSearchCV と違い、最良の値で学習し直したモデルを返しません。上のように best_params で自分で学習し直します。direction="maximize" を忘れると、既定の「最小化」になり、正解率の低い組み合わせを探してしまいます。

1

最初はランダムに試す

完了した試行が n_startup_trials(既定10)に届くまでは、ランダムサンプラーと同じように範囲から無作為に選ぶ。

ここまではランダムサーチと同じ

2

良い組と、それ以外の組に分ける

これまでの試行をスコア順に並べ、上位の少数を「良い組」、残りを「それ以外」に分ける。

上位の割合は試行数とともに変わる

3

それぞれの分布 l(x) と g(x) を作る

良い組の値の分布 l(x) と、それ以外の分布 g(x) を、値の周りに山を重ねた形(カーネル密度)で作る。

Optuna 5.0.0 の既定は複数の値をまとめて扱う multivariate

4

l(x)/g(x) が大きい値を次に試す

l(x) から候補を24個(n_ei_candidates)引き、l(x)/g(x) が最大のものを次の試行にする。良い組に多く、それ以外に少ない場所が選ばれる。

結果を足して2に戻る

Optuna 5.0.0 の既定値は、公式ドキュメントと手元のソースで確かめました。create_study() でサンプラーを指定しないと TPESampler、枝刈り(pruner)を指定しないと MedianPruner になります。TPESampler の multivariate は既定で「目的が1つなら有効」と自動で決まります。ほかに、ランダム(RandomSampler)・グリッド(GridSampler)・CMA-ES(CmaEsSampler、cmaes パッケージが必要)・ガウス過程(GPSampler、PyTorch が必要)などのサンプラーがあります。今回は PyTorch を入れていないため、GPSampler は動かしていません。

サンプラーにかかる時間

目的関数が一瞬で終わる5変数の2次関数で、300回にかかった時間です。

RandomSampler: 300試行 0.07s  best=3.4707
TPESampler: 300試行 1.04s  best=0.0453

TPE は次の候補を選ぶ計算に1回あたり約3.5ミリ秒かかりますが、1回の学習が0.1秒を超えるなら無視できる大きさです。逆に、1回が数ミリ秒の軽いモデルを数万回試すなら、ランダムのほうが時間あたりの試行数が多くなります。

SQLite に保存して途中から再開する

study = optuna.create_study(study_name="hgb", storage="sqlite:///study.db",
                            direction="maximize")
study.optimize(objective, n_trials=15)
# 別のプロセスや翌日に
study = optuna.load_study(study_name="hgb", storage="sqlite:///study.db")
study.optimize(objective, n_trials=15)       # 続きの15回
importances = optuna.importance.get_param_importances(study)
読み込んだ時点の試行数 15
続けた後 30 best 0.9824 DBサイズ 143360 bytes
重要度 {'learning_rate': 0.594, 'max_features': 0.293, 'max_leaf_nodes': 0.076, 'min_samples_leaf': 0.032, 'l2_regularization': 0.005}

30回分の記録が140KBの1ファイルに残り、別のプロセスから読み込んで続きを回せました。重要度は Optuna 5.0.0 の既定(PedAnovaImportanceEvaluator)で、乳がんのデータの勾配ブースティングでは learning_rate が約6割を占め、l2_regularization はほぼ効いていませんでした。次に範囲を見直すとき、効かない値は固定し、効く値の範囲を広げる判断に使えます。ただし30回程度の結果からの推定なので、目安として読みます。

探索範囲の決め方(対数スケールにする)

この節では、C・gamma・学習率のような値を対数スケールで探すべき理由と、範囲の広さの決め方が分かります。

C を 0.01〜1000 の範囲で探すとき、普通の一様分布(uniform)で引くと、ほとんどの候補が100〜1000に集まります。1万回引いて、桁ごとの割合を数えました。

C の範囲 一様分布(uniform) 対数一様分布(loguniform)
0.01〜0.10.0%19.1%
0.1〜10.1%19.4%
1〜100.8%20.8%
10〜1009.4%20.7%
100〜100089.7%20.0%

これを実際の SVM(load_digits・25回)で比べると、差は大きく出ました。

範囲の取り方(25回) 最良の交差検証 全候補の中央値 gamma が0.05未満の候補
一様分布 seed=00.96260.22990/25
一様分布 seed=10.97220.21881/25
対数一様分布 seed=00.98010.939515/25
対数一様分布 seed=10.97770.467814/25

良い gamma は0.001〜0.02付近にあります。一様分布ではそこに候補がほとんど入らず、25回の大半が正解率0.2前後の「外れ」でした。scikit-learn のドキュメントも、対数一様分布を「対数で間隔を取った候補の連続版」と説明し、[1, 10, 100] の代わりに loguniform(1, 100) を使う例を載せています。分布そのものの性質は、関連記事「確率分布とは|種類一覧・平均と分散・選び方をPythonで実演」で扱っています。

  1. 1既定値のスコアを測る探索の前に、既定値で交差検証。これを下回る探索結果は範囲がずれている
  2. 2公式ドキュメントの既定値を中心に広く取る桁で効く値は既定値の前後2〜3桁(C=1 なら 0.01〜1000)を対数で
  3. 3粗く20〜30回試すランダムか Optuna。最良が範囲の端に張り付いたら、その向きへ範囲を広げる
  4. 4効かない値を固定する重要度や cv_results_ の傾向で、効かない値は既定値に戻して次元を減らす
  5. 5狭めた範囲でもう一度差が分割のばらつき(標準偏差)より小さくなったら止める

整数の値(木の深さ・葉の数)は randint や suggest_int を使います。木の本数のように「大きいほど良いが遅くなる」値は、探さずに固定するか、早期終了(early_stopping)に任せるほうが予算を有効に使えます。

交差検証の分け方(チューニングの評価をそろえる)

この節では、チューニングで使う交差検証の分け方の選び方と、分け方を誤ったときにスコアがどう崩れるかが分かります。

GridSearchCV などに cv=5 と整数を渡すと、分類では StratifiedKFold(クラスの割合を保つ)、回帰では KFold になり、どちらもシャッフルしません。データがラベル順に並んでいる場合の違いを、乳がんのデータを並べ替えて確かめました(ロジスティック回帰、各分割の正解率)。

ラベル順に並んだデータ
 KFold(5) シャッフルなし [0.895 0.965 0.991 0.982 0.973]
 StratifiedKFold(5)      [0.982 0.982 0.974 0.974 0.991]
 KFold(5,shuffle=True)   [1.    0.982 0.965 0.939 1.   ]

シャッフルしない KFold では、最初の分割の検証側が「悪性」に偏り、0.895まで下がりました。どの分割でもスコアの比べ方は同じなので、候補の順位はある程度保たれますが、ばらつきが大きいほど偶然の1位が選ばれやすくなります。チューニングでは、分割を1つ作って全候補・全方法で同じものを使う(上の比較では StratifiedKFold(5, shuffle=True, random_state=0))のが基本です。

データの性質 使う分割 理由
分類・偏りなしStratifiedKFold(shuffle=True)各分割のクラスの割合をそろえる
回帰KFold(shuffle=True)並び順の偏りを消す
同じ人・同じ機械の行が複数GroupKFold・StratifiedGroupKFold同じ発生源が訓練と検証にまたがるとスコアが高く出る
時系列TimeSeriesSplit未来のデータで過去を当てる形を防ぐ
件数が少ない(数百件)RepeatedStratifiedKFold分割を何回か変えて平均し、偶然の1位を減らす

スコアの指標も先に決めます。既定はモデルの score(分類は正解率、回帰は決定係数)ですが、陽性がまれな分類では正解率では差が見えません。scoring="roc_auc" や "f1" のように、業務で困る誤りに合った指標を指定します。過学習の見分け方と交差検証そのものの仕組みは、「過学習とは|原因と対策、交差検証・正則化・リークをPythonで実測」の記事で詳しく扱っています。

検証データに合わせすぎる問題と入れ子の交差検証

この節では、チューニングで選んだスコアが本当の性能より高く出る理由と、それを正しく測る入れ子の交差検証(nested cross-validation)が分かります。

たくさんの候補から交差検証のスコアが最も高いものを選ぶと、その中には「たまたまその分割に合った」候補も混ざります。候補が多いほど、この偶然の上乗せは大きくなります。これを、答えが純粋な乱数で、どんなモデルでも本当の正解率が0.5のデータで確かめました。100件・10列の乱数データを6通り作り、決定木の4つのハイパーパラメータを RandomizedSearchCV で探しています。

from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold, cross_val_score
inner = StratifiedKFold(5, shuffle=True, random_state=1)   # 探索用(内側)
outer = StratifiedKFold(5, shuffle=True, random_state=2)   # 評価用(外側)
search = RandomizedSearchCV(DecisionTreeClassifier(), dist, n_iter=n, cv=inner)
best_cv = search.fit(X, y).best_score_                       # 探索で見えたスコア
nested = cross_val_score(search, X, y, cv=outer).mean()     # 入れ子の外側のスコア
候補の数 探索の最良CV(6データの平均) 入れ子の外側(6データの平均) 本当の正解率
1個0.4850.4970.5
10個0.5720.5100.5
100個0.6230.5130.5

何も学べないデータなのに、候補を100個試すと best_score_ は0.623になりました。入れ子の外側のスコアは0.51前後のままで、本当の0.5に近い値を示しています。best_score_ は「選ぶための値」で、「性能として報告する値」ではありません。

1

外側でデータを k 個に分ける

1つを外側のテスト、残りを外側の訓練にする。

外側のテストは探索に一切使わない

2

外側の訓練の中だけでチューニング

外側の訓練を、さらに内側の交差検証で分けて、ハイパーパラメータを選ぶ。

GridSearchCV・RandomizedSearchCV がここ

3

選んだ設定を外側のテストで測る

内側で選んだ設定で外側の訓練全体を学習し、外側のテストで1回測る。

これを k 回繰り返して平均する

scikit-learn では、SearchCV のオブジェクトをそのまま cross_val_score や cross_validate に渡すだけで入れ子になります。公式ドキュメントも、交差検証のループの中でグリッドサーチを行う例「Nested versus non-nested cross-validation」を、チューニングしたモデルの性能を測る最善の方法として案内しています。実際のデータ(乳がん・勾配ブースティング・20候補)での結果です。

外側1: 内側の最良CV 0.9626  外側のテスト 0.9649  lr=0.269 leaves=5
外側2: 内側の最良CV 0.9714  外側のテスト 0.9737  lr=0.269 leaves=5
外側3: 内側の最良CV 0.9736  外側のテスト 0.9649  lr=0.176 leaves=50
外側4: 内側の最良CV 0.9758  外側のテスト 0.9649  lr=0.269 leaves=5
外側5: 内側の最良CV 0.9714  外側のテスト 0.9558  lr=0.172 leaves=54
内側の最良CVの平均 0.9710  入れ子の外側の平均 0.9648 ± 0.0057
探索前の設定の5分割CV 0.9613

内側の最良(0.9710)より、外側のテスト(0.9648)は0.006低く出ました。また、外側の分割ごとに選ばれた設定が違い(葉の数5と50前後)、このデータでは「これが正解」という1点は無く、良い範囲が広いことが分かります。探索前の設定(learning_rate などは既定値・木の本数60)の0.9613からの改善は0.0035で、探索の手間に見合うかは、この数字を見て判断できます。入れ子の交差検証は学習の回数が外側の分割数倍(今回は5倍)になるので、最後に1回、性能を報告するときに使います。

チューニングの結果の報告

やってはいけないbest_score_ をそのまま「このモデルの正解率」として報告する。評価用データを見ながら範囲を何度も直す
→
代わりに評価用データは最後に1回だけ測る。データが少ないときは入れ子の交差検証で測り、平均と標準偏差で報告する

評価用データで選び直すと、評価用データも「探索に使ったデータ」になる

早期打ち切り(Successive Halving・Optunaの枝刈り)でチューニングを速くする

この節では、見込みの無い候補を早く打ち切って時間を減らす2つの方法、scikit-learn の Successive Halving と Optuna の枝刈り(pruning)の実測が分かります。

HalvingRandomSearchCV(データ量を増やしながら候補を減らす)

Successive Halving は、最初は全候補を少ないデータで評価し、上位の1/factor だけを残して、データを factor 倍に増やして評価し直す、を繰り返す方法です。scikit-learn 1.9.1 では実験的な機能で、from sklearn.experimental import enable_halving_search_cv を先に書いてから使います(公式ドキュメントは、予告なく API が変わりうると明記しています)。

from sklearn.experimental import enable_halving_search_cv  # noqa
from sklearn.model_selection import HalvingRandomSearchCV
s = HalvingRandomSearchCV(pipe, dist, n_candidates=81, factor=3, cv=cv,
                          min_resources=60, random_state=0).fit(X_tr, y_tr)
print("n_candidates_", s.n_candidates_, "n_resources_", s.n_resources_)
n_candidates_ [81, 27, 9] n_resources_ [60, 180, 540] n_iterations_ 3
学習の回数 合計 585 (候補×段の延べ 117 ×5分割)
halving N=81 best_cv=0.9665 test=0.9852 time=3.8s best={'svc__C': 90.9128, 'svc__gamma': 0.013}
random N=81 best_cv=0.9801 test=0.9833 time=22.8s best={'svc__C': 4.0667, 'svc__gamma': 0.0046}

この段の組み立ては、自分で計算しても同じになります。

import math
def halving_schedule(n_candidates, min_resources, max_resources, factor=3):
    # 段の数:資源が足りる段数と、候補が1つ以下になるまでの段数の小さい方
    n_iter = min(int(math.log(max_resources / min_resources, factor)) + 1,
                 math.ceil(math.log(n_candidates, factor)) + 1)
    cands = [math.ceil(n_candidates / factor**i) for i in range(n_iter)]
    res = [min_resources * factor**i for i in range(n_iter)]
    return cands, res
候補の数: [81, 27, 9]  1候補あたりの件数: [60, 180, 540]
学習の回数(5分割): 585  普通のランダムサーチ81候補: 405
件数×候補の延べ: 14580  普通: 101817

学習の回数は585回と、普通のランダムサーチ(405回)より多いのに、時間は22.8秒から3.8秒(約6分の1)に減りました。1回あたりのデータが少ないためで、「件数×候補」の延べは14%でした。評価用データの正解率は0.9852と、普通のランダムサーチ(0.9833)と同等です。

注意点が2つあります。1つは、best_score_ は最後の段(540件で学習)のスコアなので、全件で学習した普通の探索の best_score_(0.9801)とは比べられないことです。もう1つは、少ないデータでの順位と全件での順位は一致するとは限らず、全件で良い設定が早い段で落とされることがある点です。今回も選ばれた C は90.9で、普通の探索(4.07)と違う設定になりました(評価用データの正解率は同等)。公式ドキュメントにあるとおり、資源にはデータ件数のほか、resource="n_estimators" のように木の本数も使えます。

Optuna の枝刈り(学習の途中で打ち切る)

Optuna では、目的関数の中で途中経過を trial.report() で報告し、trial.should_prune() が真なら optuna.TrialPruned を投げて打ち切ります。SGDClassifier を1エポックずつ partial_fit し、30エポックの検証の正解率を報告する例で、60回を3つの枝刈りで比べました。

def objective(trial):
    alpha = trial.suggest_float("alpha", 1e-6, 1e1, log=True)
    eta0 = trial.suggest_float("eta0", 1e-5, 1e0, log=True)
    clf = SGDClassifier(loss="log_loss", alpha=alpha, learning_rate="constant",
                        eta0=eta0, random_state=0)
    for ep in range(30):
        clf.partial_fit(X_tr, y_tr, classes=classes)
        acc = clf.score(X_va, y_va)
        trial.report(acc, ep)                 # 途中経過を報告
        if trial.should_prune():              # ほかの試行の同じ時点より悪ければ
            raise optuna.TrialPruned()        # ここで打ち切る
    return acc
NopPruner                best=0.9704 完了=60 打ち切り=0 学習したエポック合計=1800/1800 time=12.0s
MedianPruner             best=0.9704 完了=18 打ち切り=42 学習したエポック合計=871/1800 time=6.0s
SuccessiveHalvingPruner  best=0.9704 完了=7 打ち切り=53 学習したエポック合計=418/1800 time=3.0s

3つとも最良は0.9704で同じまま、学習したエポックは半分(MedianPruner)から4分の1弱(SuccessiveHalvingPruner)に減り、時間も12.0秒から3.0秒になりました。MedianPruner は「同じエポックでの、これまでの試行の中央値より悪ければ打ち切る」もので、n_startup_trials=5(最初の5試行は打ち切らない)・n_warmup_steps=5(最初の5エポックは打ち切らない)を指定しています。枝刈りは、エポックや木の本数のように「途中経過が取れる学習」でだけ効きます。SVM のように1回で学習が終わるモデルでは、途中経過が無いので効きません。

よくあるエラー・つまずき(ハイパーパラメータ探索の原文つき)

この節では、GridSearchCV・RandomizedSearchCV・Optuna でよく出るエラーと警告の原文、原因、直し方が分かります。すべて手元で再現した原文です。

原文(先頭) 原因 直し方
ValueError: Invalid parameter 'C' for estimator Pipeline(steps=[('standardscaler', StandardScaler()), ('svc', SVC())]). Valid parameters are: ['memory', 'steps', 'transform_input', 'verbose'].Pipeline の中のモデルの値を、工程名なしで指定したsvc__C のように「工程名__名前」。工程名は pipe.get_params().keys() で確かめる
InvalidParameterError: The 'scoring' parameter of GridSearchCV must be a str among {...}scoring の綴り違い(例:'acuracy')'accuracy' など、sklearn.metrics.get_scorer_names() にある名前にする
UserWarning: The total space of parameters 3 is smaller than n_iter=10. Running 3 iterations. For exhaustive searches, use GridSearchCV.候補がすべてリストで、組み合わせが n_iter より少ない連続の分布(loguniform など)にするか GridSearchCV を使う
ValueError: Domain error in arguments. The `scale` parameter must be positive for all distributions...loguniform(0, 10)。対数一様分布の下限に0は使えない下限を 1e-4 のように正の値にする
ValueError: `low > 0` must hold for `log=True`, but got (low=0, high=10).Optuna の suggest_float(..., log=True) の下限が0同上。0を含めたいなら log=False にするか、別の値で「なし」を表す
FitFailedWarning: 3 fits failed out of a total of 6. The score on these train-test partitions for these parameters will be set to nan.学習できない値(例:C=-1)が候補にある。既定では nan にして続けるerror_score="raise" で原因の例外を出して直す
ValueError: For multi-metric scoring, the parameter refit must be set to a scorer key or a callable...scoring に複数の指標を渡し、refit を指定していないrefit="f1_macro" のように、選ぶ基準の指標を1つ指定する
ValueError: No trials are completed yet.Optuna で完了した試行が無いのに best_params を読んだ(全試行が失敗・打ち切り)ログの Trial 0 failed with parameters の行で失敗の原因を見る
RuntimeWarning: Inconsistent parameter values for distribution with name "a"!1回の試行の中で、同じ名前を違う範囲で suggest した名前を分ける。Optuna は最初の呼び出しの範囲だけを使う

エラーにならないつまずきも多くあります。

  • Optuna で direction を書き忘れる:既定は minimize なので、正解率を返すと最悪の設定を探します。損失(小さいほど良い)を返すなら minimize のままで正しい動きです。
  • scikit-learn の誤差の指標は符号が逆:scoring="neg_mean_squared_error" は「大きいほど良い」にそろえるため負の値です。best_score_ が -12.3 なら MSE は12.3です。
  • 標準化を探索の外で全データに当てる:検証側の平均・標準偏差が訓練に漏れます。Pipeline に入れて SearchCV に渡します。
  • n_jobs=-1 を共有のマシンで使う:全コアを使い、ほかの処理を遅くします。BLAS のスレッドと重なると、かえって遅くなることもあります。
  • random_state を固定しない:ランダムサーチや TPE の結果が実行ごとに変わり、比べられません。比べるときは seed を何通りか回して幅を見ます。

ハイパーパラメータチューニングの方法の判断表

この節では、状況ごとにどのチューニング方法を選ぶかと、始める前の確かめ方が分かります。

状況 選ぶ方法 根拠(この記事の実測)
ハイパーパラメータが1〜2個、候補が数通りGridSearchCV2個・25回では3方式の差が0.001以内。表で傾向を読みやすい
3個以上、または連続の値RandomizedSearchCV(loguniform)5個・32回でグリッドの最良が、ランダム5回の最低と同じ
3個以上で、30回以上試せるOptuna(TPE)20回を過ぎてから交差検証の最良がランダムより約0.005高い
試行が10〜20回しか取れないRandomizedSearchCV(方式より範囲の見直しが効く)TPE の最初の10回はランダム。SVM の例では候補まで同じ
1回の学習が重く、データ件数で軽くできるHalvingRandomSearchCV81候補で22.8秒→3.8秒、テストの正解率は同等
エポック単位で途中経過が取れるOptuna の枝刈り(MedianPruner など)最良は同じまま、エポック1,800→418
条件分岐のある探索(モデルの種類ごとに違う値)Optuna目的関数の中で if 文で書ける
性能を報告する・データが少ない入れ子の交差検証乱数データで best_score_ 0.623、外側は0.513
  • 1評価用データを先に分けた探索・範囲の見直しには一切使わず、最後に1回だけ測る
  • 2既定値のスコアを測った改善の幅が分割の標準偏差より小さければ、探索をやめる判断もある
  • 3前処理を Pipeline に入れた標準化・特徴量選択・次元削減も分割ごとに訓練側だけで学習させる
  • 4桁で効く値を対数スケールにしたC・gamma・学習率・正則化は loguniform か log=True
  • 5分割と seed を固定した全候補・全方法で同じ分割。比べるときは seed を数通り回す
  • 6最良が範囲の端でないか見た端なら、その向きに範囲を広げてやり直す

主成分分析の成分数(n_components)も、Pipeline に入れれば同じ方法でハイパーパラメータとして探せます。成分数の意味は、関連記事「主成分分析(PCA)とは|手順・寄与率・負荷量の読み方をPythonで実演」で扱っています。

よくある質問

この節では、ハイパーパラメータとチューニングについて、よく聞かれる質問への短い答えが分かります。

Q. ハイパーパラメータとパラメータの違いを一言で言うと何ですか。
A. パラメータは学習でデータから決まる値(重み・係数)、ハイパーパラメータは学習の前に人が決める値(学習率・木の深さ・正則化の強さ)です。

Q. グリッドサーチとランダムサーチはどちらを使えばよいですか。
A. ハイパーパラメータが1〜2個で候補が決まっているならグリッド、3個以上か連続の値ならランダムです。効く値は少数のことが多く、ランダムのほうが効く値を多く試せます。

Q. Optuna を使えば必ず精度が上がりますか。
A. いいえ。手元の5変数の例では交差検証のスコアは上がりましたが、評価用データの正解率はランダム・グリッドと差がありませんでした。試行が10〜20回なら、TPE の最初の10回はランダムと同じ動きです。

Q. ベイズ最適化と Optuna の関係は何ですか。
A. ベイズ最適化は「これまでの結果から、良さそうな次の候補を確率のモデルで選ぶ」方法の総称です。Optuna の既定の TPE はその一種で、ガウス過程を使う GPSampler もあります。

Q. 試行回数は何回にすればよいですか。
A. 決まった答えはありません。目安として、ハイパーパラメータの数×10〜20回から始め、その回までの最良の推移が平らになったら止めます。今回の5変数では20〜30回で伸びが止まりました。

Q. best_score_ と評価用データのスコアが違うのはなぜですか。
A. best_score_ は多くの候補から最も高いものを選んだ値なので、偶然の上乗せを含みます。答えの無い乱数データでも、100候補で0.623になりました。性能の報告には評価用データか入れ子の交差検証を使います。

Q. 交差検証の分割数(cv)はいくつにすればよいですか。
A. 5が既定で、よく使われます。数百件と少ないときは RepeatedStratifiedKFold で分割を何回か変えて平均すると、偶然の1位を減らせます。

Q. 深層学習のハイパーパラメータも同じ方法で探せますか。
A. 考え方は同じです。1回の学習が重いので、Optuna の枝刈りやエポックを資源にした Successive Halving(Hyperband)を組み合わせるのが一般的です。学習率は対数スケールで探します。

Q. n_jobs=-1 にすれば速くなりますか。
A. コアが空いていれば速くなります。共有のマシンや、numpy の BLAS がすでに複数スレッドを使っている場合は、取り合いでかえって遅くなることがあります。

Q. E資格や G検定でもハイパーパラメータは出ますか。
A. 学習率・バッチサイズ・正則化・グリッドサーチなどは、機械学習の基礎として扱われます。試験の範囲は、関連記事「E資格とは|試験内容・合格率・G検定との違いとNumPyで解く要点」で扱っています。

まとめ

この節では、この記事の要点を5つにまとめます。

  • ハイパーパラメータは学習の前に決める設定値で、設定ひとつで正解率が0.16から0.98まで動く。チューニングは、その組み合わせを交差検証のスコアで選ぶ作業。
  • グリッドサーチは「全組み合わせ×分割」の二重ループで、自作して GridSearchCV と誤差0で一致した。ランダムサーチの候補も numpy で同じ値を再現できた。
  • 同じ予算なら、2個では3方式の差は誤差の範囲、5個では Optuna の交差検証スコアが最も高かった。ただし評価用データでは差が出ず、効いたのは対数スケールの範囲(中央値0.23→0.94)だった。
  • best_score_ は選ぶための値で、性能より高めに出る。乱数データで0.623(本当は0.5)。報告は評価用データか入れ子の交差検証で。
  • 時間が足りなければ早期打ち切り。Successive Halving で22.8秒→3.8秒、Optuna の枝刈りでエポック1,800→418、どちらも最良はほぼ同じ。

numpy での自作に慣れておくと、ライブラリの結果を確かめる力がつきます。配列操作の基本は、関連記事「NumPyの使い方 基本一覧|配列の作成・演算・形状変更」で扱っています。

参考・出典

この節では、この記事で確かめた公式ドキュメントと論文の一覧が分かります(確認日 2026年11月5日)。

  • scikit-learn 1.9.1 documentation「Tuning the hyper-parameters of an estimator」(GridSearchCV・RandomizedSearchCV・Successive Halving・Tips for parameter search)
    https://scikit-learn.org/stable/modules/grid_search.html
  • scikit-learn 1.9.1 documentation「Nested versus non-nested cross-validation」
    https://scikit-learn.org/stable/auto_examples/model_selection/plot_nested_cross_validation_iris.html
  • scikit-learn 1.9.1 documentation「Cross-validation: evaluating estimator performance」
    https://scikit-learn.org/stable/modules/cross_validation.html
  • Optuna 5.0.0 documentation「optuna.samplers.TPESampler」
    https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.TPESampler.html
  • Optuna 5.0.0 documentation「optuna.study.create_study」
    https://optuna.readthedocs.io/en/stable/reference/generated/optuna.study.create_study.html
  • Bergstra, Bengio「Random Search for Hyper-Parameter Optimization」JMLR 13(2012)
    https://www.jmlr.org/papers/v13/bergstra12a.html
  • Akiba, Sano, Yanase, Ohta, Koyama「Optuna: A Next-generation Hyperparameter Optimization Framework」KDD 2019
    https://arxiv.org/abs/1907.10902

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ハイパーパラメータチューニングとは|グリッド・ランダム・Optunaを同予算で比較」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり