データもバージョン管理したい?ならdvcを使ってみないか?
みなさんデータのバージョン管理してますか?ソースコードのバージョン管理はgitとかを使ってされる方が多いかと思いますが、データのバージョン管理もgitですると結構問題が山積みなんです!今回は主にMLの分野で使われるデータのバージョン管理のためのツールであるdvcを紹介します。
dvcとは?
dvcは公式ページの紹介を見ると、以下のように表現されています。
データサイエンティスト向けのデータバージョン管理に使いやすいGit拡張機能。 最小限のオーバーヘッドで、データサイエンスのワークフローにデータバージョン管理を適用します。
なぜdvcのようなツールが必要であるかを簡単に説明します。ソースコードの管理については基本的にテキストファイルなのでファイル容量はそこまで大きくはないですが、データ、特に機械学習で用いらるようなものはそれぞれのファイルのデータ量も大きい場合がありますし、ファイル数も多数ある場合も少なくありません。そのようなファイルをGitで管理すること自体はできますが、ファイルをステージングするための処理に時間がかかりますし、例えばGitHubではファイルサイズ制限があり、そもそも大容量のファイルを取り扱うことはできません。
このような問題に対処するためにdvcが考案されました。dvcには以下の特徴があります。
- コーディング
- MLプロジェクトのあらゆる側面(データとモデルのバージョン、MLパイプラインと実験)を、人間が読めるメタファイルで定義できる
- ベストプラクティスと確立されたエンジニアリングツールセットを活用でき、データサイエンスとのギャップを縮小可能
- バージョン管理: Git(または任意のSCM)を使用して、DVCメタファイル(プレースホルダーとして)をコミットすることで、ソースコードと設定、パラメータとメトリクス、データアセットとプロセスを含むMLプロジェクト全体のバージョン管理と共有が可能
- 安全なコラボレーション: プロジェクトのあらゆる側面へのアクセスを制御し、選択した人やチームと共有
dvcを利用することにより、データを管理するだけでなく、MLOpsのパイプライン全体の情報を管理することができ、再現性の担保であったり実験結果の共有などを行うことができます。
早速使ってみよう!
それでは早速dvcを使ってデータを管理してみましょう。今回Google Cloud Storagee(以下、GCS)をデータ保存先のリモート先として利用しますので、以下の実践ではGoogle Cloud Storageが利用できる環境を用意しておいてください。なお、チュートリアルの内容は以下のページに沿って進めます。
GCSのバケット準備
まずはデータの保存先となるGCSバケットを作成します。作成方法は省略しますが、バケットを作成して、以下のように空っぽのバケットを作成してください。

Python環境の構築
以下のコマンドでdvcが利用できる環境を構築します。
uv init dvc_tutorial -p 3.12
cd dvc_tutorial
uv add "dvc[gs]"
dvcの初期化
uvで作成したフォルダは基本的にgitレポジトリとして扱われますので、gitの初期化は省略してdvcの初期化を行います。
まず初期化前のフォルダ構成をみてみます。
drwxr-xr-x@ - user 20 Nov 21:09 .git
drwxr-xr-x@ - user 20 Nov 21:09 .venv
.rw-r--r--@ 109 user 20 Nov 21:09 .gitignore
.rw-r--r--@ 5 user 20 Nov 21:09 .python-version
.rw-r--r--@ 90 user 20 Nov 21:09 main.py
.rw-r--r--@ 274 user 20 Nov 21:09 pyproject.toml
.rw-r--r--@ 0 user 20 Nov 21:09 README.md
.rw-r--r--@ 1.3k user 20 Nov 21:09 Taskfile.yml
.rw-r--r--@ 364k user 20 Nov 21:09 uv.lock
それでは早速dvcを初期化してみましょう。初期化するためにはdvc initを実行します。
uv run 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>
実行すると合わせてのせたようなログが表示されます。初期化が終わったのでファイル構成をみてみると以下のようになっています。
drwxr-xr-x@ - user 20 Nov 21:12 .dvc
drwxr-xr-x@ - user 20 Nov 21:12 .git
drwxr-xr-x@ - user 20 Nov 21:09 .venv
.rw-r--r--@ 139 user 20 Nov 21:12 .dvcignore
.rw-r--r--@ 109 user 20 Nov 21:09 .gitignore
.rw-r--r--@ 5 user 20 Nov 21:09 .python-version
.rw-r--r--@ 90 user 20 Nov 21:09 main.py
.rw-r--r--@ 274 user 20 Nov 21:09 pyproject.toml
.rw-r--r--@ 0 user 20 Nov 21:09 README.md
.rw-r--r--@ 1.3k user 20 Nov 21:09 Taskfile.yml
.rw-r--r--@ 364k user 20 Nov 21:09 uv.lock
結果を見ると.dvcと.dvcignoreの二つが追加されていることがわかります。これらはgitで言うところの.gitと.gitignoreそれぞれに対応するものになります。dvcでは設定ファイル自体はgitで管理される必要があるためされる必要があるため、この状態でgit statusを実行すると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に登録することでdvc側ではデータのメタデータのみを管理し、データの実態はdvc側が管理することになります。それでは変更をgitに登録しましょう。
git add .
git commit -m "Initialize dvc"
データの登録
それではデータの登録をしてみましょう。今回はdataというフォルダの下にdata.txtというファイルを作成します。バージョンを見やすくするために、以下のようにversion 1という内容でファイルを作成します。
mkdir data
echo "version 1" > data/data.txt
この状態ではdvcで管理されていないので、git statusを実行するとdataフォルダ全体に対してトラックされていないファイルとして扱われます。
git status
# 結果
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
data/
nothing added to commit but untracked files present (use "git add" to track)
dvcにおいてデータを登録するにはdvc add <対象ファイル>のようにコマンドを実行します。それでは早速dvc/data.txtに対して実行してみます。
uv run dvc add data/data.txt
# 結果
100% Adding...|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|1/1 [00:00, 1.88file/s]
To track the changes with git, run:
git add data/data.txt.dvc data/.gitignore
To enable auto staging, run:
dvc config core.autostage true
結果を見ると、data/data.txtがdvcの管理対象に入り、それに合わせてdata/data.txt.dvcとdata/.gitignoreが生成されました。前者はデータをdvcとしてgit側が扱うことができるためのファイルであり、data/.gitignoreにはデータの本体がgitで追跡されないように情報が入っています。
/data.txt
データをdvcの管理対象に追加すると先ほどのように専用のファイルが生成されるので、それらをgitで管理します。
git add data/data.txt.dvc data/.gitignore
git commit -m "Add initial data"
実際、これらのコードをGItHubにPushして確認するとデータの実態は保存されていません。

GCSにデータをアップロードする
実際に運用する場合は先ほどお見せしたようにコードはGitHubで管理し、データの実態はGCSなどのリモートストレージに格納します。それでは早速最初に作成したGCSバケットにデータの実態をアップロードしてみましょう。
まずはローカル環境でgcloudコマンドによりアップロードができるように認証します。
gcloud auth login
次にGCSバケットをリモートレポジトリとして登録します。GCSバケットのパスを指定しつつ、-dでdvc上の名前を指定します。
uv run dvc remote add -d gcs_bucket gs://...
# 結果
Setting 'gcs_bucket' as a default remote.
バケットを登録できたらdvc pushを実行することでファイルの実態をpushすることができます。なお、今回はリモート先が一箇所だけなので特に指定してないです。
uv run dvc push
# 結果
Collecting |1.00 [00:00, 364entry/s]
Pushing
1 file pushed
pushが成功したようなのでバケットを見ると、filesフォルダが生成されていることが確認できます。

フォルダを深ぼっていくとネストされたフォルダがあり、そこにファイルが保存されていることがわかります。dvcではファイルがハッシュ化されて分割して保存されており、基本的に人がそれぞれのファイルを見てコンテンツを判断できるかというとそういうわけではないです。

データを更新してみる
それではデータを新しくしてみましょう。data/data.txtのコンテンツをversion 2で上書きし、バージョン登録します。
echo "version 2" > data/data.txt
dvc add data/data.txt
git add data/data.txt.dvc
git commit -m "Update data"
そしてデータを変更したのでGCS上にpushします。
uv run dvc push
# 結果
Collecting |1.00 [00:00, 585entry/s]
Pushing
1 file pushed
新しくファイルを追加したので、新たなネストフォルダが作成されていることが確認できます。

リモートストレージからデータを取得
先ほどお見せしたようなGitHubで管理していると、レポジトリをcloneしただけではデータの実態は手元にないことになります。データを取得するにはdvc pullコマンドを実行する必要があります。
まずcloneした状態でdataフォルダを見ると、以下のようにデータの実態がないことを確認できます。
ll data
# 結果
.rw-r--r--@ 10 user 20 Nov 21:55 .gitignore
.rw-r--r--@ 86 user 20 Nov 21:56 data.txt.dvc
次に、dvc pullを実行してみます。
uv run dvc pull
# 結果
Collecting |1.00 [00:00, 163entry/s]
Fetching
Building workspace index |1.00 [00:00, 145entry/s]
Comparing indexes |3.00 [00:00, 1.43kentry/s]
Applying changes |1.00 [00:00, 455file/s]
A data/data.txt
1 file fetched and 1 file added
改めてdataフォルダを見るとdata/data.txtが取得できていることが確認できます。
ll data
# 結果
.rw-r--r--@ 10 user 20 Nov 21:55 .gitignore
.rw-r--r--@ 10 user 20 Nov 21:57 data.txt
.rw-r--r--@ 86 user 20 Nov 21:56 data.txt.dvc
なお、データの内容を見るとdata.txtの一番最新状態であるversion 2が表示されます。
cat data/data.txt
# 結果
version 2
データのバージョンを変更する
今、data/data.txtにはversion 1とversion 2それぞれがあり、最新版の実態はversion 2になります。ユースケースとして、例えば前のバージョンのデータにアクセスしたいという場合があり、以下のような流れで実行するとデータを取得できます。
- 該当データの設定をコミットしたgitのコミットハッシュを取得する
- 該当のコミットにHEADを切り替える
-
dvc pullを実行する
データを管理した該当コミットを控えておく必要はあるので、tagをつけるなどして遡りやすくしておくことをお勧めします。
それでは早速version 1のデータに戻してみましょう。念の為、現時点でデータの内容を確認します。
cat data/data.txt
# 結果
version 2
次にgitのコミット履歴を見てみます。私の環境では以下のような履歴になっています。
git log --oneline
# 結果
afb53ea (HEAD -> main, origin/main, origin/HEAD) Add GCS Bucket
8749151 Update data
4df6525 Add initial data
af2c951 Initialize dvc
b1f8249 Initial commit
8749151がversion 2のコミット、4df6525がversion 1のコミットです。なので、4df6525にHEADを切り替えます。なお、ただ切り替えただけではデータの実態は更新されません。
git checkout 4df6525
cat data/data.txt
# 結果
version 2
それではversion 1の情報をもとにGCSからデータを取得してみましょう。dvc pullを実行してみます。
uv run dvc pull
# Collecting |0.00 [00:00, ?entry/s]
# Fetching
# Building workspace index |2.00 [00:00, 1.20kentry/s]
# Comparing indexes |3.00 [00:00, 2.35kentry/s]
# Applying changes |1.00 [00:00, 880file/s]
# M data/data.txt
# No remote provided and no default remote set.
# 1 file modified
結果を見ると、data/data.txtの内容が変更されたことが確認できます。早速data/data.txtの内容を見てみます。
cat data/data.txt
# 結果
version 1
結果を見ると、無事version 1のデータに戻すことができデータに戻すことができました!例えばmainブランチのHEADに移動してデータを見てみるとversion 1のままですがdvc pullをするとversion 2に戻ることも確認でき戻ります。
git checkout main
cat data/data.txt
# 結果
version 1
uv run dvc pull
cat data/data.txt
# 結果
version 2
まとめ
今回はデータのバージョンを管理するためのdvcについて、データの取り扱い方法を紹介しました。dvcは実験管理などさまざまな機能があるため、次回以降もっと紹介していこうと思います。
Discussion