データもバージョン管理したいあなたへ

データバージョンの管理とは?

データバージョンの管理とは、バイナリデータのバージョンを管理することを指します。データバージョンの管理は、Git 等でのコードのバージョン管理をバイナリデータに拡張しています。実験の再現性を高められるメリットがあります。

DVC とは?

データのバージョンを管理する機能をもつオープンソースソフトウェアです。データのハッシュをテキストファイルで保持し git でバージョン管理します。また、yaml ファイルで実行パイプラインを定義して監視対象データが更新された際にハッシュを更新することで、新しいハッシュ値を含んだデータをバージョン管理します。更新されたデータファイルはキャッシュディレクトリに保存され、必要なタイミングで自動的に復元されます。

データのリモートリポジトリを定義することで、データ一式を簡単なコマンド操作で S3 等へ push / pull することができます。

データバージョン管理やパイプライン管理以外にも、実行結果の指標蓄積や指標推移の視覚化機能もあります。

dvc.org

解決したい課題

課題は、「Python を利用したデータ分析環境のスタックをコントロールして、実行(実験)の再現性を高めたい!」です。

全て手動、記憶、手順書で管理することも可能ですが、忘却、作業ミス、伝達ミス等の影響により再現できないことも多いです。コードを復元し、ランダムシードを固定し、ログや MLflow 等で実験結果を残しても、入力データが再現できなければ実行は再現できません。

特にデータのバージョン管理と実験の再現性を強化するのが DVC です。

下図は、データ分析環境のスタックと解決技術の例です。

データ分析環境のスタックと解決技術の例

機械学習モデル開発やデータ分析の作業は試行錯誤の連続です。

データサイエンスプロジェクトの指数関数的な複雑さ(※1)

DVC を利用すると、どのコミットでどのデータを利用し、どのデータをアウトプットしたのかが明確になります。

DVC がデータのバージョンを git のコミットに関連付け(※1)

DVC のパイプライン機能

YAML ファイルで定義した処理パイプラインに従って処理を実行することができます。実行対象は Python に限らず Linux コマンドが利用可能です。

パイプラインの定義の例

簡単な機械学習モデルの開発パイプラインを想定したコードを用意しました。依存関係を持つ 4 個の Python スクリプトを想定します。

処理と依存関係

  1. make_dataset: DB 等からデータを取得してファイルを出力
  2. build_feature: make_dataset で取得したファイルを Train と Test に分割して保存
  3. train: Train データを使ってモデルを出力
  4. predict: Test データとモデルを使って推論値を出力

パイプラインのDAG表現

各処理の出力ファイルの依存関係

パイプラインの各処理ステージで出力されるファイルの依存関係です。

パイプラインのDAG表現: ファイルの依存関係

サンプルコード

定義ファイル「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 を利用することで実行の再現性が高まる

参照

本家サイト

https://dvc.org/

動画のチュートリアルが分かりやすいです。

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

デジタルテクノロジー統括部 デジタルビジネス部 アナリティクスグループ シニアデータアナリスト - 浦山 昌生

浦山 昌生さんのプロフィール

デジタルテクノロジー統括部 デジタルビジネス部 アナリティクスグループ シニアデータアナリスト