Git 已成为源代码版本控制的规范,这是有道理的。它让团队能够高效协作,追踪随时间推移的变化,回溯到早期状态,并通过分支管理不同的开发线。对于软件开发,Git 通常足以保证代码库本身的可复现性。如果你拥有特定 Git 提交的代码,通常可以重新构建软件在该时间点的样子(假定外部依赖也得到管理)。然而,机器学习项目带来了复杂性,使得 Git 超出了其原设计用途,特别是在数据和模型产物方面。这里是为什么仅依靠 Git 实现机器学习可复现性存在不足的原因:大型文件的挑战机器学习通常始于数据,数据集可以非常大,从吉字节到太字节不等。同样,训练好的模型,特别是深度学习网络等复杂的模型,也可能产生大型二进制文件。Git 并非为高效处理大型二进制文件而设计。其核心机制是为每次提交存储所有被追踪文件的快照。当你提交对大型文件的更改时,Git 本质上会存储一份新副本(或者压缩的差异数据,但存储空间仍会显著增加)。这会带来一些问题:仓库膨胀: 存储大型文件的多个版本会导致 .git 目录迅速增长,使仓库难以管理。性能下降: git clone、git checkout 和 git push 等操作变得非常缓慢,因为它们需要传输和管理这些大型文件。检出之前的提交可能涉及下载数吉字节的数据,即使你只在该时间点需要代码。存储成本和限制: 托管服务提供商通常对仓库大小有限制。即使是自行托管,存储需求也可能变得过高。设想一个情景,你有一个 10GB 的数据集。如果你将其添加到 Git 中,然后更新它,比如通过添加新样本或预处理,Git 将存储这个大型文件的版本。几次更新后,你的仓库大小很容易膨胀数十或数百吉字节。Git LFS:一个部分解决方案认识到大型文件的问题,Git 大型文件存储 (LFS) 应运而生。Git LFS 将 Git 仓库中的大型文件替换为小型文本指针。实际的大型文件存储在单独的 LFS 服务器上。当你检出提交时,Git 获取代码,Git LFS 客户端根据指针下载相应的大型文件。尽管 Git LFS 有助于缓解将大型文件直接存储在 Git 中所带来的仓库膨胀和性能问题,但它并未完全满足机器学习工作流程的可复现性需求:元数据脱节: LFS 主要管理存储。它不自然地将训练运行中使用的特定数据集版本与生成的模型或达到的指标关联起来。你仍然需要一个单独的机制来记录“模型 X 是使用数据集指针 Y 和代码提交 Z 训练的”。数据谱系: LFS 追踪文件版本,但它不记录数据转换的谱系。如果你的数据集经过多个预处理步骤(清洗、特征工程),单凭 LFS 无法表示此工作流程,也无法让你轻松复现中间数据状态。操作负担: 设置和管理 LFS 服务器会增加复杂性,同步 LFS 文件仍然需要明确的 git lfs pull 命令。追踪实验,而非仅仅文件机器学习中的可复现性不仅仅是拥有正确版本的代码和数据文件。它是关于重新创建整个实验背景:使用了哪个特定版本的数据?(例如,dataset_v2.1.csv)哪个版本的代码处理了数据并训练了模型?(例如,Git 提交 a1b2c3d)训练使用了哪些超参数?(例如,学习率 = 0.001,批大小 = 32)结果指标是什么?(例如,准确率 = 92.5%)哪个特定软件环境和依赖项处于活跃状态?Git 有效地追踪代码更改。它能够追踪数据文件(存在困难或通过 LFS),但它没有内置机制来系统地记录超参数、指标,或者特定数据版本、代码版本和实验结果之间复杂的依赖关系。提交配置文件有所帮助,但它不提供可查询的、集中的实验记录,也无法直接关联到所用的特定大型数据资产。试图仅通过 Git 提交信息或单独的电子表格管理这些信息,会很快变得难以管理且容易出错,特别是随着实验数量的增加。因此,尽管 Git 是机器学习项目代码组件版本控制的一个重要基础,但我们需要专门设计用来处理大型数据集版本控制和追踪实验完整背景的补充工具。这就是数据版本控制 (DVC) 和 MLflow 等工具发挥作用的地方,它们与 Git 配合使用,为机器学习可复现性提供一个更完整的解决方案。