3. 版本控制入门:Git 基础操作

说到可复现实验,版本控制是绕不开的一环。你想想看,一个实验跑完,代码改了三版,数据换了五版,最后连自己都分不清哪个是最终版——这种经历,我猜你也有过。

Git 就是来解决这个问题的。它不复杂,核心就几个命令。但用好它,你的实验就有了「后悔药」和「时光机」。

3.1 为什么实验代码需要版本控制?

我刚开始做研究时,代码管理全靠文件夹命名:实验_v1、实验_v2_最终、实验_v3_真的最终……结果有一次,我把一个跑了两天的模型代码改崩了,想回退却找不到原始版本。嗯,从那以后,我再也不敢裸写代码了。

版本控制能给你三样东西:

  • 历史回溯:每一次修改都有记录,随时可以回到任意时间点
  • 变更追踪:谁、在什么时候、改了哪些文件,一目了然
  • 协作基础:多人同时开发,互不干扰,最后合并

核心原则:实验代码的每一次「可复现」状态,都应该对应一个 Git 提交。跑通一个实验,就 commit 一次。

3.2 Git 的三大区域

理解 Git,先要搞懂它的三个区域。说白了,就是文件在三个地方流转:

区域 作用 类比
工作区(Working Directory) 你实际编辑文件的地方 你的实验台
暂存区(Staging Area) 临时存放准备提交的修改 你的待办清单
仓库(Repository) 永久保存所有历史版本 你的实验档案柜

流程很简单:你在工作区改代码,改好了用 git add 放到暂存区,确认无误后用 git commit 存入仓库。就这么三步。

Git 三大区域与数据流向 工作区 编辑文件 暂存区 准备提交 本地仓库 永久保存 git add git commit 核心流程 工作区修改 → git add → 暂存区 → git commit → 本地仓库

3.3 初始化仓库:git init

一切从 git init 开始。这个命令会在当前目录创建一个隐藏的 .git 文件夹,里面装着 Git 需要的所有元数据。

cd ~/projects/my_experiment
git init

执行后,你会看到类似 Initialized empty Git repository in ... 的提示。这就意味着你的实验代码有了「户口」。

我的习惯:每个实验项目都单独建一个仓库,不要把所有实验塞到一个仓库里。否则历史记录会乱成一锅粥。

3.4 添加文件到暂存区:git add

改完代码后,用 git add 把文件加入暂存区。你可以指定单个文件,也可以一次加所有文件。

# 添加单个文件
git add train.py

# 添加所有变更文件
git add .

# 添加指定目录
git add utils/

我个人建议用 git add . 之前,先跑一下 git status 看看哪些文件被改了。避免把不该提交的文件(比如数据集、日志文件)也加进去。

我曾经踩过的坑:有一次不小心把 2GB 的数据集 add 进去了,提交时卡了半天。后来我学会了用 .gitignore 文件来排除这些大文件。

3.5 提交到仓库:git commit

git commit 是把暂存区的修改永久保存到仓库。每次提交都要写一条消息,说明这次改了啥。

git commit -m "完成数据预处理模块,修复缺失值处理逻辑"

提交消息的写法有讲究。我总结了一个简单模板:

类型 格式 示例
新增功能 feat: 简短描述 feat: 添加模型评估函数
修复 bug fix: 简短描述 fix: 修复数据加载路径错误
重构代码 refactor: 简短描述 refactor: 拆分训练脚本
文档更新 docs: 简短描述 docs: 更新 README 使用说明

为什么要这么写?因为几个月后你回头看提交历史,一眼就能知道每个版本做了什么。说白了,这是写给未来的自己看的。

3.6 推送到远程仓库:git push

本地仓库的代码,只有推送到远程(比如 GitHub、GitLab),才算真正安全。万一电脑坏了,代码还能从远程恢复。

# 添加远程仓库
git remote add origin https://github.com/yourname/experiment.git

# 推送代码
git push -u origin main

-u 参数的意思是设置上游分支,以后直接 git push 就行,不用每次都指定远程和分支。

我的建议:每次实验跑通一个关键节点,就 push 一次。这样即使本地代码丢了,远程还有备份。我一般每天至少 push 一次。

3.7 拉取远程更新:git pull

如果你在多台电脑上工作,或者和同事协作,就需要用 git pull 把远程的最新代码拉下来。

git pull origin main

这个命令相当于 git fetch + git merge。它会先下载远程的更新,然后合并到你的当前分支。

注意:pull 之前,最好先 commit 或 stash 你本地的修改。否则可能会遇到合并冲突。我遇到过几次冲突,解决起来挺麻烦的。

3.8 实验代码的版本历史管理

有了 Git,你的实验就有了完整的版本历史。用 git log 可以查看所有提交记录。

git log --oneline --graph --all

这个命令会以图形化的方式展示分支和提交历史。看起来就像一棵树,每个节点就是一个版本。

我一般这样管理实验版本:

  • v0.1:初始代码框架,能跑通但结果不对
  • v0.2:修复数据预处理 bug,结果接近预期
  • v0.3:调参后效果提升,记录超参数配置
  • v1.0:实验复现成功,提交最终版本

每个版本都对应一个 commit。这样,你可以随时用 git checkout 回到任意版本,重新跑实验。

小技巧:在 commit 消息里加上实验的关键参数,比如 feat: 添加模型,学习率 0.001,batch_size 32。这样看历史就知道每个版本用了什么配置。

3.9 避坑指南

最后,分享几个我踩过的坑:

  • 不要提交大文件:数据集、模型权重文件用 Git LFS 管理,或者单独存放
  • 不要提交敏感信息:、密码等用环境变量,别写死在代码里
  • 不要频繁 commit:每次提交应该是一个完整的逻辑单元,改一行字就 commit 没必要
  • 不要忘记写 commit 消息:空消息的提交,以后根本看不懂

嗯,Git 的基础操作就这些。说白了,就是 init、add、commit、push、pull 五个命令。但用好它们,你的实验代码就有了完整的版本历史,可复现性也就有了基础保障。