0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Apple M5 MaxでMLflow公式Hyperparameter Tuningを実践する — Optunaでハイパーパラメータ探索

0
Posted at

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の対応

です。

この関係は記事後半で詳しく解説します。


事前準備

前回作成したプロジェクトへ移動します。

Terminal
cd mlflow-quickstart

Optunaを追加する

公式チュートリアルでは、

MLflow
+
Optuna

を使用します。

前回すでにMLflowを導入しているので、今回はOptunaだけ追加します。

Terminal
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を起動する

Terminal
uv run jupyter lab

今回は前回のNotebookを使い回さず、新しいNotebookを作成します。

ファイル名は、

mlflow_hyperparameter_tuning.ipynb

とします。

スクリーンショット 2026-08-13 11.00.03.png

前回の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")

スクリーンショット 2026-08-13 11.01.54.png

warningが出た場合は、以下のパッケージを追加した上でjupyterを再起動し、再度実行すると消えます。

uv add ipywidgets

では戻ってTracking URIを確認します。

print(mlflow.get_tracking_uri())

実行結果:

sqlite:///mlflow.db

続いて今回専用のExperimentを作成します。

mlflow.set_experiment(
    "Hyperparameter Tuning Experiment"
)

スクリーンショット 2026-08-13 11.10.41.png

これで、

という状態になります。


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)

実行結果:
スクリーンショット 2026-08-13 11.11.56.png


データの流れ


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 ...

と処理が進みます。

実行結果:(抜粋)
スクリーンショット 2026-08-13 11.19.31.png


実行中の全体像


最良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: ...

のような結果になります。

実行結果:
スクリーンショット 2026-08-13 11.21.29.png

Trial番号やパラメータ、errorの具体的な値は実行結果によって異なる場合があります。


STEP 4: View the results in the MLflow UI

次はMLflow UIから結果を確認します。

JupyterLabとは別のTerminalを開きます。

プロジェクトディレクトリへ移動します。

Terminal
cd mlflow-quickstart-m5max

MLflow Serverを起動します。

Terminal
uv run mlflow server \
  --backend-store-uri sqlite:///mlflow.db \
  --port 5000

ブラウザから次へアクセスします。

http://localhost:5000

スクリーンショット 2026-08-13 11.23.49.png


Hyperparameter Tuning Experimentを開く

MLflow UIから、

Hyperparameter Tuning Experiment

を選択します。

すると、

study

という親Runが表示されます。

さらにその配下に、

trial_0
trial_1
trial_2
...

という子Runが確認できます。

スクリーンショット 2026-08-13 11.25.17.png

OptunaのStudyをMLflow Parent Run、各TrialをChild Runとして管理


各Trialを確認する

任意の、

trial_x

を開きます。

次のような情報を確認できます。

Parameters

max_depth
n_estimators
max_features

さらに、

Metrics

error

も記録されています。

モデルも、

model

として保存されています。

スクリーンショット 2026-08-13 11.29.46.png


ChartでTrialを比較する

Experiment画面へ戻ります。

Chart表示を開きます。

MLflow UIでは複数のRunについて、

Parameter
     ×
Metric

を可視化できます。

[Add chart]から「Parallel coordinates」を追加してみます。
スクリーンショット 2026-08-13 11.43.43.png

スクリーンショット 2026-08-13 11.48.05.png

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)

実行結果:
スクリーンショット 2026-08-13 11.51.52.png

モデルURIを作成します。

best_model_uri = (
    f"runs:/{best_run_id}/model"
)

print(best_model_uri)

実行結果:
スクリーンショット 2026-08-13 11.52.37.png


Model Registryへ登録する

registered_model = mlflow.register_model(
    model_uri=best_model_uri,
    name="housing-price-predictor",
)

実行結果:
スクリーンショット 2026-08-13 11.53.51.png

登録結果を確認します。

print(
    "Model name:",
    registered_model.name,
)

print(
    "Model version:",
    registered_model.version,
)

実行結果:
スクリーンショット 2026-08-13 11.54.43.png


公式コードからの変更点

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

スクリーンショット 2026-08-13 11.56.59.png

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を実際に利用するチュートリアルへ入っていきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?