3. 版本控制:Git与DVC基础、数据版本管理、模型版本管理
聊到训练流水线,版本控制是绕不开的第一道坎。很多人觉得「不就是 Git 吗?我天天用」。嗯,但训练项目跟普通软件项目还真不一样——你想想看,一个模型动辄几百 MB,数据集甚至上 TB,Git 根本管不了。所以这一章,我们得把 Git 和 DVC 怎么配合讲清楚。
3.1 为什么 Git 不够用?
Git 设计出来是管代码的,它擅长处理文本文件。但我们的训练项目里,真正值钱的东西往往是这三样:代码、数据、模型。代码用 Git 没问题,数据和模型呢?
我在项目中遇到过最典型的场景:同事把 2GB 的 CSV 文件直接 push 到仓库,结果整个团队 clone 一次要等半小时。更麻烦的是,Git 对大文件的 diff 完全无能为力——你根本看不出两个版本的 CSV 到底改了哪几行。
核心矛盾:Git 管理代码的「文本差异」能力很强,但数据和模型是「二进制大文件」,Git 既存不下也 diff 不了。
所以我们需要一个专门管大文件的工具,跟 Git 配合使用。这就是 DVC(Data Version Control)出场的原因。
3.2 Git + DVC 的分工体系
说白了,DVC 不是要替代 Git,而是给 Git 打工。它的核心思路是:
- Git 管代码和元数据:Python 脚本、配置文件、DVC 的 .dvc 文件(这些都很小)
- DVC 管数据和模型:训练集、测试集、模型权重文件(这些都很大)
DVC 会在你的仓库里生成一些 .dvc 文件,这些文件只有几 KB,里面存的是大文件的哈希值和存储位置。Git 只跟踪这些 .dvc 文件,真正的数据文件则被 DVC 推送到远程存储(比如 S3、GCS、NAS)。
我习惯把这种模式叫做「指针文件 + 对象存储」——.dvc 文件就是指针,指向远程存储里的具体对象。
3.3 DVC 基础操作
安装 DVC 很简单,一行命令搞定:
pip install dvc
然后初始化:
cd your-project
git init
dvc init
注意顺序——先有 Git 仓库,再在仓库里初始化 DVC。DVC 会在 .dvc/ 目录下创建自己的配置和缓存。
3.3.1 跟踪数据文件
假设你有一个 data/ 目录,里面是训练集:
dvc add data/train.csv
这条命令做了三件事:
- 把 data/train.csv 复制到 DVC 缓存(.dvc/cache/)
- 生成 data/train.csv.dvc 指针文件
- 把 data/train.csv 加入 .gitignore(防止 Git 误跟踪)
然后你只需要把 .dvc 文件提交到 Git:
git add data/train.csv.dvc .gitignore
git commit -m "add training data"
我的习惯:每次 dvc add 之后立刻 git commit,保持 .dvc 文件与代码版本一一对应。这样回溯到某个 Git commit 时,对应的数据版本也自动确定了。
3.3.2 配置远程存储
本地缓存只是临时中转,真正的数据要推到远程。我常用 S3 兼容的对象存储:
dvc remote add -d myremote s3://my-bucket/dvc-store
dvc push
其他团队成员 clone 仓库后,只需要:
dvc pull
就能把当前版本对应的数据文件拉下来。整个过程对开发者来说几乎是透明的——你只看到 .dvc 文件变了,但背后 DVC 帮你搞定了大文件的版本切换。
3.4 数据版本管理实战
数据版本管理最核心的场景是:你改了数据,想回退到旧版本。
举个例子。你训练了一个模型,效果不错。后来同事更新了数据,重新训练后效果反而变差了。你想看看是不是数据出了问题。
用 Git 查看历史:
git log --oneline
# a1b2c3d add new training data
# e4f5g6h initial data version
切回旧版本:
git checkout e4f5g6h
dvc checkout
dvc checkout 会根据当前 .dvc 文件里的哈希值,从缓存里恢复对应的数据文件。整个过程不需要重新下载——只要缓存里还有旧版本的数据。
注意:dvc checkout 只恢复 DVC 跟踪的文件,不会影响 Git 管理的代码。如果你同时需要旧代码和旧数据,先 git checkout 再 dvc checkout。
3.4.1 数据集的增量更新
我在项目中遇到过另一个常见问题:数据集很大,每次只新增了几个样本,但 dvc add 会把整个目录重新哈希一遍,很慢。
DVC 2.0 之后引入了 dvc commit 模式:
# 先修改数据文件
# 然后告诉 DVC 文件变了
dvc status # 会显示 data/ 有变更
dvc commit data/ # 只把变更的文件加入缓存
这样避免了全量重新哈希,对大数据集友好很多。
3.5 模型版本管理
模型文件本质上也是大文件,管理方式跟数据一样。但我建议把模型和数据分开跟踪,因为它们的更新频率和生命周期不同。
我习惯在项目里建一个 models/ 目录:
dvc add models/model_v1.pkl
git add models/model_v1.pkl.dvc
git commit -m "model v1: ResNet50, 85.2% accuracy"
这样每个模型版本都有一个对应的 Git commit,commit message 里可以写清楚模型的关键信息(架构、精度、训练数据版本)。
3.5.1 模型注册表思路
当模型版本多起来之后,光靠 Git log 查找会变得很痛苦。我建议用 models/registry.yaml 来维护一个简单的模型注册表:
models:
- name: resnet50_v1
path: models/resnet50_v1.pkl
accuracy: 0.852
data_version: e4f5g6h
trained_by: "张三"
date: "2024-03-15"
- name: resnet50_v2
path: models/resnet50_v2.pkl
accuracy: 0.873
data_version: a1b2c3d
trained_by: "李四"
date: "2024-03-20"
这个 registry.yaml 本身由 Git 管理,每次训练完模型后更新它。这样你只需要看这个文件,就能知道所有模型版本的来龙去脉。
最佳实践:模型文件名里不要只写 v1、v2,建议包含关键指标,比如 resnet50_acc87.pkl。这样即使脱离 registry,光看文件名也能知道大概。
3.6 常见问题与避坑
最后分享几个我踩过的坑:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| dvc push 很慢 | 网络带宽不足,或远程存储距离远 | 使用 dvc push --jobs 4 并行上传;或配置就近的远程存储 |
| 缓存占用磁盘太大 | .dvc/cache 里积累了所有历史版本 | 定期 dvc gc 清理无用缓存;或设置缓存大小上限 |
| 多人同时修改数据 | DVC 没有锁机制,可能冲突 | 约定数据修改前先 git pull + dvc pull,改完立即 push |
| .dvc 文件冲突 | 两个分支都改了同一个 .dvc 文件 | 手动合并 .dvc 文件(本质是 YAML,可读性还行) |
我曾经有一次因为没及时 dvc gc,缓存撑爆了服务器磁盘,导致整个团队的 CI 全部挂掉。嗯,从那以后我养成了每周跑一次 dvc gc 的习惯。
小技巧:在 .dvc/config 里可以设置 cache.type = symlink,这样工作目录里的文件是符号链接,不占用额外磁盘空间。但要注意,Windows 上 symlink 需要管理员权限。
版本控制这件事,说白了就是给项目的每个关键状态拍一张快照。Git 拍代码的快照,DVC 拍数据和模型的快照。两者配合好了,你就能随时回到任何一个历史状态,复现当时的实验结果。这是训练流水线自动化的基石,也是后续所有自动化流程的前提。