データバージョンの管理とは?
データバージョンの管理とは、バイナリデータのバージョンを管理することを指します。データバージョンの管理は、Git 等でのコードのバージョン管理をバイナリデータに拡張しています。実験の再現性を高められるメリットがあります。
DVC とは?
データのバージョンを管理する機能をもつオープンソースソフトウェアです。データのハッシュをテキストファイルで保持し git でバージョン管理します。また、yaml ファイルで実行パイプラインを定義して監視対象データが更新された際にハッシュを更新することで、新しいハッシュ値を含んだデータをバージョン管理します。更新されたデータファイルはキャッシュディレクトリに保存され、必要なタイミングで自動的に復元されます。
データのリモートリポジトリを定義することで、データ一式を簡単なコマンド操作で S3 等へ push / pull することができます。
データバージョン管理やパイプライン管理以外にも、実行結果の指標蓄積や指標推移の視覚化機能もあります。
解決したい課題
課題は、「Python を利用したデータ分析環境のスタックをコントロールして、実行(実験)の再現性を高めたい!」です。
全て手動、記憶、手順書で管理することも可能ですが、忘却、作業ミス、伝達ミス等の影響により再現できないことも多いです。コードを復元し、ランダムシードを固定し、ログや MLflow 等で実験結果を残しても、入力データが再現できなければ実行は再現できません。
特にデータのバージョン管理と実験の再現性を強化するのが DVC です。
下図は、データ分析環境のスタックと解決技術の例です。
機械学習モデル開発やデータ分析の作業は試行錯誤の連続です。
DVC を利用すると、どのコミットでどのデータを利用し、どのデータをアウトプットしたのかが明確になります。
DVC のパイプライン機能
YAML ファイルで定義した処理パイプラインに従って処理を実行することができます。実行対象は Python に限らず Linux コマンドが利用可能です。
パイプラインの定義の例
簡単な機械学習モデルの開発パイプラインを想定したコードを用意しました。依存関係を持つ 4 個の Python スクリプトを想定します。
処理と依存関係
- make_dataset: DB 等からデータを取得してファイルを出力
- build_feature: make_dataset で取得したファイルを Train と Test に分割して保存
- train: Train データを使ってモデルを出力
- predict: Test データとモデルを使って推論値を出力
各処理の出力ファイルの依存関係
パイプラインの各処理ステージで出力されるファイルの依存関係です。
サンプルコード
定義ファイル「dvc.yaml」
DVC の実行パイプラインを定義するファイルです。手で作成しても良いですし、dvc stage add コマンドで作成することもできます。構文が単純なので手で作成することをオススメします。
第1階層は「stages」で固定。第2階層は処理ステージの名称。第3階層は、「cmd」、「deps」、「outs」」を利用することが多いです。
このサンプルでは、4つのステージを定義しています。各ステージで、実行するコマンド、依存するファイル、出力するファイルを定義しています。
dvc.yaml
stages: make_dataset: cmd: >- python3 src/data/make_dataset.py data/raw/dataset.pickle deps: - src/data/make_dataset.py outs: - data/raw/dataset.pickle build_features: cmd: >- python3 src/features/build_features.py data/raw/dataset.pickle data/interim/features_train.pickle data/interim/features_test.pickle deps: - src/features/build_features.py - data/raw/dataset.pickle outs: - data/interim/features_train.pickle - data/interim/features_test.pickle train: cmd: >- python3 src/models/train_model.py data/interim/features_train.pickle models/model_binary deps: - src/models/train_model.py - data/interim/features_train.pickle outs: - models/model_binary predict: cmd: >- python3 src/models/predict_model.py data/interim/features_test.pickle models/model_binary data/processed/predicted.csv deps: - src/models/predict_model.py - data/interim/features_test.pickle - models/model_binary outs: - data/processed/predicted.csv
データを生成
src/data/make_dataset.py
データを取得して Pandas pickle 形式で出力します。このサンプルコードでは決め打ちの List からPandas DataFrame を生成しています。実際のプロジェクトでは、データベース、CSV、Excel 等から取得することが多いです。
import click import pandas as pd @click.command() @click.argument("output_filepath", type=click.Path()) def main(**kwargs): result_df = pd.DataFrame([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6]) result_df.to_pickle(kwargs["output_filepath"]) print(f"data output:\n{result_df}") # DataFrame の内容を表示 print(f"output filepath:\n{kwargs['output_filepath']}") # 書き込むファイル名を表示 if __name__ == "__main__": main()
特徴量生成
src/features/build_features.py
データセットを読み込み、モデルへ入力するデータを生成し、ファイルに保存します。
import click import pandas as pd @click.command() @click.argument("input_filepath", type=click.Path(exists=True)) @click.argument("output_train_filepath", type=click.Path()) @click.argument("output_test_filepath", type=click.Path()) def main(**kwargs): dataset_df = pd.read_pickle(kwargs["input_filepath"]) split_index = len(dataset_df) // 2 dataset_df.loc[:split_index, :].to_pickle(kwargs["output_train_filepath"]) dataset_df.loc[split_index:, :].to_pickle(kwargs["output_test_filepath"]) print("output filepath:") print(kwargs["output_train_filepath"]) # Train データを書き込むファイル名を表示 print(kwargs["output_test_filepath"]) # Test データを書き込むファイル名を表示 if __name__ == "__main__": main()
学習
src/models/train_model.py
train データを読み込んで学習します。モデルパラメーターをファイルとして出力します。
import click import pandas as pd @click.command() @click.argument("input_filepath", type=click.Path(exists=True)) @click.argument("output_model_filepath", type=click.Path()) def main(**kwargs): feature_df = pd.read_pickle(kwargs["input_filepath"]) model_df = feature_df.mean() model_df.to_pickle(kwargs["output_model_filepath"]) print("output filepath:") print(kwargs["output_model_filepath"]) # 書き込むファイル名を表示 print(f"model: {model_df}") # モデルのパラメーター(DataFrame)を表示 if __name__ == "__main__": main()
推論
src/models/predict_model.py
test データとモデルパラメーターを読み込んで推論します。
import click import pandas as pd @click.command() @click.argument("input_filepath", type=click.Path(exists=True)) @click.argument("model_filepath", type=click.Path(exists=True)) @click.argument("output_filepath", type=click.Path()) def main(**kwargs): feature_df = pd.read_pickle(kwargs["input_filepath"]) model_df = pd.read_pickle(kwargs["model_filepath"]) result_df = (feature_df > model_df[0]).astype(int) result_df.to_csv(kwargs["output_filepath"], index=False) print('output filepath:') print(kwargs['output_filepath']) # 書き込むファイル名を表示 if __name__ == "__main__": main()
ディレクトリ作成
ファイル出力先のディレクトリを作成し、.gitignore で無視します。
$ mkdir -p data/raw/ data/processed/ data/interim/ models/ $ echo '/data/' >> .gitignore $ echo '/models/' >> .gitignore $ git add .gitignore $ git commit .gitignore -m '[add] data, model ディレクトリを無視'
実行手順
dvc 初期化(初回のみ)
dvc を初期化して git commit します。
$ dvc init Initialized DVC repository. You can now commit the changes to git. +---------------------------------------------------------------------+ | | | DVC has enabled anonymous aggregate usage analytics. | | Read the analytics documentation (and how to opt-out) here: | | <https://dvc.org/doc/user-guide/analytics> | | | +---------------------------------------------------------------------+ What's next? ------------ - Check out the documentation: <https://dvc.org/doc> - Get help and share ideas: <https://dvc.org/chat> - Star us on GitHub: <https://github.com/iterative/dvc> $ git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: .dvc/.gitignore new file: .dvc/config new file: .dvcignore $ git commit -m '[add] DVC 初期化' [main 6b0ab3d] [add] DVC 初期化 3 files changed, 6 insertions(+) create mode 100644 .dvc/.gitignore create mode 100644 .dvc/config create mode 100644 .dvcignore $
実行前のワーキングディレクトリ
コードと dvc.yaml があり、全ての変更が git commit されている状態から始めます。
$ git status On branch main Your branch is up to date with 'origin/main'. nothing to commit, working tree clean
dvc repro でパイプラインを実行
パイプラインを実行します。
実行時に出力ファイル等のキャッシュを残すことで、再実行時にキャッシュを利用します。
パイプラインのどの部分を修正しても、このコマンドだけで実行するので実行ミスが減るのが良いところだと思います。
repro は reproduction(再現、再生) の略です。コマンドの意味合いとしては、「実行しろ」というより「再現しろ」と言っているように思います。
$ dvc repro Running stage 'make_dataset': > python3 src/data/make_dataset.py data/raw/dataset.pickle data output: 0 0 1 1 2 2 3 3 4 4 5 5 1 6 2 7 3 8 4 9 5 10 6 output filepath: data/raw/dataset.pickle Running stage 'build_features': > python3 src/features/build_features.py data/raw/dataset.pickle data/interim/features_train.pickle data/interim/features_test.pickle output filepath: data/interim/features_train.pickle data/interim/features_test.pickle Updating lock file 'dvc.lock' Running stage 'train': > python3 src/models/train_model.py data/interim/features_train.pickle models/model_binary output filepath: models/model_binary model: 0 2.666667 dtype: float64 Updating lock file 'dvc.lock' Running stage 'predict': > python3 src/models/predict_model.py data/interim/features_test.pickle models/model_binary data/processed/predicted.csv output filepath: data/processed/predicted.csv Updating lock file 'dvc.lock' To track the changes with git, run: git add dvc.lock To enable auto staging, run: dvc config core.autostage true Use `dvc push` to send your updates to remote storage. $
各ステージで Python コマンドが実行され、それぞれのコマンドの標準出力が表示されています。
実行後に、dvc.lock を追跡するなら git リポジトリに追加することや、自動的に staged にするなら設定を変更するようにとメッセージが出ています。
ワーキングディレクトリの状態を確認
ワーキングディレクトリの状態を確認します。
dvc.lock というファイルが追加されています。これは、dvc repro によって生成されたファイルです。
$ git status On branch main Your branch is up to date with 'origin/main'. Untracked files: (use "git add <file>..." to include in what will be committed) dvc.lock nothing added to commit but untracked files present (use "git add" to track) $
dvc.lock の内容を確認
dvc.lock の中身を確認します。 yaml 形式で、各ステージの依存関係や出力したファイルの情報がまとまっています。
各ファイルの md5 ハッシュやサイズを残すことでキャッシュ判定に利用します。
dvc.lock
schema: '2.0' stages: make_dataset: cmd: python3 src/data/make_dataset.py data/raw/dataset.pickle deps: - path: src/data/make_dataset.py hash: md5 md5: a1dacb410e0754946eadc1a38bf8c686 size: 381 outs: - path: data/raw/dataset.pickle hash: md5 md5: 9905581d49af693ef3b19f65c6307d9f size: 645 build_features: cmd: python3 src/features/build_features.py data/raw/dataset.pickle data/interim/features_train.pickle data/interim/features_test.pickle deps: - path: data/raw/dataset.pickle hash: md5 md5: 9905581d49af693ef3b19f65c6307d9f size: 645 - path: src/features/build_features.py hash: md5 md5: bd25a3783d71b608f9b568ad8fd12c44 size: 665 outs: - path: data/interim/features_test.pickle hash: md5 md5: 8db762962ea91fc4ed43d0a56734760d size: 605 - path: data/interim/features_train.pickle hash: md5 md5: 18f6663c51d39eaa476d893ab63f2d42 size: 605 train: cmd: python3 src/models/train_model.py data/interim/features_train.pickle models/model_binary deps: - path: data/interim/features_train.pickle hash: md5 md5: 18f6663c51d39eaa476d893ab63f2d42 size: 605 - path: src/models/train_model.py hash: md5 md5: f83e65ab6d355d33a3700779b687e260 size: 433 outs: - path: models/model_binary hash: md5 md5: 61ebe565c815c34a6b731e7ab2db3a46 size: 594 predict: cmd: python3 src/models/predict_model.py data/interim/features_test.pickle models/model_binary data/processed/predicted.csv deps: - path: data/interim/features_test.pickle hash: md5 md5: 8db762962ea91fc4ed43d0a56734760d size: 605 - path: models/model_binary hash: md5 md5: 61ebe565c815c34a6b731e7ab2db3a46 size: 594 - path: src/models/predict_model.py hash: md5 md5: 361ca4c6455034785e33f247a4672bf0 size: 591 outs: - path: data/processed/predicted.csv hash: md5 md5: 2f66b5caabd5091fdeb25b1f9180aa01 size: 14
キャッシュファイルの実体を確認
キャッシュファイルの実態を確認します。
dvc.lock の最後の出力を見てみます。
outs: - path: data/processed/predicted.csv hash: md5 md5: 2f66b5caabd5091fdeb25b1f9180aa01 size: 14
dvc が管理するキャッシュファイルとパイプラインの出力ファイルを比較します。
$ ls -l .dvc/cache/files/md5/2f/66b5caabd5091fdeb25b1f9180aa01 -r--r--r-- 1 urayama urayama 14 Oct 3 17:29 .dvc/cache/files/md5/2f/66b5caabd5091fdeb25b1f9180aa01 $ ls -l data/processed/predicted.csv -rw-rw-r-- 1 urayama urayama 14 Oct 3 18:00 data/processed/predicted.csv $ diff .dvc/cache/files/md5/2f/66b5caabd5091fdeb25b1f9180aa01 data/processed/predicted.csv $
念のため、md5 ハッシュも確認します。
$ md5sum data/processed/predicted.csv 2f66b5caabd5091fdeb25b1f9180aa01 data/processed/predicted.csv
巨大なファイルをキャッシュに入れると、実験のたびにキャッシュが増加します。キャッシュしたくないファイルがある場合は、dvc.yaml ファイルに追加で設定をする必要があります。
dvc.lock をコミット
dvc.lock ファイルをコミットします。
$ git add dvc.lock $ git commit dvc.lock -m '[add] dvc の管理ファイルを追加' [main aeaeb4e] [add] dvc の管理ファイルを追加 1 file changed, 72 insertions(+) create mode 100644 dvc.lock $ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree clean $
パイプラインの途中のコードを修正
コードを修正します。build_features.py のtrain/test分割の比率を変更してみました。
1行だけ修正します。
$ git diff diff --git a/src/features/build_features.py b/src/features/build_features.py index 20de9f0..4b51119 100644 --- a/src/features/build_features.py +++ b/src/features/build_features.py @@ -9,7 +9,7 @@ import pandas as pd def main(**kwargs): dataset_df = pd.read_pickle(kwargs["input_filepath"]) - split_index = len(dataset_df) // 2 + split_index = (len(dataset_df) * 2) // 3 dataset_df.loc[:split_index, :].to_pickle(kwargs["output_train_filepath"]) dataset_df.loc[split_index:, :].to_pickle(kwargs["output_test_filepath"]) print("output filepath:")
git commit
コミットします。
$ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/features/build_features.py no changes added to commit (use "git add" and/or "git commit -a") $ git commit src/features/build_features.py -m '[update] 分割比率を変更' [main a99c7f8] [update] 分割比率を変更 1 file changed, 1 insertion(+), 1 deletion(-) $ git status On branch main Your branch is ahead of 'origin/main' by 2 commits. (use "git push" to publish your local commits) nothing to commit, working tree clean $
パイプラインを実行
パイプラインを実行します。
$ dvc repro Stage 'make_dataset' didn't change, skipping Running stage 'build_features': > python3 src/features/build_features.py data/raw/dataset.pickle data/interim/features_train.pickle data/interim/features_test.pickle output filepath: data/interim/features_train.pickle data/interim/features_test.pickle Updating lock file 'dvc.lock' Running stage 'train': > python3 src/models/train_model.py data/interim/features_train.pickle models/model_binary output filepath: models/model_binary Updating lock file 'dvc.lock' Running stage 'predict': > python3 src/models/predict_model.py data/interim/features_test.pickle models/model_binary data/processed/predicted.csv output filepath: data/processed/predicted.csv Updating lock file 'dvc.lock' To track the changes with git, run: git add dvc.lock To enable auto staging, run: dvc config core.autostage true Use `dvc push` to send your updates to remote storage. $
make_dataset ステージは処理がスキップされました。build_features.py に依存する build_features ステージ以降が実行されています。
dvc.lock の差分を確認してコミット
dvc.lock の差分を確認します。
$ git status On branch main Your branch is ahead of 'origin/main' by 2 commits. (use "git push" to publish your local commits) Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: dvc.lock no changes added to commit (use "git add" and/or "git commit -a") $ git diff $ git diff | cat diff --git a/dvc.lock b/dvc.lock index db6ecd2..b66e2e4 100644 --- a/dvc.lock +++ b/dvc.lock @@ -22,24 +22,24 @@ stages: size: 645 - path: src/features/build_features.py hash: md5 - md5: bd25a3783d71b608f9b568ad8fd12c44 - size: 665 + md5: e31b731b36af456c5e27c36e84ea0758 + size: 671 outs: - path: data/interim/features_test.pickle hash: md5 - md5: 8db762962ea91fc4ed43d0a56734760d - size: 605 + md5: 7678d02cd86e6d21e13adc7fd8a3a687 + size: 589 - path: data/interim/features_train.pickle hash: md5 - md5: 18f6663c51d39eaa476d893ab63f2d42 - size: 605 + md5: 53430dafc0ff87c69c884b450bcb35c3 + size: 621 train: cmd: python3 src/models/train_model.py data/interim/features_train.pickle models/model_binary deps: - path: data/interim/features_train.pickle hash: md5 - md5: 18f6663c51d39eaa476d893ab63f2d42 - size: 605 + md5: 53430dafc0ff87c69c884b450bcb35c3 + size: 621 - path: src/models/train_model.py hash: md5 md5: f83e65ab6d355d33a3700779b687e260 @@ -47,7 +47,7 @@ stages: outs: - path: models/model_binary hash: md5 - md5: 61ebe565c815c34a6b731e7ab2db3a46 + md5: af2daa13171f6337c62f12591196d6f3 size: 594 predict: cmd: python3 src/models/predict_model.py data/interim/features_test.pickle models/model_binary @@ -55,11 +55,11 @@ stages: deps: - path: data/interim/features_test.pickle hash: md5 - md5: 8db762962ea91fc4ed43d0a56734760d - size: 605 + md5: 7678d02cd86e6d21e13adc7fd8a3a687 + size: 589 - path: models/model_binary hash: md5 - md5: 61ebe565c815c34a6b731e7ab2db3a46 + md5: af2daa13171f6337c62f12591196d6f3 size: 594 - path: src/models/predict_model.py hash: md5 @@ -68,5 +68,5 @@ stages: outs: - path: data/processed/predicted.csv hash: md5 - md5: 2f66b5caabd5091fdeb25b1f9180aa01 - size: 14 + md5: 30069ad3af8e4421341d0be438a2c56c + size: 10 $ git commit dvc.lock -m '[update] dvc 管理ファイルを更新' [main 8879119] [update] dvc 管理ファイルを更新 1 file changed, 14 insertions(+), 14 deletions(-) $ git status On branch main Your branch is ahead of 'origin/main' by 3 commits. (use "git push" to publish your local commits) nothing to commit, working tree clean $
build_features ステージ以降のファイルの md5 ハッシュとファイルサイズが変わっています。
変更内容をコミットしました。
過去の実験を再現
過去のバージョンに戻してから実行する
2個前のコミット(分割方法の変更前)に戻します。以下のコミットの前の状態に戻ります。
- dvc パイプラインの実行による dvc.lock の更新
- build_features.py の train/test 分割の更新
$ cat data/processed/predicted.csv 0 1 1 1 1 $ git reset --hard HEAD^^ HEAD is now at aeaeb4e [add] dvc の管理ファイルを追加 $ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree clean $
dvc パイプラインを実行
dvc パイプラインを実行します。
過去に実行した際のキャッシュが使われるため、コードが実行されずに出力ファイルだけが置換されて完了します。
$ dvc repro Stage 'make_dataset' didn't change, skipping Stage 'build_features' is cached - skipping run, checking out outputs Stage 'train' is cached - skipping run, checking out outputs Stage 'predict' is cached - skipping run, checking out outputs Use `dvc push` to send your updates to remote storage. $ git status On branch main Your branch is ahead of 'origin/main' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree clean $ cat data/processed/predicted.csv 0 0 0 1 1 1 1 $
dvc.lock ファイルもリポジトリに追加されているので更新や差分はありません。
dvc がパイプラインの各ステージの出力ファイルをキャッシュから復元しています。
パラーメーターを外部に持つ方法
dvc.yaml の foreach stages とか matrix stages を利用するとグリッドサーチっぽいことや、複数のパラメーターでパイプラインを実行するとができて便利です。
params.yaml にパラメーターを保存し、dvc.yaml から参照します。
dvc.yaml にはファイルの入出力を記載し、params.yaml にはパラメータを記載することで、パラメータの数が増えても管理がしやすくなります。実験の規模が大きくなると、特徴量の加工のバリエーション、モデルのバリエーション等が膨大に増えることがあるので、「増えそうだな」と思ったら dvc でパイプラインを定義すると良いと思います。
matrix の例
以下は簡単な例です。params.yaml の記載例です。
models: [model_a, model_b, model_c] params1: [1, 10, 100]
dvc.yaml の記載例です。
matrix: のサブキーに置換したいラベルを指定します。
stages: grid_train: matrix: model: ${models} param1: ${params1} cmd: >- python src/models/train_model.py data/interim/features_train.pickle models/matrix_${item.model}_${item.param1}.model ${item} deps: - src/models/train_model.py - data/interim/features_train.pickle outs: - models/matrix_${item.model}_${item.param1}.model
以下のようなプレースホルダが利用可能です。
- matrix: のサブキー model: ${models} 等
- params.yaml で定義するトップレベルのキーを指定します
- トップレベルキーの内部のリスト要素が展開されて ${item} や ${item.model} を置換する
- ${item}
- 全てのキーとバリューに置換される
- たとえば「--model model_a --param1 1」に置換される
- ${item.model}
- 特定のキー(ここでは model)のバリューに置換される
- grid_train@model_a-1 stage の場合は、「model_a」に置換される
実行例です。複数を変化させながらコマンドを実行しているのが確認できます。
$ dvc repro Running stage 'grid_train@model_a-1': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_a_1.model --model model_a --param1 1 output filepath: models/matrix_model_a_1.model Running stage 'grid_train@model_a-10': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_a_10.model --model model_a --param1 10 output filepath: models/matrix_model_a_10.model Running stage 'grid_train@model_a-100': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_a_100.model --model model_a --param1 100 output filepath: models/matrix_model_a_100.model Running stage 'grid_train@model_b-1': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_b_1.model --model model_b --param1 1 output filepath: models/matrix_model_b_1.model Running stage 'grid_train@model_b-10': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_b_10.model --model model_b --param1 10 output filepath: models/matrix_model_b_10.model Running stage 'grid_train@model_b-100': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_b_100.model --model model_b --param1 100 output filepath: models/matrix_model_b_100.model Running stage 'grid_train@model_c-1': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_c_1.model --model model_c --param1 1 output filepath: models/matrix_model_c_1.model Running stage 'grid_train@model_c-10': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_c_10.model --model model_c --param1 10 output filepath: models/matrix_model_c_10.model Running stage 'grid_train@model_c-100': > python src/models/train_model.py data/interim/features_train.pickle models/matrix_model_c_100.model --model model_c --param1 100 output filepath: models/matrix_model_c_100.model Use `dvc push` to send your updates to remote storage. $
foreach の例
パラメーターのセットごとに異なるキー構造を定義することができます。
params.yaml の例です。
train_params: minimum: model: linear lr: 0.01 traial01: model: lightgbm lr: 0.1 num_iter: 50 max_depth: 20 traian02: model: lightgbm lr: 0.1 num_iter: 40 max_depth: 10 traian03: model: randomforest n_estimators: 10 max_depth: 10 random_state: 12345
dvc.yaml の例です。
stages: foreach_train: foreach: ${train_params} do: cmd: >- python src/models/train_model.py data/interim/features_train.pickle models/foreach_${key}.model ${item} deps: - src/models/train_model.py - data/interim/features_train.pickle outs: - models/foreach_${key}.model
以下のようなプレースホルダが利用可能です。
- foreach: ${train_params}
- params.yaml で定義するトップレベルのキーを指定
- ${key}
- do: で利用可能。第2レベルのキーに置換される。「minimum」とか「traial01」とか。
- ${item}
- do: で利用可能。第3レベルのキーとバリューに置換される。
- minimum の場合は、「--model linear --lr 0.01」に置換される
- ${item.model}
- do: で利用可能。第3レベルの特定のバリューに置換される。
- minimum の場合は、「linea」に置換される。
マニュアルには記載が見つけられませんでしたが、第4レベルのキーとバリューも利用可能です。(ここでは割愛しますが・・・)
dvc repro の実行例です。実際には処理を行わずにコマンドだけを表示する「dry run」モードにしています。
$ dvc repro --dry foreach_train Stage 'make_dataset' didn't change, skipping Stage 'build_features' didn't change, skipping Running stage 'foreach_train@traial01': > echo python src/models/train_model.py data/interim/features_train.pickle models/foreach_traial01.model --model lightgbm --lr 0.1 --num_iter 50 --max_depth 20 Running stage 'foreach_train@minimum': > echo python src/models/train_model.py data/interim/features_train.pickle models/foreach_minimum.model --model linear --lr 0.01 Running stage 'foreach_train@traial03': > echo python src/models/train_model.py data/interim/features_train.pickle models/foreach_traial03.model --model randomforest --n_estimators 10 --max_depth 10 --random_state 12345 Running stage 'foreach_train@traial02': > echo python src/models/train_model.py data/interim/features_train.pickle models/foreach_traial02.model --model lightgbm --lr 0.1 --num_iter 40 --max_depth 10 Use `dvc push` to send your updates to remote storage. $
コマンドオプションがそれぞれ異なることが分かると思います。
リモートリポジトリの利用
リモートリポジトリを設定すると、キャッシュファイルをリモートに保存することができます。 git で dvc.lock 等のパイプラインとキャッシュファイルの紐づけを行い、ファイルの実体は dvc push / dvc pull で転送します。
ローカルストレージ、S3、GCS、Google Drive、WevDAV 等の様々なサービスが利用可能です。認証関連の設定は割愛します。
$ dvc remote list # ← リモートリポジトリが設定されていないことを確認します $ dvc remote add remote_storage s3://some_bucket/dvc_repos/your_repos_name/ $ dvc remote default remote_storage $ dvc remote list remote_storage s3://some_bucket/dvc_repos/your_repos_name/ $
dvc のリモートストレージ設定を git commit します
$ git diff .dvc/config diff --git a/.dvc/config b/.dvc/config index c4eb340..d426491 100644 --- a/.dvc/config +++ b/.dvc/config @@ -1,2 +1,4 @@ +[core] + remote = remote_storage +['remote "remote_storage"'] + url = s3://some_bucket/dvc_repos/your_repos_name/ $ git commit .dvc/config -m '[update] DVC のリモートストレージを設定'
dvc のキャッシュをリモートリポジトリにプッシュします。s3 や gcs に投げる際は、dvc[s3] 等の追加パッケージが必要になる場合があります。
$ dvc push
dvc のキャッシュをリモートリポジトリからプルします。
$ dvc pull
パイプラインの可視化
dvc.yaml と params.yaml で定義したパイプラインを可視化することが可能です。
ASCII テキスト形式、Mermaid、Markdown、Graphviz(dot)形式で出力することができます。
Markdown 形式の場合は、Markdown 内に Mermaid 形式で埋め込みます。GitLab 等の Mermaid に対応したサービスを利用する際に便利です。
$ poetry run dvc dag +--------------+ | make_dataset | +--------------+ * * * +----------------+ | build_features | +----------------+ ** ** ** ** * ** +-------+ * | train | ** +-------+ ** ** ** ** ** * * +---------+ | predict | +---------+
出力ファイル名を描画。
$ poetry run dvc dag --outs +-------------------------+ | data/raw/dataset.pickle | +-------------------------+ **** ***** ***** **** *** ***** +------------------------------------+ *** | data/interim/features_train.pickle | * +------------------------------------+ * * * * * * * +---------------------+ +-----------------------------------+ | models/model_binary | | data/interim/features_test.pickle | +---------------------+* +-----------------------------------+ **** **** ***** ***** *** *** +------------------------------+ | data/processed/predicted.csv | +------------------------------+
PNG で出力する場合は以下のコマンドを入力します。Graphviz を経由します。
$ pip install graphviz $ mkdir -p reports/figures/ $ dvc dag --dot | dot -Tpng > reports/figures/pipeline.png
まとめ
DVC を利用してデータバージョン管理の実践方法を書きました。以下を感じて頂けているとありがたいです。
- DVC と Git でデータのバージョンが管理できる
- DVC は処理の依存関係をもとに最適な実行を行う
- DVC を利用することで実行の再現性が高まる
参照
本家サイト
動画のチュートリアルが分かりやすいです。
dvc.yaml のマニュアル
https://dvc.org/doc/user-guide/project-structure/dvcyaml-files
dvc.yaml の書式や動作で困ったらここを見ると良いです。matrix や foreach の詳しい使い方は網羅的には文書化されていない気がします。
引用
※1. Versioning Data and Models | Data Version Control · DVC
浦山 昌生さんのプロフィール
デジタルテクノロジー統括部 デジタルビジネス部 アナリティクスグループ シニアデータアナリスト