3. 版本控制基础与医学影像模型
医学影像模型的一个版本,从来不只是“一个 .pt 或 .onnx 文件”。它至少由四类对象共同定义:模型权重(几十 MB 到数 GB 的二进制张量)、训练与推理配置(YAML/JSON、预处理参数、窗宽窗位、体素间距、类别映射)、数据版本(DICOM/NIfTI 数据集、标注、划分清单)以及代码(网络结构、损失、后处理)。Git 擅长管理文本与代码,却对二进制大文件天然不友好;DVC 与 Git LFS 正是为弥补这一缺口而出现。理解三者的分工,是医学影像模型可复现发布的前提。
一、三种工具的本质差异
Git 是分布式内容寻址存储:每个对象按 SHA-1(新版本为 SHA-256)哈希存储,仓库中保存完整历史。它对文本做增量 diff,但对二进制文件每次修改都保存整份副本,导致仓库迅速膨胀。一个 200 MB 的模型权重改 10 次,仓库就多出约 2 GB,且 clone 时全量下载,这是 Git 不适合直接存权重的根本原因。
Git LFS(Large File Storage)采用“指针替换”策略:仓库中只保存一个约 130 字节的文本指针文件,真实内容存放在远端 LFS 服务器。clone 时按需拉取。它保留了 Git 的分支、标签、提交语义,适合“文件数量少、变更不频繁、需要与代码同版本”的场景,例如模型权重、少量固定测试影像。
DVC(Data Version Control)走得更远:它把大文件与目录的哈希写入 .dvc 元文件,真实数据放在本地缓存或远端存储(S3、MinIO、NAS、SSH)。DVC 的核心价值在于数据流水线——用 dvc.yaml 描述 stage 的依赖与输出,DVC 据此判断哪些阶段需要重跑,实现“数据变了才重训”。它更适合数据集、标注、中间特征这类体量大、目录结构复杂、需要追踪血缘的对象。
二、适用性对照
| 维度 | Git | Git LFS | DVC |
|---|---|---|---|
| 存储对象 | 代码、配置、小文本 | 少量大二进制文件 | 数据集、目录、权重、中间产物 |
| 仓库体积 | 小 | 小(仅指针) | 小(仅 .dvc 元文件) |
| 版本粒度 | 文件级 | 文件级 | 文件/目录级 + 流水线级 |
| 血缘追踪 | 无 | 无 | 有(dvc.lock 记录依赖哈希) |
| 典型医学影像用途 | 训练脚本、配置、类别映射 | 发布用模型权重、固定测试集 | DICOM/NIfTI 数据集、标注、特征 |
实践中的常见组合是:代码与配置用 Git,数据集与中间产物用 DVC,发布权重用 Git LFS 或 DVC。三者并不互斥,而是分层协作。
三、一个可落地的混合工作流
假设一个肺结节分割项目,目录结构如下:
lung-nodule/
├── src/ # 网络、损失、后处理(Git)
├── configs/
│ └── seg_v2.yaml # 预处理与训练配置(Git)
├── data/
│ └── train.dvc # 指向 DICOM 数据集(DVC)
├── models/
│ └── seg_v2.pt # 发布权重(Git LFS)
└── dvc.yaml # 流水线定义
初始化与接入:
git init && git lfs install
git lfs track "models/*.pt"
dvc init
dvc remote add -d storage ssh://nas/medimg-dvc
dvc add data/train
git add .gitattributes data/train.dvc models/seg_v2.pt
git commit -m "feat: seg_v2 权重与数据集版本"
dvc push
其中 dvc add data/train 会生成 data/train.dvc,内容形如:
outs:
- md5: 8f3c1a9e2b7d4c05e6f1a2b3c4d5e6f7.dir
size: 48213452800
nfiles: 12640
path: train
这个 md5 是目录内容的整体哈希。只要数据集任何一个文件变化,哈希就变,DVC 便能识别“数据版本已更新”。
四、流水线与可复现性
dvc.yaml 把“数据→预处理→训练→评估”串成有向无环图:
stages:
preprocess:
cmd: python src/preprocess.py --in data/train --out data/processed
deps: [src/preprocess.py, data/train]
outs: [data/processed]
train:
cmd: python src/train.py --cfg configs/seg_v2.yaml
deps: [src/train.py, configs/seg_v2.yaml, data/processed]
outs: [models/seg_v2.pt]
metrics: [metrics.json]
执行 dvc repro 时,DVC 比对 deps 与 outs 的哈希:若 configs/seg_v2.yaml 改了学习率,train 阶段重跑;若只改了评估脚本,preprocess 与 train 均跳过。dvc.lock 记录每次运行的精确哈希,使“某次发布的权重对应哪份数据、哪份配置”可被精确回溯。
五、选择判断与常见陷阱
判断用哪个工具,可先问三个问题:对象是文本还是二进制?变更频率高不高?是否需要记录“数据→模型”的血缘?文本、低频、无需血缘,用 Git;二进制、低频、需与代码同版本,用 Git LFS;体量大、目录复杂、需要流水线复现,用 DVC。
常见陷阱包括:把 DICOM 直接 git add,仓库数月内膨胀到数十 GB;git lfs track 写在提交之后,历史大文件仍留在 Git 对象库,需用 git lfs migrate import 重写历史;DVC 只 dvc add 却忘记 dvc push,换机器后 dvc pull 失败;以及把患者隐私数据推到公共远端。医学影像场景下,远端存储应启用访问控制与加密,DICOM 中的 PHI 需先脱敏再入库。
把版本控制做对,模型发布才不是“把文件拷到服务器”。Git 管代码与配置,DVC 管数据与流水线,Git LFS 管发布权重,三者各司其职,共同构成医学影像模型可追溯、可复现的版本基线。