Apple M5 MaxでMLflow公式Hyperparameter Tuningを実践する — Optunaでハイパーパラメータ探索
はじめに
この記事では、Apple Silicon M5 Max 上でMLflow公式の
Tracking Hyperparameter Tuning with MLflow
チュートリアルを実行します。
公式チュートリアル:
前回の記事では、MLflow Quickstartとして、
データ準備
↓
モデル学習
↓
MLflow Tracking
↓
MLflow UI
↓
モデル保存
↓
モデル再ロード
まで実行しました。
今回はその次のステップとして、
「モデルの設定値を自動的に変えながら、より性能の良いモデルを探す」
Hyperparameter Tuningを行います。
今回のゴール
今回作る仕組みは次のようになります。
30通りのパラメータを試し、
どのパラメータ
↓
どの評価結果
↓
どのモデル
だったのかをMLflowで一元管理します。
公式チュートリアルとの対応
公式ページは現在、見出し番号が少し特殊で、
Step 1
Step 2
Step 3
Step 3
Step 4
Step 5
となっています。
この記事では順序を変更せず、分かりやすくするため次のように表記します。
| この記事 | 公式チュートリアル |
|---|---|
| 事前準備 | Prerequisites: Set up MLflow and Optuna |
| STEP 1 | Create a new experiment |
| STEP 2 | Prepare Your Data |
| STEP 3-A | Define the objective function |
| STEP 3-B | Run the hyperparameter tuning study |
| STEP 4 | View the results in the MLflow UI |
| STEP 5 | Register Your Best Model |
今回の環境
前回のQuickstartで作成した環境をそのまま使用します。
| 項目 | 環境 |
|---|---|
| Mac | Apple Silicon M5 Max |
| Architecture | arm64 |
| Python | 3.14.6 |
| Python管理 | uv |
| MLflow | 3.x |
| Hyperparameter Tuning | Optuna |
| Machine Learning | scikit-learn |
| Model | RandomForestRegressor |
| Dataset | California Housing |
| Backend Store | SQLite |
| Notebook | JupyterLab |
今回のRandom Forestはscikit-learnによるCPU処理です。そのためApple GPUのMPS設定は必要ありません。
全体像
今回特に重要なのが、
OptunaのStudy / Trialと、MLflowのParent Run / Child Runの対応
です。
この関係は記事後半で詳しく解説します。
事前準備
前回作成したプロジェクトへ移動します。
cd mlflow-quickstart
Optunaを追加する
公式チュートリアルでは、
MLflow
+
Optuna
を使用します。
前回すでにMLflowを導入しているので、今回はOptunaだけ追加します。
uv add optuna
uv sync
uv run python -c "import optuna; print(optuna.__version__)"
# scikit-learnも確認しておきます。
uv run python -c "import sklearn; print(sklearn.__version__)"
JupyterLabを起動する
uv run jupyter lab
今回は前回のNotebookを使い回さず、新しいNotebookを作成します。
ファイル名は、
mlflow_hyperparameter_tuning.ipynb
とします。
前回のNotebookでは
mlflow.sklearn.autolog()を有効化しました。新しいNotebook/Kernalにすることで、今回の手動LoggingとAutologgingが混ざるのを防ぎます。
STEP 1: Create a new experiment
まずMLflowを読み込みます。
前回と同じSQLite DBを使用します。
import mlflow
mlflow.set_tracking_uri("sqlite:///mlflow.db")
warningが出た場合は、以下のパッケージを追加した上でjupyterを再起動し、再度実行すると消えます。
uv add ipywidgets
では戻ってTracking URIを確認します。
print(mlflow.get_tracking_uri())
実行結果:
sqlite:///mlflow.db
続いて今回専用のExperimentを作成します。
mlflow.set_experiment(
"Hyperparameter Tuning Experiment"
)
これで、
という状態になります。
STEP 2: Prepare Your Data
次に学習データを準備します。
公式チュートリアルでは、
California Housing Dataset
を使用します。
次のセルを実行します。
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
X, y = fetch_california_housing(
return_X_y=True
)
X_train, X_val, y_train, y_val = train_test_split(
X,
y,
random_state=0,
)
データサイズを確認します。
print("X :", X.shape)
print("y :", y.shape)
print("X_train:", X_train.shape)
print("X_val :", X_val.shape)
データの流れ
STEP 3-A: Define the objective function
ここからOptunaを使用します。
Optunaが何度も呼び出す、
objective()
という関数を作ります。
必要なライブラリを読み込みます。
import mlflow
import optuna
import sklearn
def objective(trial):
# Setting nested=True will create a child run under the parent run.
with mlflow.start_run(nested=True, run_name=f"trial_{trial.number}") as child_run:
rf_max_depth = trial.suggest_int("rf_max_depth", 2, 32)
rf_n_estimators = trial.suggest_int("rf_n_estimators", 50, 300, step=10)
rf_max_features = trial.suggest_float("rf_max_features", 0.2, 1.0)
params = {
"max_depth": rf_max_depth,
"n_estimators": rf_n_estimators,
"max_features": rf_max_features,
}
# Log current trial's parameters
mlflow.log_params(params)
regressor_obj = sklearn.ensemble.RandomForestRegressor(**params)
regressor_obj.fit(X_train, y_train)
y_pred = regressor_obj.predict(X_val)
error = sklearn.metrics.mean_squared_error(y_val, y_pred)
# Log current trial's error metric
mlflow.log_metrics({"error": error})
# Log the model file
mlflow.sklearn.log_model(regressor_obj, name="model")
# Make it easy to retrieve the best-performing child run later
trial.set_user_attr("run_id", child_run.info.run_id)
return error
まだこの時点では30回の学習は始まりません。
ここでは、
「Optunaが1回のTrialで何をすればよいか」
だけを定義しています。
1回のTrialの処理
探索するパラメータ
今回Optunaが変更する値は3つです。
max_depth
n_estimators
max_features
ただし詳しい意味については、記事後半で解説します。
STEP 3-B: Run the hyperparameter tuning study
いよいよHyperparameter Tuningを実行します。
まずMLflowの親Runを作成します。
その中でOptunaのStudyを開始します。
# Create a parent run that contains all child runs for different trials
with mlflow.start_run(run_name="study") as run:
# Log the experiment settings
n_trials = 30
mlflow.log_param("n_trials", n_trials)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=n_trials)
# Log the best trial and its run ID
mlflow.log_params(study.best_trial.params)
mlflow.log_metrics({"best_error": study.best_value})
if best_run_id := study.best_trial.user_attrs.get("run_id"):
mlflow.log_param("best_child_run_id", best_run_id)
実行すると、Terminal/NotebookにOptunaのTrial結果が順番に表示されます。
例えば、
Trial 0 finished ...
Trial 1 finished ...
Trial 2 finished ...
...
Trial 29 finished ...
と処理が進みます。
実行中の全体像
最良Trialを確認する
Optunaによって見つかった最良のTrialを確認します。
print(
"Best trial number:",
study.best_trial.number,
)
print(
"Best error:",
study.best_value,
)
print(
"Best parameters:",
study.best_trial.params,
)
print(
"Best MLflow run ID:",
study.best_trial.user_attrs["run_id"],
)
例えば、
Best trial number: 17
Best error: ...
Best parameters: {...}
Best MLflow run ID: ...
のような結果になります。
Trial番号やパラメータ、errorの具体的な値は実行結果によって異なる場合があります。
STEP 4: View the results in the MLflow UI
次はMLflow UIから結果を確認します。
JupyterLabとは別のTerminalを開きます。
プロジェクトディレクトリへ移動します。
cd mlflow-quickstart-m5max
MLflow Serverを起動します。
uv run mlflow server \
--backend-store-uri sqlite:///mlflow.db \
--port 5000
ブラウザから次へアクセスします。
http://localhost:5000
Hyperparameter Tuning Experimentを開く
MLflow UIから、
Hyperparameter Tuning Experiment
を選択します。
すると、
study
という親Runが表示されます。
さらにその配下に、
trial_0
trial_1
trial_2
...
という子Runが確認できます。
OptunaのStudyをMLflow Parent Run、各TrialをChild Runとして管理
各Trialを確認する
任意の、
trial_x
を開きます。
次のような情報を確認できます。
Parameters
max_depth
n_estimators
max_features
さらに、
Metrics
error
も記録されています。
モデルも、
model
として保存されています。
ChartでTrialを比較する
Experiment画面へ戻ります。
Chart表示を開きます。
MLflow UIでは複数のRunについて、
Parameter
×
Metric
を可視化できます。
[Add chart]から「Parallel coordinates」を追加してみます。

Parallel Coordinatesでは、1本の線が1回のtrialを表します。左からmax_depth、n_estimators、max_featuresの設定値をたどり、最後のerrorがその組み合わせで得られた評価値です。
今回はerrorが小さいほど良いため、右端のerror軸で下側に到達している線ほど性能の良いモデルです。
複数の線を比較することで、「どのハイパーパラメータの組み合わせでerrorが小さくなったのか」を視覚的に確認できます。
STEP 5: Register Your Best Model
最後に、一番性能が良かったモデルを
MLflow Model Registry
へ登録します。
まず最良TrialのRun IDを取得します。
best_run_id = study.best_trial.user_attrs[
"run_id"
]
print(best_run_id)
モデルURIを作成します。
best_model_uri = (
f"runs:/{best_run_id}/model"
)
print(best_model_uri)
Model Registryへ登録する
registered_model = mlflow.register_model(
model_uri=best_model_uri,
name="housing-price-predictor",
)
登録結果を確認します。
print(
"Model name:",
registered_model.name,
)
print(
"Model version:",
registered_model.version,
)
公式コードからの変更点
MLflow公式チュートリアルでは説明用として、
runs:/<具体的なRun ID>/model
というモデルURIを指定します。
しかしRun IDは環境ごとに異なります。
そのためこの記事では、
best_run_id = study.best_trial.user_attrs[
"run_id"
]
best_model_uri = (
f"runs:/{best_run_id}/model"
)
として、自分の実行結果からRun IDを自動取得するようにしています。
処理内容やチュートリアルの順番自体は変えていません。
Model RegistryをUIから確認する
MLflow UIを開きます。
Modelsへ移動します。
以下のモデルが表示されているはずです。
housing-price-predictor v1
Hyperparameter Tuningで選ばれた最良モデル(trail_16)がModel RegistryにVersion 1として登録
ここまでで完成
今回作ったシステム全体は次のようになります。
ここから初学者向け解説
ここまで公式チュートリアルの実行を優先してきました。
ここから、
- ハイパーパラメータとは何か
- Optunaとは何か
- Study / Trialとは何か
- Parent Run / Child Runとは何か
- Random Forestとは何か
- MSEとは何か
- Model Registryとは何か
を整理します。
ハイパーパラメータとは何か
機械学習モデルには大きく、
学習によって決まる値
と、
人間が事前に決める設定値
があります。
後者がハイパーパラメータです。
今回のRandom Forestなら、
max_depth
n_estimators
max_features
などです。
例えば、
設定A
max_depth = 5
n_estimators = 100
設定B
max_depth = 20
n_estimators = 250
ではモデルの性能が変わる可能性があります。
しかし、
どの組み合わせが一番良いのか?
は、実際に試さなければ分かりません。
Hyperparameter Tuningとは
そこで、
パラメータを変える
↓
モデルを学習
↓
評価
↓
またパラメータを変える
↓
再び学習
を繰り返します。
これが、
Hyperparameter Tuning
です。
Optunaとは
今回、この探索を担当しているのが、
Optuna
です。
人間が、
max_depth = 5
max_depth = 10
max_depth = 15
...
と全部手作業で試す代わりに、
trial.suggest_int(...)
などを使って候補を探索します。
Studyとは
Optunaにおける、
Study
は、
1回のHyperparameter Tuning全体
です。
今回なら、
30 Trialすべて
をまとめたものがStudyです。
Trialとは
Trialは、
1組のハイパーパラメータを試す1回の実験
です。
例えば、
Trial 0
max_depth = 7
n_estimators = 130
max_features = 0.53
で1回学習します。
次は、
Trial 1
max_depth = 21
n_estimators = 240
max_features = 0.78
で学習します。
つまり、
Study
│
├─ Trial 0
├─ Trial 1
├─ Trial 2
│
├─ ...
│
└─ Trial 29
という関係です。
MLflowではどう表現するのか
ここが今回のチュートリアルで最も重要です。
Optunaでは、
Study
└─ Trial
ですが、MLflowでは、
Parent Run
└─ Child Run
として表現できます。
つまり、
です。
今回、
mlflow.start_run(
nested=True
)
とした理由がこれです。
nested=True によって、
このRunは親Runの配下に作る
という意味になります。
なぜ親子Runが必要なのか
もし30 Trialを全部フラットに保存すると、
trial_0
trial_1
trial_2
trial_3
...
trial_29
と大量のRunだけが並びます。
100回、
1000回、
10000回とTrialを実施すると、どのチューニングで作られたRunなのか分かりにくくなります。
そこで、
2026-08-13 Study A
│
├─ trial_0
├─ trial_1
└─ trial_2
2026-08-14 Study B
│
├─ trial_0
├─ trial_1
└─ trial_2
のようにまとめるわけです。
Random Forestとは
今回使用した、
RandomForestRegressor
は、複数のDecision Treeを組み合わせて予測するモデルです。
イメージとしては、
です。
1本のDecision Treeだけに頼らず、複数のTreeの結果を組み合わせることで安定した予測を目指します。
max_depthとは
max_depth
はDecision Treeを、
どこまで深く成長させるか
を表します。
max_depth = 2
○
/ \
○ ○
と、
max_depth = 10
○
/ \
○ ○
/ \
○ ○
/ \
...
ではモデルの複雑さが大きく変わります。
n_estimatorsとは
n_estimators
は、
Random Forestで何本のDecision Treeを作るか
です。
例えば、
n_estimators = 50
なら50本、
n_estimators = 300
なら300本のTreeを作ります。
max_featuresとは
max_features
は、
それぞれのDecision Treeで使用する特徴量の割合
を制御します。
今回Optunaでは、
0.2 ~ 1.0
の範囲から探索しています。
objective関数とは
Optunaでは、
def objective(trial):
という関数が重要です。
これは、
このパラメータを試したとき、そのモデルはどのくらい良かったか?
を返す関数です。
今回返しているのは、
return mse
です。
MSEとは
MSEは、
Mean Squared Error
の略です。
日本語では、
平均二乗誤差
と呼ばれます。
予測値と正解値の差が小さいほど、
MSE → 小さい
となります。
そのため今回Optunaでは、
direction="minimize"
としています。
つまり、
errorをできるだけ小さくしてください
とOptunaに指示しています。
Model Registryとは
ここまでのMLflow Trackingでは、
大量のモデル
が生成されました。
しかし実際に使いたいのは、
その中で最も良いモデル
です。
そこでModel Registryへ登録します。
Trackingが、
実験を記録する場所
なのに対して、
Model Registryは、
利用するモデルを管理する場所
というイメージです。
なぜRun IDを保存したのか
objective関数の中で、
trial.set_user_attr(
"run_id",
child_run.info.run_id,
)
としています。
これは、
Optuna Trial
↕
MLflow Run
を結びつけるためです。
そのため、
study.best_trial
から、
最良Trial
↓
MLflow Run ID
↓
保存Model
をたどれます。
M5 Maxは今回何をしているのか
今回のチュートリアルでは、
M5 Max GPU
↓
MPS
は使用していません。
RandomForestRegressorは今回CPU上で実行されています。
つまり今回の構成は、
です。
前回と同じく、
device = "mps"
などを書く必要はありません。
M5 Maxの性能をまだ使い切っていない
ここも重要です。
今回の公式チュートリアルは、
study.optimize(
objective,
n_trials=30,
)
となっており、まずは分かりやすくHyperparameter Tuningの仕組みを学ぶ構成です。
そのため、
M5 Maxの多数のCPUコアを最大限活用すること自体は今回の目的ではありません。
まず公式チュートリアルをそのまま理解してから、
並列Trial
CPU並列化
Pruning
Sampler変更
などへ進むのがおすすめです。
Quickstartから何が変わったのか
前回は、
人間
↓
Parameter決定
↓
1 Model
↓
MLflow
でした。
今回は、
人間
↓
探索範囲だけ指定
↓
Optuna
↓
たくさんのParameter
↓
たくさんのModel
↓
MLflow
↓
Best Model
になりました。
これが機械学習実験を自動化していく第一歩です。
今回理解しておきたい3つ
今回のチュートリアルで特に重要なのは次の3点です。
1. Optuna
どのParameterを試すか
を担当する。
2. MLflow Tracking
どのParameterで
どんな結果になったか
を記録する。
3. Model Registry
最終的に採用するModel
を管理する。
つまり、
という役割分担です。
まとめ
今回、M5 Max上でMLflow公式Hyperparameter Tuningチュートリアルを実行しました。
全体の流れは、
California Housing Dataset
↓
Optuna Study
↓
30 Trials
↓
RandomForestRegressor
↓
MLflow Child Runs
↓
error比較
↓
Best Trial
↓
Best Model
↓
Model Registry
です。
Quickstartでは、
「1回の機械学習実験を記録する」
ことを学びました。
今回はさらに進んで、
「大量の機械学習実験を自動実行し、
その結果を整理して最良モデルを選ぶ」
ところまで進みました。
MLflowを使う意味も、Quickstartだけのときよりかなり見えてきます。
次のチュートリアル
MLflow Getting Startedの順番では、次は、
Deep Learning
です。
ここから、
MLflow
↓
Deep Learning
↓
PyTorch
↓
Apple Silicon / MPS
へ進みます。
ここから先は、M5 MaxのGPUを実際に利用するチュートリアルへ入っていきます。














