Databricksにおける機械学習モデルトレーニングのウォークスルー

以前の[エンドツーエンドの機械学習のサンプル](https://qiita.com/taka_yayoi/items/f48ccd35e0452611d81b)に加えて、よりシンプルなサンプルが追加されていました。

https://docs.databricks.com/ja/machine-learning/train-model/scikit-learn.html#scikit-learn-classification-notebook

翻訳済みのノートブックはこちらです。

https://github.com/taka-yayoi/public_repo_2/blob/master/ml_quickstart/ml-quickstart-training.py

# Databricks MLクイックスタート: モデルのトレーニング

このノートブックでは、Databricksにおける機械学習モデルトレーニングの概要を説明します。モデルをトレーニングするには、Databricks機械学習ランタイムにプレインストールされているscikit-learnのようなライブラリを活用することができます。さらに、トレーニングしたモデルの追跡にMLflowを活用し、ハイパーパラメータチューニングをスケールさせるためにSparkTrialsとHyperoptを活用することもできます。

このチュートリアルでは以下をカバーします:
- Part 1: MLflowトラッキングを用いたシンプルな分類モデルのトレーニング
- Part 2: Hyperoptを用いたより性能の良いモデルをトレーニングするためのハイパーパラメーターチューニング

モデルライフサイクル管理とモデル推論を含む、Databricksにおける機械学習のプロダクション化の詳細に関しては、MLのエンドツーエンドのサンプルをご覧ください ([AWS](https://docs.databricks.com/ja/mlflow/end-to-end-example.html)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/mlflow/end-to-end-example)|[GCP](https://docs.gcp.databricks.com/applications/mlflow/end-to-end-example.html))。



### 要件
- Databricksランタイム7.5 ML以降が稼働しているクラスター

![Screenshot 2023-11-15 at 17.37.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/785faca4-e2bd-cb4b-276a-d5567bb7bd1c.png)


### ライブラリ

必要なライブラリをインポートします。これらのライブラリは、Databricks機械学習ランタイム([AWS](https://docs.databricks.com/ja/machine-learning/index.html)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/machine-learning/)|[GCP](https://docs.gcp.databricks.com/runtime/mlruntime.html))クラスターにプレインストールされており、互換性とパフォーマンスにチューニングされています。

```py:Python
import mlflow
import numpy as np
import pandas as pd
import sklearn.datasets
import sklearn.metrics
import sklearn.model_selection
import sklearn.ensemble

from hyperopt import fmin, tpe, hp, SparkTrials, Trials, STATUS_OK
from hyperopt.pyll import scope
```

### データのロード

このチュートリアルでは、様々なワインのサンプルを記述しているデータセットを使用します。この[データセット](https://archive.ics.uci.edu/ml/datasets/Wine)はUCI機械学習リポジトリから取得しているものであり、DBFS ([AWS](https://docs.databricks.com/ja/dbfs/index.html)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/dbfs/)|[GCP](https://docs.gcp.databricks.com/data/databricks-file-system.html))に格納されています。赤ワインと白ワインを品質に基づいて分類することがゴールとなります。

アップロードや他のデータソースからのロードに関する詳細については、データ取扱に関するドキュメント([AWS](https://docs.databricks.com/ja/data/index.html)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/data/)|[GCP](https://docs.gcp.databricks.com/data/index.html))をご覧ください。

```py:Python
# データのロードと前処理
white_wine = pd.read_csv("/dbfs/databricks-datasets/wine-quality/winequality-white.csv", sep=';')
red_wine = pd.read_csv("/dbfs/databricks-datasets/wine-quality/winequality-red.csv", sep=';')
white_wine['is_red'] = 0.0
red_wine['is_red'] = 1.0
data_df = pd.concat([white_wine, red_wine], axis=0)

# ワイン品質に基づいた分類ラベルの定義
data_labels = data_df['quality'] >= 7
data_df = data_df.drop(['quality'], axis=1)

# 80/20でトレーニング/テストデータセットを分割
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
  data_df,
  data_labels,
  test_size=0.2,
  random_state=1
)
```

## Part 1. 分類モデルのトレーニング

### MLflowトラッキング

[MLflowトラッキング](https://www.mlflow.org/docs/latest/tracking.html)を用いることで、お使いの機械学習トレーニングコード、パラメータ、モデルを整理することができます。

[*autologging*](https://www.mlflow.org/docs/latest/tracking.html#automatic-logging)を用いることで、自動でのMLflowトラッキングを有効化することができます。

```py:Python
# このノートブックでのMLflow autologgingを有効化
mlflow.autolog()
```
```
2023/11/15 03:18:38 INFO mlflow.tracking.fluent: Autologging successfully enabled for sklearn.
2023/11/15 03:18:39 INFO mlflow.tracking.fluent: Autologging successfully enabled for pyspark.
2023/11/15 03:18:39 INFO mlflow.tracking.fluent: Autologging successfully enabled for pyspark.ml.
```

次に、トレーニングされたモデルや関連付けられるメトリクスやパラメータが自動で記録されるMLflowランのコンテキスト内で分類器をトレーニングします。

テストデータに対するモデルのAUCスコアのようなその他のメトリクスを追加することも可能です。

```py:Python
with mlflow.start_run(run_name='gradient_boost') as run:
  model = sklearn.ensemble.GradientBoostingClassifier(random_state=0)
  
  # モデル、パラメータ、トレーニングメトリクスが自動でトラッキングされます
  model.fit(X_train, y_train)

  predicted_probs = model.predict_proba(X_test)
  roc_auc = sklearn.metrics.roc_auc_score(y_test, predicted_probs[:,1])
  
  # テストデータに対するAUCスコアは自動で記録されないので、手動で記録します
  mlflow.log_metric("test_auc", roc_auc)
  print("Test AUC of: {}".format(roc_auc))
```
![Screenshot 2023-11-15 at 17.39.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/2925ba97-8145-4336-a383-decec4561d94.png)

画面右のフラスコマークをクリックすると記録されたモデルを確認することができます。
![Screenshot 2023-11-15 at 17.39.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/de369658-c5fa-c3b2-2968-a23eec4060e5.png)

このモデルのパフォーマンスに満足しない場合には、異なるハイパーパラメーターを用いて別のモデルをトレーニングします。

```py:Python
# 新たなランをスタートし、後でわかるようにrun_nameを割り当てます
with mlflow.start_run(run_name='gradient_boost') as run:
  model_2 = sklearn.ensemble.GradientBoostingClassifier(
    random_state=0, 
    
    # n_estimatorsで新たなパラメータ設定をトライします
    n_estimators=200,
  )
  model_2.fit(X_train, y_train)

  predicted_probs = model_2.predict_proba(X_test)
  roc_auc = sklearn.metrics.roc_auc_score(y_test, predicted_probs[:,1])
  mlflow.log_metric("test_auc", roc_auc)
  print("Test AUC of: {}".format(roc_auc))
```
![Screenshot 2023-11-15 at 17.40.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ca7c2633-2e8c-e0ad-1366-c9711214e402.png)

ハイパーパラメーターが異なる2つ目のモデルが記録されます。
![Screenshot 2023-11-15 at 17.41.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ddec35a1-9e33-c459-ef2e-8ca7a7ab7c82.png)

### MLflowランの参照

記録されたトレーニングランを参照するには、エクスペリメントサイドバーを表示するためにノートブック右上の**Experiment**アイコンをクリックします。必要であれば、最新のランを取得、監視するためにリフレッシュアイコンをクリックします。

<img width="350" src="https://docs.databricks.com/_static/images/mlflow/quickstart/experiment-sidebar-icons.png"/>

より詳細なMLflowエクスペリメントページ([AWS](https://docs.databricks.com/ja/mlflow/tracking.html#notebook-experiments)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/mlflow/tracking#notebook-experiments)|[GCP](https://docs.gcp.databricks.com/applications/mlflow/tracking.html#notebook-experiments))を表示するために、エクスペリメントページアイコンをクリックすることもできます。このページでは、複数のランを比較し、特定のランの詳細を表示することができます。

![Screenshot 2023-11-15 at 17.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/98eb70a9-c01b-e8cd-f732-40ce90aa2b69.png)

### モデルのロード

また、MLflow APIを用いて特定のランの結果にアクセスすることができます。以下のセルのコードでは、特定のMLflowランでトレーニングされたモデルのロード方法と、予測での利用方法を説明しています。また、MLflowランのページ([AWS](https://docs.databricks.com/ja/mlflow/tracking.html#view-notebook-experiment)|[Azure](https://docs.microsoft.com/azure/databricks/applications/mlflow/tracking#view-notebook-experiment)|[GCP](https://docs.gcp.databricks.com/applications/mlflow/tracking.html#view-notebook-experiment))では特定のモデルをロードするためのコードスニペットを参照することができます。

```py:Python
# モデルが記録された後では、別のノートブックやジョブでモデルをロードすることができます
# mlflow.pyfunc.load_modelを用いることで、共通APIを通じてモデルの予測を行うことができます
model_loaded = mlflow.pyfunc.load_model(
  'runs:/{run_id}/model'.format(
    run_id=run.info.run_id
  )
)

predictions_loaded = model_loaded.predict(X_test)
predictions_original = model_2.predict(X_test)

# ロードされたモデルはオリジナルと一致すべきです
assert(np.array_equal(predictions_loaded, predictions_original))
```

結果が一致するのでassertionを通過します。

## Part 2. ハイパーパラメーターチューニング

この時点で、シンプルなモデルをトレーニングし、皆様の取り組み結果を整理するためにMLflowトラッキングサービスを活用しました。このセクションでは、Hyperoptを用いてより洗練されたチューニングを実行する方法をカバーします。

### HyperoptとSparkTrialsによる並列トレーニング


[Hyperopt](http://hyperopt.github.io/hyperopt/)はハイパーパラメーターチューニングのためのPythonライブラリです。DatabricksにおけるHyperopt活用の詳細については、ドキュメント([AWS](https://docs.databricks.com/ja/machine-learning/automl-hyperparam-tuning/index.html#hyperparameter-tuning-with-hyperopt)|[Azure](https://learn.microsoft.com/ja-jp/azure/databricks/machine-learning/automl-hyperparam-tuning/#hyperparameter-tuning-with-hyperopt)|[GCP](https://docs.gcp.databricks.com/applications/machine-learning/automl-hyperparam-tuning/index.html#hyperparameter-tuning-with-hyperopt))を参照ください。

並列でのハイパーパラメーター探索と複数のモデルのトレーニング実行を行うために、HyperoptとSparkTrialsを活用することができます。これによって、モデルパフォーマンスの最適化に要する時間を削減します。モデルとパラメータを自動で記録するように、MLflowトラッキングはHyperoptとインテグレーションされています。

```py:Python
# 探索する検索空間の定義
search_space = {
  'n_estimators': scope.int(hp.quniform('n_estimators', 20, 1000, 1)),
  'learning_rate': hp.loguniform('learning_rate', -3, 0),
  'max_depth': scope.int(hp.quniform('max_depth', 2, 5, 1)),
}

def train_model(params):
  # それぞれのワーカーでautologgingを有効化
  mlflow.autolog()
  with mlflow.start_run(nested=True):
    model_hp = sklearn.ensemble.GradientBoostingClassifier(
      random_state=0,
      **params
    )
    model_hp.fit(X_train, y_train)
    predicted_probs = model_hp.predict_proba(X_test)
    # テストデータに対するAUCに基づくチューニング
    # プロダクション環境では、代わりに別の検証用データセットを活用することができます
    roc_auc = sklearn.metrics.roc_auc_score(y_test, predicted_probs[:,1])
    mlflow.log_metric('test_auc', roc_auc)
    
    # fminがauc_scoreを最大化するように、lossを -1*auc_score に設定します
    return {'status': STATUS_OK, 'loss': -1*roc_auc}

# SparkTrialsがSparkワーカーを用いてチューニングを分散させます
# 並列度を高めると処理を加速しますが、それぞれのハイパーパラメーターのトライアルが他のトライアルから得られる情報が減少します
# 小規模なクラスターやDatabricks Community Editionでは parallelism=2 に設定してください
spark_trials = SparkTrials(
  parallelism=8
)

with mlflow.start_run(run_name='gb_hyperopt') as run:
  # 最大のAUCを達成するパラメーターを特定するためhyperoptを使用します
  best_params = fmin(
    fn=train_model, 
    space=search_space, 
    algo=tpe.suggest, 
    max_evals=32,
    trials=spark_trials)
```

ハイパーパラメーターチューニングの過程で多数のモデルが生成されますが、それらはすべてMLflowによって追跡・記録されます。
![Screenshot 2023-11-15 at 17.44.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/7310cd5e-ca42-a122-3181-c111bd07fd12.png)

### ベストモデルを取得するためにランを検索します

すべてのランはMLflowで追跡されるので、テストデータに対する最大のAUCを持つチューニングのランを特定するために、MLflowのsearch runs APIを用いて、ベストなランのメトリクスとパラメーターを取得することができます。

このチューニングされたモデルは、パート1でトレーニングされたよりシンプルなモデルよりも良いパフォーマンスであるべきです。

```py:Python
# テストデータに対するAUCでランをソートします。値が同じ場合には最新のランを使用します
best_run = mlflow.search_runs(
  order_by=['metrics.test_auc DESC', 'start_time DESC'],
  max_results=10,
).iloc[0]
print('Best Run')
print('AUC: {}'.format(best_run["metrics.test_auc"]))
print('Num Estimators: {}'.format(best_run["params.n_estimators"]))
print('Max Depth: {}'.format(best_run["params.max_depth"]))
print('Learning Rate: {}'.format(best_run["params.learning_rate"]))

best_model_pyfunc = mlflow.pyfunc.load_model(
  'runs:/{run_id}/model'.format(
    run_id=best_run.run_id
  )
)
best_model_predictions = best_model_pyfunc.predict(X_test[:5])
print("Test Predictions: {}".format(best_model_predictions))
```

ハイパーパラメーターチューニングによって、AUC(Area Under Curve)は0.91から0.917まで改善しました。

```
Best Run
AUC: 0.9174331940947587
Num Estimators: 935.0
Max Depth: 5.0
Learning Rate: 0.09614045842606779
```
![Screenshot 2023-11-15 at 17.48.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/5e4330d7-87b6-13e4-d280-f6c4daeb0959.png)

### UIで複数のランを比較

パート1と同じように、**Experiment**サイドバーの上部にある外部リンクアイコン経由でMLflowエクスペリメントの詳細ページでランを参照、比較できます。

エクスペリメント詳細ページでは、親のランを展開するために　"+"　アイコンをクリックし、親を除くすべてのランを選択し、**Compare**をクリックします。メトリックに対する様々なパラメーターの値のインパクトを表示するparallel coordinates plotを用いて、様々なランを可視化することができます。
![Screenshot 2023-11-15 at 17.49.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/cc761d84-984f-b515-0c16-9bbcb0336295.png)
![Screenshot 2023-11-15 at 17.49.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/585e9b57-b74b-ce42-bc2f-4c70211b29ec.png)
![Screenshot 2023-11-15 at 17.50.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f373c651-f3c7-a304-bd99-b55d2adab866.png)

# まとめ

世の中は生成AI一色ではありますが、世の中のユースケースすべてが生成AIという訳ではありません。この例で触れたような、構造化データの特徴量に基づく機械学習モデルのニーズも依然として存在しており、そのようなモデル開発・運用の営みはこれまで以上に増えてくると思います。そういった場合に、MLflowなどによって実現される[MLOps](https://qiita.com/taka_yayoi/items/47dcd3aee7d028aeef79)の重要性も同様に高まってくると考えております。是非、DatabricksでMLflowを活用いただき、MLOpsを実現してください！

### Databricksクイックスタートガイド

[Databricksクイックスタートガイド](https://www.amazon.co.jp/dp/B09V1YXFVQ/)


### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)