Machine learning experiments depend on more than source code. A model's performance can change when the training dataset, preprocessing logic, hyperparameters, or software environment changes. If a team cannot identify exactly which inputs produced a particular model, reproducing the experiment becomes difficult.
Git works well for versioning source code, configuration files, and small text-based assets. However, large datasets, model checkpoints, and other binary files can quickly make a Git repository difficult to manage.
Data Version Control (DVC) addresses this problem by connecting data and machine learning artifacts to a version-controlled workflow. It allows teams to track large files, define reproducible pipelines, and associate experiment results with the code and data used to produce them.
This article explains how DVC works, how to version a dataset, how to build a reproducible training pipeline, and how to compare machine learning experiments.
What Is DVC?
DVC is an open-source tool designed to manage data and machine learning workflows alongside Git. It tracks the relationship between source code, datasets, pipeline stages, and generated artifacts without requiring every large file to be stored directly in Git.
Instead of committing a large dataset to Git, DVC stores metadata in the repository and keeps the actual data in a configured storage location. The storage might be local, on a shared server, or in a supported cloud object store.
A typical project may look like this:
ml-project/
├── data/
│ └── training.csv
├── src/
│ └── train.py
├── models/
├── metrics.json
├── params.yaml
├── dvc.yaml
├── dvc.lock
└── .git/
The files serve different purposes:
training.csv contains the dataset used by the model.
train.py contains the training code.
params.yaml stores configurable training parameters.
dvc.yaml defines the pipeline stages.
dvc.lock records the resolved dependencies and outputs of pipeline stages.
metrics.json contains evaluation results.
The exact files depend on the project. DVC generates and updates its metadata as you add data and execute pipelines.
Git and DVC work together: Git versions the code and DVC metadata, while DVC manages references to the larger data and model artifacts.
Install DVC and Initialize a Project
Before tracking data, install Git and Python. DVC can be installed into a virtual environment using Python's package manager.
python -m venv .venv
Activate the environment.
On Windows:
.venv\Scripts\Activate.ps1
On Linux or macOS:
source .venv/bin/activate
Install DVC:
python -m pip install dvc
For cloud storage integrations, install the appropriate DVC storage extra for your provider. For example, an S3-based remote generally requires the corresponding S3 dependencies.
Create the project directory and initialize Git and DVC:
mkdir ml-project
cd ml-project
git init
dvc init
DVC initialization creates the configuration and metadata files needed to manage data in the repository.
Commit the initial setup:
git add .dvc .gitignore
git commit -m "Initialize DVC project"
DVC versions can introduce changes to commands and configuration options. Use a compatible version across development and CI environments, and check the installed version when troubleshooting.
Track a Dataset Without Committing It to Git
Suppose the project uses a CSV file containing historical customer transactions.
Place the file in the data directory:
data/training.csv
Track it with DVC:
dvc add data/training.csv
DVC creates a metadata file, typically data/training.csv.dvc, and updates the relevant Git ignore rules so the large data file is not accidentally committed to Git.
The metadata file contains information that allows DVC to identify and retrieve the tracked data. It does not contain the entire dataset.
Commit the metadata:
git add data/training.csv.dvc .gitignore
git commit -m "Track training dataset with DVC"
The dataset itself remains in the working directory. To make it available to other developers or CI workers, configure DVC storage and push the data there.
Configure Remote Storage
A shared remote allows team members to retrieve the same dataset instead of keeping independent copies.
For example, a local directory can be used as a simple remote:
dvc remote add -d storage /path/to/shared/dvc-storage
The path must refer to a suitable storage location accessible to the machines that need the data. A local path is useful for testing but is not automatically shared across different computers.
Commit the remote configuration:
git add .dvc/config
git commit -m "Configure DVC remote storage"
Push the tracked data:
dvc push
For a cloud remote, configure the provider-specific location and authentication according to DVC's supported storage options. Keep credentials outside version-controlled configuration files.
A developer who clones the repository can retrieve the tracked data using:
git clone <repository-address>
cd ml-project
dvc pull
Replace <repository-address> with the actual Git repository address.
This workflow separates source-code history from large data storage while keeping both connected through version control.
Build a Reproducible Training Pipeline
Tracking a dataset is useful, but a machine learning experiment also depends on the operations that transform the data and train the model.
DVC pipelines describe these operations as stages. Each stage declares its command, dependencies, outputs, and optionally parameters and metrics.
Consider a training script that reads a CSV file, trains a model, and writes the resulting artifact.
Create the Training Script
Create src/train.py:
import json
from pathlib import Path
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
import joblib
data_path = Path("data/training.csv")
model_path = Path("models/model.joblib")
metrics_path = Path("metrics.json")
df = pd.read_csv(data_path)
# Example dataset requirements:
# The CSV contains a target column named "target".
if "target" not in df.columns:
raise ValueError("The dataset must contain a 'target' column.")
X = df.drop(columns=["target"])
y = df["target"]
if X.empty or len(df) < 2:
raise ValueError("The dataset does not contain enough training data.")
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42,
stratify=y if y.nunique() > 1 else None
)
model = make_pipeline(
StandardScaler(),
LogisticRegression(max_iter=1000, random_state=42)
)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
model_path.parent.mkdir(parents=True, exist_ok=True)
joblib.dump(model, model_path)
metrics_path.write_text(
json.dumps({"accuracy": accuracy}, indent=2),
encoding="utf-8"
)
print(f"Model accuracy: {accuracy:.4f}")
This example assumes that the CSV contains numeric features suitable for the chosen preprocessing and model. Categorical data, missing values, class imbalance, and other dataset-specific requirements may require additional preprocessing.
The fixed random seed makes the split more consistent, but it does not guarantee identical results across all library versions, platforms, or hardware configurations.
Install the required dependencies:
python -m pip install pandas scikit-learn joblib
Record the project's dependency versions using an appropriate dependency-management workflow. For example, a pinned requirements file can help CI and other developers recreate a compatible environment.
Define the Pipeline
Create dvc.yaml in the project root:
stages:
train:
cmd: python src/train.py
deps:
- src/train.py
- data/training.csv
outs:
- models/model.joblib
metrics:
- metrics.json
This stage tells DVC to execute the training script when its relevant dependencies change.
The deps section identifies inputs that affect the training stage. The outs section identifies the generated model artifact. The metrics section identifies evaluation results that DVC can track and compare.
Run the pipeline:
dvc repro
DVC evaluates the stage dependencies and determines whether the stage needs to run. If the relevant inputs have not changed and the existing outputs are available, DVC can reuse the existing results instead of repeating the work.
After a successful run, commit the pipeline definition and generated lock metadata:
git add dvc.yaml dvc.lock
git commit -m "Add reproducible training pipeline"
If the pipeline produces changes to other tracked metadata or configuration files, include those as appropriate.
Push the model artifact to remote storage:
dvc push
Now the Git commit identifies the pipeline configuration, while DVC tracks the data and generated model artifact associated with that state.
Track Parameters Instead of Hardcoding Them
Machine learning experiments often differ only in a few settings, such as regularization strength, learning rate, batch size, or number of training iterations.
Keeping these values inside the training script makes it harder to compare experiments systematically.
Create a params.yaml file:
train:
test_size: 0.2
random_state: 42
max_iter: 1000
regularization: 1.0
The training script can load these values and use them to configure the model. For example, regularization can be passed to the C parameter of LogisticRegression, while max_iter controls the maximum number of solver iterations.
Add the parameter file to the training stage:
stages:
train:
cmd: python src/train.py
deps:
- src/train.py
- data/training.csv
params:
- train
outs:
- models/model.joblib
metrics:
- metrics.json
The parameter section tells DVC that the train section of params.yaml is an input to the stage. The script must actually read and use these parameters; declaring them in dvc.yaml alone does not change the training behavior.
When you change a tracked parameter, DVC can detect that the stage's inputs have changed and rerun the relevant work.
Compare Machine Learning Experiments
A reproducible workflow should make it possible to compare experiments rather than merely rerun them.
For example, you might train one model with a regularization value of 1.0 and another with 0.1. If the script reads this value from params.yaml, you can use DVC experiments to evaluate the alternative configuration without permanently changing the main project state.
Run an experiment with a modified parameter:
dvc exp run --set-param train.regularization=0.1
DVC runs the pipeline with the specified parameter override and records the experiment state.
To inspect recorded experiments, use:
dvc exp show
The command displays available experiment information, including tracked metrics when configured.
For a more useful comparison, track metrics that match the actual objective. Accuracy alone may not be sufficient for imbalanced classification problems. Precision, recall, F1 score, ROC-AUC, or a business-specific metric may provide a better evaluation.
Also keep the test dataset and evaluation procedure consistent across experiments. If each experiment uses a different test split, apparent improvements may result from differences in the data rather than a better model.
Restore an Earlier Dataset or Model
One of DVC's main benefits is the ability to retrieve artifacts associated with an earlier version-controlled state.
Suppose a dataset update causes model performance to decline. If the earlier Git commit tracks the previous DVC metadata, you can inspect that commit and restore the corresponding files.
For example:
git log --oneline
git checkout <previous-commit>
dvc pull
Replace <previous-commit> with the desired Git commit identifier.
The first command lists the repository history. The second checks out the selected version of the repository, including the DVC metadata. The third retrieves the data and artifacts associated with that state from the configured remote, when they are available.
Checking out an older commit can affect your working tree. Commit or safely preserve any uncommitted changes before switching versions.
For routine collaboration, prefer a dedicated branch or another isolated working environment when investigating historical experiments.
DVC vs. Git for Machine Learning Projects
Capability | Git | DVC |
|---|
Source-code versioning | Core capability | Works alongside Git |
Large dataset management | Can track files, but large binaries can make repositories cumbersome | Tracks data through metadata and external storage |
Pipeline definition | Does not provide native ML pipeline reproduction | Supports dependency-based pipeline stages |
Experiment tracking | Tracks commits and code changes | Supports experiment workflows and metric comparisons |
Artifact storage | Stores committed files in repository history | Can use configured local or remote storage |
Reproducibility | Captures source-code history | Connects code, data, parameters, and pipeline outputs |
DVC does not replace Git. The two tools solve related but different problems and are most useful when used together.
Common Mistakes to Avoid
Forgetting to Push Data to Remote Storage
Committing a .dvc metadata file does not automatically make the dataset available to teammates.
Run dvc push after tracking or generating the required artifacts, and verify that the remote contains the expected data.
Changing Data Outside the Versioned Workflow
If a dataset changes without the corresponding DVC metadata being updated, the repository may no longer describe the actual working data correctly.
After modifying tracked data, run the appropriate DVC commands, review the resulting metadata changes, and commit them with the related code changes.
Assuming a Fixed Random Seed Guarantees Reproducibility
A fixed seed controls some sources of randomness, but library versions, preprocessing behavior, hardware, parallel execution, and nondeterministic algorithms can still affect results.
Record dependencies, parameters, data versions, and relevant environment details. Test reproducibility by running the same pipeline in a clean environment.
Tracking Metrics Without Defining Their Meaning
A metric is useful only when its calculation is consistent and appropriate for the task.
Document the evaluation method, maintain consistent data splits, and avoid selecting models based on metrics that do not reflect the actual problem.
Storing Secrets in the Repository
Cloud credentials, API keys, and other secrets should not be committed to Git or DVC configuration files.
Use a secure secret-management mechanism and grant only the permissions required by the pipeline.
Advantages and Limitations
DVC helps teams manage large datasets without bloating Git history, reproduce pipeline stages, compare experiments, and restore artifacts associated with previous project versions. Its Git-based workflow also makes it easier to review changes to pipeline definitions and parameters.
However, DVC introduces additional configuration and operational responsibilities. Teams must manage remote storage, access permissions, dependency versions, and artifact retention. Reproducibility is also not automatic: the pipeline must explicitly declare its dependencies, and the code must account for nondeterministic behavior and environment differences.
DVC records and retrieves the artifacts you track. It cannot compensate for missing data, undocumented external dependencies, or training code that relies on uncontrolled inputs.
Summary
DVC helps make machine learning experiments reproducible by connecting datasets, model artifacts, parameters, and pipeline stages to version-controlled project history.
A practical workflow starts by tracking large files with DVC, storing them in a shared remote, defining pipeline dependencies in dvc.yaml, and committing the corresponding metadata through Git. Parameter tracking and experiment comparison then help teams understand why one model performs differently from another.
For reliable results, combine DVC with pinned dependencies, consistent evaluation datasets, secure credentials, and documented training procedures. This makes it easier to investigate regressions, reproduce previous experiments, and collaborate without relying on manually maintained copies of datasets and models.
Join the conversation! Your thoughts help the community grow.