Hydra と DVC で実験管理メモ

概要

Hydra を使って config 管理はできたものの、中間生成物のキャッシュが上手に無駄なく管理できないでいました。つまり hydra でパイプラインチックなものを上手く作りたいですが、できれば既存のツールで実現したいです。イメージはこちらの記事のような感じ。

zenn.dev

  1. preprocess
  2. feature_extract
  3. train

というプロセスがあり、各プロセスで中間生成物を生成するケースを考える。 中間生成物を生成することのメリットは、各処理で同じ処理(結果が同じになる処理)を複数回実行しないことで計算コストが削減できることである。

各プロセスにおける処理は入力データとコンフィグの値のみに依存するとする(つまり、再現性がある)。 この前提であれば、各プロセスの中間生成物の生成結果に違いが生じるのは入力データかコンフィグのパラメータが変更された場合のみである。

今回実現したいのは正しくこれで、config は hydra で管理し、preprocess.pytrain.py などの実行対象のファイル自体をパイプラインの各ステップとして扱い、最終的なパイプラインを作ります。

今回作ったパイプラインはこちらです。

github.com

DVC (Data Version Control)

dvc とは、データのハッシュをテキストファイルで保持し git でバージョン管理するものです。また、パイプラインも作成することができます。この辺りは以下の記事が詳しいのでそちらを参照ください

techtekt.persol-career.co.jp

DVC x Hydra

Hydra で config 管理しつつ、 DVC でパイプラインの作成とバージョン管理をする方法は、実は公式から提供されていました。こちらの記事ドキュメントがそれに当たります。

こちらが記事やドキュメントを参考にして作った簡単なパイプラインです。

.
├── README.md
├── conf
│   ├── config.yaml
│   ├── model
│   │   ├── logistic_regression.yaml
│   │   └── random_forest.yaml
│   └── paths
│       └── default.yaml
├── dvc.lock
├── dvc.yaml
├── params.yaml
├── pyproject.toml
├── requirements-dev.lock
├── requirements.lock
└── src
    └── hydra_dvc_practice
        ├── __init__.py
        ├── eval.py
        ├── load_data.py
        ├── preprocess.py
        └── train.py

conf には hydra を使う前提の設定ファイル config.yaml を配置し、src/hydra_dvc_practice にパイプラインの構成要素となる各ファイルを配置しました。 一般的に hydra を使う場合、

python load_data.py
python preprocess.py
python train.py
python eval.py

のように、ファイルごとの実行が前提となります。

これを dvc を使うことで

dvc exp run 

コマンド一発で、キャッシュ管理を含むパイプラインの実行が走ります。

そして、このパイプラインの設計図が dvc.yaml です。

stages:
  load_data:
    cmd: rye run python src/hydra_dvc_practice/load_data.py
    deps:
      - src/hydra_dvc_practice/load_data.py
    outs:
      - data/train.csv
      - data/test.csv
    params:
      - test_size
      - target
      - seed
      - paths

  preprocess:
    cmd: rye run python src/hydra_dvc_practice/preprocess.py
    deps:
      - data/train.csv
      - data/test.csv
      - src/hydra_dvc_practice/preprocess.py
    outs:
      - data/train_scaled.csv
      - data/test_scaled.csv
    params:
      - scaler

load_data step と preprocess stage について切り出しています。

  • cmd : 各 stage で実行するコマンド
  • deps : その stage が依存する対象のファイル。上記の例では、実行対象のファイルに何か変更が発生した場合にはキャッシュが使用されないことを意味しています。もちろんですが、変更が発生した stage 以降の stage も再実行されます (この辺りは柔軟に変更できるはずです)
  • outs : 中間生成物。ここで指定した中間生成物は dvc の管理対象となります
  • params : 依存関係を持ちたい設定パラメタ。つまり deps における hydra で管理している値バージョンですね。ここでは例えば、 seed の値を変更した場合は、deps 同様に再実行が発生します。

これが主な設定ですが、他にもいろいろな設定があるそうです。

params.yamldvc.lock はパイプラインの実行ごとに生成されます。

もともと dvc は params.yaml にパイプライン実行に関する全てのパラメタ設定を書くことを前提としていますが、今回はその設定自体を hydra でもっているため、実行時に params.yaml を生成する必要があります。

dvc.lock には stage 単位での実行に関する情報が記載され、これを参照することで cache 判定が行われているはずです。

また、hydra でよく使う、コマンドラインを使った設定パラメタ override にも対応しており、使い勝手ほぼそのまま再現されています。

dvc exp run -S model=random_forest -S seed=42

実験結果の参照も容易に行うことができます。

dvc.org

おわりに

hydra と dvc を使ったパイプラインの作成をしてみました。ファイル単位での管理が、既存のパイプラインツールとは異なるユニークな機能なのかなと思っています。そしてそれが hydra とも相性がいい点ではあるような気がします。

また、今回はそもそもの dvc の機能ついてはあまり触れてはいないので、そちらも機会があればというところです。

調査中に遭遇した DagsHub というのにも少し興味があったので、これも機会あれば触ってみようと思いました。


今後使っていくかは不明ですが、やりたいことはとりあえずできたのでヨシ!

参考資料