2536c937e3
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。 第01章 向量 | 第02章 矩阵 | 第03章 微积分 第04章 统计学 | 第05章 概率论 | 第06章 机器学习 第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音 第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络 第13章 计算与操作系统 | 第14章 数据结构与算法 第15章 生产级软件工程 | 第16章 SIMD与GPU编程 第17章 AI推理 | 第18章 ML系统设计 第19章 应用人工智能 | 第20章 前沿人工智能 翻译说明: - 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留 - mkdocs.yml 配置中文导航 + language: zh - README.md 已翻译为中文(兼 docs/index.md) - docs/ 目录包含指向各章文件的 symlink - 约 29,000 行中文内容,排除 .cache/ 构建缓存
219 lines
9.9 KiB
Markdown
219 lines
9.9 KiB
Markdown
# Git 与版本控制
|
||
|
||
*Git 是软件团队在不相互覆盖工作的情况下进行协作的方式。本文涵盖 Git 的心智模型、分支策略、合并与变基、冲突解决、拉取请求,以及管理机器学习特定挑战(如大文件和实验追踪)的方法。*
|
||
|
||
- 每个严肃的软件项目都使用版本控制。**Git** 是主导系统,几乎所有开源项目和公司都在使用。没有 Git,协作就是通过电子邮件发送 zip 文件并祈祷没人覆盖你的更改。有了 Git,每次更改都可追踪、可撤销、可追溯。
|
||
|
||
- 对于机器学习工程师:Git 追踪你的代码、配置和实验脚本。结合实验追踪工具,它能提供可重现性:"是哪个确切的代码和配置产生了这个模型?"
|
||
|
||
## 心智模型
|
||
|
||
- Git 追踪项目的**快照**。每次提交都是那一刻所有追踪文件的完整快照,而不是差异(在内部,Git 为效率存储差异,但从概念上讲,每次提交都是一个完整状态)。
|
||
|
||
- 文件的四个"位置":
|
||
|
||
1. **工作目录**:磁盘上的实际文件。你在这里编辑。
|
||
2. **暂存区**(索引):你标记为下一次提交的文件。`git add` 将更改移到这里。
|
||
3. **本地仓库**:你的提交历史,存储在 `.git/` 中。`git commit` 将暂存区保存为新的快照。
|
||
4. **远程仓库**(例如 GitHub):一个共享副本。`git push` 上传你的提交,`git pull` 下载他人的提交。
|
||
|
||
```
|
||
Working Dir → git add → Staging → git commit → Local Repo → git push → Remote
|
||
← git pull ←
|
||
```
|
||
|
||
- 暂存区正是 Git 强大之处。你可以编辑 10 个文件,但只提交其中的 3 个,将其他更改保留给另一次提交。这使得清晰的、有重点的提交成为可能。
|
||
|
||
### 基本命令
|
||
|
||
```bash
|
||
git init # 创建新仓库
|
||
git clone url # 下载远程仓库
|
||
git status # 有什么变化?(最常用的命令)
|
||
git add file.py # 暂存特定文件
|
||
git add . # 暂存所有更改(谨慎使用)
|
||
git commit -m "descriptive msg" # 提交暂存的更改
|
||
git push # 将提交上传到远程
|
||
git pull # 下载并合并远程更改
|
||
git log --oneline # 紧凑的提交历史
|
||
git diff # 显示未暂存的更改
|
||
git diff --staged # 显示已暂存的更改
|
||
```
|
||
|
||
## 分支
|
||
|
||
- **分支**是指向一次提交的指针。默认分支是 `main`(或 `master`)。创建分支让你拥有独立的开发线:你可以在不影响 `main` 的情况下进行更改。
|
||
|
||
```bash
|
||
git branch feature-x # 创建分支
|
||
git checkout feature-x # 切换到此分支
|
||
git checkout -b feature-x # 创建并切换(一步完成)
|
||
git branch -d feature-x # 删除分支(合并后)
|
||
git branch -a # 列出所有分支(本地 + 远程)
|
||
```
|
||
|
||
- **何时分支**:始终需要。永远不要直接提交到 `main`。每个功能、错误修复或实验都有其自己的分支。这保持了 `main` 的稳定性和可部署性。
|
||
|
||
### 分支策略
|
||
|
||
- **功能分支**(最常见):每个功能/修复从 `main` 创建一个分支。完成后,打开拉取请求(PR)以合并回去。简单,适用于大多数团队。
|
||
|
||
- **主干开发**:开发人员频繁(每天多次)提交到 `main`,使用特性标记隐藏未完成的工作。持续部署的团队(Google、Facebook)更偏好这种方式。需要优秀的 CI/CD。
|
||
|
||
- **Gitflow**:为功能、发布和热修复设置单独的分支。更复杂,适用于有版本化发布的软件(移动应用、打包软件)。对大多数机器学习项目来说过于复杂。
|
||
|
||
- 对于机器学习团队:**功能分支**配合短生命周期的分支(1-3 天内合并)是最佳选择。生命周期长的分支会与 `main` 产生分歧,导致痛苦的合并冲突。
|
||
|
||
## 合并与变基
|
||
|
||
- **合并**创建一个新的"合并提交",将两个分支合并:
|
||
|
||
```bash
|
||
git checkout main
|
||
git merge feature-x
|
||
```
|
||
|
||
- 这保留了完整的历史记录:你可以看到工作是在分支上完成的,以及何时合并的。合并提交有两个父节点。
|
||
|
||
- **变基**在你的分支上重放提交到目标分支之上:
|
||
|
||
```bash
|
||
git checkout feature-x
|
||
git rebase main
|
||
```
|
||
|
||
- 这会重写历史:你的分支上的提交会获得新的哈希值,就好像你是从 `main` 的当前顶端开始工作一样。结果是线性的历史记录(没有合并提交),阅读起来更清晰。
|
||
|
||
- **何时使用哪种**:
|
||
- **变基**用于使用最新的 `main` 更改更新你的功能分支(保持分支整洁和最新)。
|
||
- **合并**用于将你的功能分支集成到 `main`(保留分支历史)。
|
||
- **永远不要变基已经推送并与他人共享**的提交。变基会重写历史;如果其他人已经基于原始提交开展工作,变基会导致混乱。
|
||
|
||
## 解决冲突
|
||
|
||
- **冲突**发生在两个分支修改同一文件的同一行时。Git 无法自动决定保留哪个更改,需要你手动解决。
|
||
|
||
```
|
||
<<<<<<< HEAD
|
||
learning_rate = 0.001
|
||
=======
|
||
learning_rate = 0.0005
|
||
>>>>>>> feature-x
|
||
```
|
||
|
||
- `<<<<<<< HEAD` 和 `=======` 之间是当前分支的版本。`=======` 和 `>>>>>>> feature-x` 之间是传入分支的版本。你决定保留哪个(或组合它们),删除标记,保存,然后运行 `git add` 添加已解决的文件。
|
||
|
||
- **陷阱**:不要在已提交的文件中留下冲突标记。它们是会破坏你代码的字面文本。解决后始终搜索 `<<<<<<<`。
|
||
|
||
- **减少冲突**:保持分支短生命周期,频繁将 `main` 合并到你的分支中,避免多人同时编辑同一个文件。
|
||
|
||
## 编写良好的提交信息
|
||
|
||
- 提交信息是为了未来的你和你的队友。"修复错误"告诉不了你什么。"修复批次大小计算中的差一错误,该错误导致 8-GPU 训练时 OOM"告诉你一切。
|
||
|
||
- **格式**:
|
||
|
||
```
|
||
简短摘要(50 字以内,祈使语气)
|
||
|
||
如果需要,可附带更长的描述。解释 WHY,而不是 WHAT
|
||
(差异显示了什么改变了)。每行不超过 72 个字符。
|
||
|
||
Fixes #123
|
||
```
|
||
|
||
- **祈使语气**:"添加功能"而不是"已添加功能"或"添加了功能"。将其视为完成句子:"如果应用此提交,它将**添加功能**。"
|
||
|
||
- **原子提交**:每个提交应做一件事。"添加数据加载器"是一个提交。"添加数据加载器并修复无关的错误并更新 README"应该是三个提交。这使得 `git bisect`(找到哪个提交引入了错误)成为可能。
|
||
|
||
## 拉取请求与代码审查
|
||
|
||
- **拉取请求(PR)**提议将一个分支合并到 `main`。它是代码审查的门户:队友阅读你的更改,提出改进建议,并在合并前批准。
|
||
|
||
- **良好的 PR 实践**:
|
||
- 保持 PR 小(少于 400 行更改)。大的 PR 会被敷衍批准,因为没人想审查 2000 行。
|
||
- 编写清晰的描述:更改了什么、为什么以及如何测试。
|
||
- 链接到促使更改的问题或工单。
|
||
- 及时回复审查评论。
|
||
- 在合并前压缩琐碎的提交(这样 `main` 就有干净的历史记录)。
|
||
|
||
- **代码审查不是为了找错误**(测试来做这个)。它的目的是:知识分享(审查者学习代码库)、设计反馈(这是正确的方法吗?)和维护标准(命名、风格、架构)。
|
||
|
||
## .gitignore
|
||
|
||
- `.gitignore` 文件告诉 Git 排除哪些文件不被追踪。对于机器学习项目:
|
||
|
||
```gitignore
|
||
# Python
|
||
__pycache__/
|
||
*.pyc
|
||
*.egg-info/
|
||
.venv/
|
||
env/
|
||
|
||
# 数据和模型(对 git 来说太大)
|
||
data/
|
||
*.csv
|
||
*.parquet
|
||
models/
|
||
*.pt
|
||
*.onnx
|
||
*.bin
|
||
checkpoints/
|
||
|
||
# 密钥
|
||
.env
|
||
*.pem
|
||
credentials.json
|
||
|
||
# IDE
|
||
.vscode/
|
||
.idea/
|
||
*.swp
|
||
|
||
# 操作系统
|
||
.DS_Store
|
||
Thumbs.db
|
||
|
||
# Jupyter
|
||
.ipynb_checkpoints/
|
||
|
||
# 实验输出
|
||
wandb/
|
||
mlruns/
|
||
outputs/
|
||
logs/
|
||
```
|
||
|
||
- **陷阱**:在文件已被提交后将文件添加到 `.gitignore` 不会将其从仓库中移除。你还必须使用 `git rm --cached file` 来取消追踪。该文件会永远留在历史中,除非你重写历史(这很麻烦)。
|
||
|
||
## Git 在机器学习中的应用
|
||
|
||
- 机器学习引入了传统软件不面临的挑战:
|
||
|
||
- **大文件**:数据集和模型权重可能有数 GB 或更大。Git 是为文本文件(源代码)设计的,而不是二进制 blob。解决方案:
|
||
- **Git LFS**(大文件存储):在 Git 中追踪指针,将实际文件存储在单独的服务器上。简单,但在 GitHub 上有限制存储/带宽。
|
||
- **DVC**(数据版本控制):将数据和模型文件与 Git 分开管理,使用远程存储(S3、GCS)。像 Git 一样用于数据:`dvc add data.csv`、`dvc push`、`dvc pull`。
|
||
|
||
- **实验追踪**:哪个提交 + 哪些超参数 + 哪个数据产生了哪些指标?Git 追踪代码,但不追踪完整的实验上下文。
|
||
- **Weights & Biases(W&B)**:记录指标、超参数、系统信息,并链接到 Git 提交。提供用于比较运行结果的仪表板。
|
||
- **MLflow**:开源的实验追踪,带有模型注册表。记录参数、指标和产物。
|
||
- **简单方法**:在你的训练脚本中记录 Git 哈希值:`git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()`。将其与你的结果一起存储。
|
||
|
||
- **可重现性检查清单**(每个实验需要追踪的内容):
|
||
- Git 提交哈希值(确切的代码版本)
|
||
- 配置文件 / 超参数
|
||
- 随机种子
|
||
- Python 和库版本(`pip freeze`)
|
||
- 数据版本(DVC 哈希值或数据集版本标签)
|
||
- 硬件(GPU 类型、GPU 数量)
|
||
|
||
```bash
|
||
# 快速可重现性快照
|
||
echo "Commit: $(git rev-parse HEAD)" > experiment_info.txt
|
||
echo "Branch: $(git branch --show-current)" >> experiment_info.txt
|
||
echo "Dirty: $(git status --porcelain | wc -l) files" >> experiment_info.txt
|
||
pip freeze >> experiment_info.txt
|
||
nvidia-smi >> experiment_info.txt
|
||
```
|