Hyperoptのコンセプト

[Hyperopt concepts \| Databricks on AWS](https://docs.databricks.com/applications/machine-learning/automl-hyperparam-tuning/hyperopt-concepts.html) [2022/2/1時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

本書では、分散処理でHyperoptを使用する際に理解すべきいくつかのコンセプトを説明します。

- [`fmin()`](#fmin)
- [`SparkTrials`クラス](#sparktrialsクラス)
- [`SparkTrials`とMLflow](#sparktrialsとmlflow)

DatabricksにおけるHyperoptの使い方を説明しているサンプルについては、[Hyperoptによるハイパーパラメーターチューニング](https://qiita.com/taka_yayoi/items/ac30f00aa851707c0dbf#hyperopt%E3%81%AB%E3%82%88%E3%82%8B%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%BC%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0)をご覧ください。

# `fmin()`

Hyperoptのランを実行するために`fmin()`を使用します。以下の表に`fmin()`の引数を示します。詳細に関しては[Hyperoptのドキュメント](https://github.com/hyperopt/hyperopt/wiki/FMin)を参照ください。それぞれの引数をどのように使うのかのサンプルについては、[サンプルノートブック](https://qiita.com/taka_yayoi/items/ac30f00aa851707c0dbf#hyperopt%E3%81%AB%E3%82%88%E3%82%8B%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%BC%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0)をご覧ください。

| 引数 | 説明 |
|:--|:--|
| `fn`  |目的関数。Hyperoptは、引数spaceで指定されるハイパーパラメーター空間から生成される値を用いてこの関数を呼び出します。この関数はスカラー値としてのlossや、ディクショナリを返却することができます(詳細に関しては[Hyperoptのドキュメント](https://github.com/hyperopt/hyperopt/wiki/FMin)を参照ください)。通常、この関数にはモデルトレーニングと損失計算のためのコードが含まれます。   |
| `space`  |探索するハイパーパラメーター空間を定義します。Hyperoptでは、どのようにこの空間を定義するのかに関して、非常に高い柔軟性を提供します。アルゴリズムのようなカテゴリーのオプションや、uniform、logのような数値に対する確率分布を選択することができます。   |
| `algo`  | ハイパーパラメーター空間を探索するために使用するHyperoptの探索アルゴリズムです。最も使用されるのは、ランダムサーチの`hyperopt.rand.suggest`やTPEの`hyperopt.tpe.suggest`です。  |
| `max_evals`  | トライするハイパーパラメーターの設定の数(フィッティングするモデルの数)です。  |
| max_queue_len  | 事前にHyperoptが生成しておくハイパーパラメーター設定の数です。HyperoptのTPEの生成アルゴリズムは時間を要するため、これをデフォルトの1より大きな値に増やしていくことは有用ですが、通常は`SparkTrials`の設定の`parallelism`より小さい値にします。  |
| `trials`  | `Trials`あるいは`SparkTrials`オブジェクトです。目的変数でscikit-learnメソッドのようなシングルマシンのアルゴリズムを呼び出す際には`SparkTrials`を使用します。目的関数でMLlibのメソッドやHorovodのような分散トレーニングアルゴリズムを呼び出す際には`Trials`を使用します。  |
| `early_stop_fn`  | `max_evals`に到達する前に`fmin`を停止すべきかどうかを決定するオプションの早期停止(early stopping)関数です。デフォルトは`None`です。この関数の入力のシグネチャは`Trials, *args`であり、出力のシグネチャは`bool, *args`です。出力のブール値は停止すべきかそうでないかを示します。`*args`は任意のステートであり、`early_stop_fn`の呼び出し結果は、次の呼び出しの入力となります。`Trials`は`SparkTrials`になることもあります。`SparkTrials`を使う際、それぞれのトライアルの後に早期停止関数が呼び出されることは保証されず、投票されることになります。[早期停止関数の例](https://github.com/hyperopt/hyperopt/blob/master/hyperopt/early_stop.py)。  |

# `SparkTrials`クラス

`SparkTrials`はDatabricksによって開発されたAPIであり、お使いのHyperoptのコードを変更することなしに、Hyperoptのランを分散させることができます。`SparkTrials`はシングルマシンによるチューニングを、Sparkのワーカーに分散させることで高速化を可能にします。

:::note
**注意**
`SparkTrials`はscikit-learnのようなシングルマシン向けのMLモデルの計算処理を並列化するために設計されています。MLlibやHorovodのような分散MLアルゴリズムで作成されたモデルに対しては、`SparkTrials`を使わないでください。この場合、モデル構築プロセスは自動でクラスター上で並列化されるので、デフォルトのHyperoptの`Trials`クラスを使用してください。
:::

このセクションでは、`SparkTrials`に渡す引数をどのように設定するのか、`SparkTrials`の実装上の側面を説明します。

## 引数

`SparkTrials`は2つのオプションの引数を受け取ります。

- `parallelism`: 同時に評価するトライアルの最大数。値を大きくすることで、より多くのハイパーパラメーター設定のテストにスケールアウトすることができます。Hyperoptは過去の結果に基づいて新たなトライアルを処理するので、並列度と適合度にはトレードオフが存在します。固定の`max_evals`に対しては、並列度を大きくすることで計算速度を高めることができますが、並列度を引き下げることで、それぞれのイテレーションが多くの過去の結果にアクセスできるようになるのでより良い結果につながる場合があります。

    デフォルト値: 利用可能なSparkエグゼキューターの数。最大: 128。値がクラスター構成で許可される同時実行タスク数を上回った場合、`SparkTrials`は並列度をこの値に削減します。

- `timeout`: `fmin()`の呼び出しにかかる最大秒数。この値を超えると、全てのランは停止され、`fmin()`は終了します。完了したランの情報は保存されます。

## 実装

`fmin()`に渡される目的関数`fn`を定義する際、そして、クラスター構成を選択する際、`SparkTrials`がチューニングタスクをどのように分散するのかを理解することは有益です。

Hyperoptにおいては、あるトライアルは通常、1つのハイパーパラメーター設定による1つのモデルのフィッティングに対応します。Hyperoptはトライアルの生成、評価を繰り返します。

`SparkTrials`を用いることで、お使いのクラスターのドライバーノードが新たなトライアルを生成し、ワーカーノードがそれらのトライアルを評価します。それぞれのトライアルは1つのタスクを持つSparkジョブとして生成され、ワーカーマシン上のタスクの中で評価されます。クラスターがワーカーごとに複数のタスクを実行できるように設定されている場合には、そのワーカー上で一度に複数のトライアルが評価されます。

# `SparkTrials`とMLflow

Databricks機械学習ランタイムでは、ワーカーからMLflowへのロギングをサポートしています。Hyperoptに渡す目的関数の中でカスタムのロギングコードを追加することができます。

`SparkTrials`は以下のようにネストされたMLflowランとして結果を記録します。

- メインあるいは親のラン: `fmin()`の呼び出しはメインのランとして記録されます。アクティブなランが存在する場合、`SparkTrials`はこのアクティブなランを記録し、`fmin()`が戻り値を返した際にこの欄を終了させません。アクティブなランが存在しない場合、`SparkTrials`が新たなランを作成、記録し、`fmin()`が戻り値を返す前にこのランを終了させます。
- 子供のラン: メインのランの下に子供のランとして、テストされたそれぞれのハイパーパラメーターの設定(トライアル)が記録されます。ワーカーからのMLflowログも、対応する子供のランの元に格納されます。

`fmin()`を呼び出す際、アクティブなMLflowラン管理を行うことをお勧めします。すなわち、`with mlflow.start_run():`ステートメントの中で`fmin()`の呼び出しをラップしてください。これによって、それぞれの`fmin()`の呼び出しが別々のMLflowメインランに記録されるので、当該ランの追加のタグ、パラメーター、メトリクスの記録が容易となります。

:::note
**注意**
同じアクティブなMLflowランの中で、`fmin()`を複数回呼び出す際、MLflowは同じメインランの中にこれらの呼び出しを記録します。記録されたパラメーターやタグの名前の衝突を避けるために、MLflowは衝突する名前にUUIDを追加します。
:::

ワーカーから記録する際、目的関数の中で明示的にランを管理する必要はありません。子供のランにパラメーターを記録するためには、目的関数で`mlflow.log_param("param_from_worker", x)`を呼び出してください。目的関数内でパラメーター、メトリクス、タグ、アーティファクトを記録することができます。

### Databricks 無料トライアル

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