4. 算法工程项目的目录结构与依赖管理
为什么算法项目需要固定目录结构
算法项目与普通业务代码的差异在于:它同时包含可执行代码(训练、推理、评估)、不可执行但必须版本化的资产(配置、模型权重、数据快照)以及环境契约(依赖版本、Python 版本、CUDA 版本)。如果目录随意堆放,会出现三类典型故障:训练脚本找不到配置、推理服务加载了错误版本的模型、本地能跑而部署环境因依赖漂移而崩溃。因此需要一套约定俗成的目录骨架,把"代码—配置—数据—模型—环境"五类对象分开放置,并让每一类都有明确的读写边界。
目录骨架的落地方式
下面给出一个可直接使用的目录创建脚本。它同时完成三件事:建立目录树、写入占位文件、生成依赖锁定文件。脚本使用 set -euo pipefail 保证任何一步失败立即中止,避免留下半成品目录。
#!/usr/bin/env bash
# init_algo_project.sh — 初始化算法工程目录骨架
set -euo pipefail
PROJECT="${1:-algo-project}"
mkdir -p "$PROJECT"/{src/{data,models,train,infer},configs,tests,scripts,docs,artifacts}
# 源码包标识
touch "$PROJECT"/src/__init__.py
touch "$PROJECT"/src/data/__init__.py
touch "$PROJECT"/src/models/__init__.py
touch "$PROJECT"/src/train/__init__.py
touch "$PROJECT"/src/infer/__init__.py
# 配置分层:base 提供默认值,train/infer 只写覆盖项
cat > "$PROJECT"/configs/base.yaml <<'YAML'
seed: 42
data:
path: data/raw/train.csv
batch_size: 64
model:
name: mlp
hidden_dim: 128
train:
epochs: 20
lr: 0.001
YAML
cat > "$PROJECT"/configs/train.yaml <<'YAML'
defaults:
- base
train:
epochs: 50
YAML
cat > "$PROJECT"/configs/infer.yaml <<'YAML'
defaults:
- base
model:
checkpoint: artifacts/model_v1.pt
YAML
# 依赖声明:直接依赖写版本范围,锁定文件由 pip-compile 生成
cat > "$PROJECT"/requirements.txt <<'REQ'
numpy>=1.24,<2.0
pandas>=2.0,<3.0
scikit-learn>=1.3,<2.0
pyyaml>=6.0,<7.0
REQ
# 虚拟环境与锁定
python3 -m venv "$PROJECT"/.venv
"$PROJECT"/.venv/bin/pip install --upgrade pip
"$PROJECT"/.venv/bin/pip install -r "$PROJECT"/requirements.txt
"$PROJECT"/.venv/bin/pip freeze > "$PROJECT"/requirements.lock
echo "工程骨架已创建:$PROJECT"
echo "激活环境:source $PROJECT/.venv/bin/activate"
依赖锁定的关键区别
requirements.txt 与 requirements.lock 承担不同职责,混用会导致环境漂移:
| 文件 | 内容 | 是否提交版本控制 | 更新时机 |
|---|---|---|---|
| requirements.txt | 直接依赖 + 版本范围(如 numpy>=1.24,<2.0) | 是 | 新增或升级直接依赖时手动修改 |
| requirements.lock | pip freeze 输出的全量精确版本(含传递依赖) | 是 | 每次修改 requirements.txt 后重新生成 |
| .venv/ | 本地虚拟环境目录 | 否(写入 .gitignore) | 每台机器独立创建 |
部署时使用
pip install -r requirements.lock,而不是 requirements.txt。前者保证传递依赖也被固定,后者只约束直接依赖,传递依赖仍可能被解析到不同版本。验证环境一致性
创建完成后,用以下命令验证本地环境与锁定文件一致:
# 1. 检查虚拟环境是否激活
which python
# 预期输出包含 algo-project/.venv/bin/python
# 2. 对比当前环境与锁定文件的差异
pip freeze | diff - requirements.lock
# 无输出表示完全一致;有输出则说明环境已漂移
# 3. 验证配置可被正确加载
python -c "
import yaml
with open('configs/train.yaml') as f:
cfg = yaml.safe_load(f)
print('epochs =', cfg['train']['epochs'])
print('batch_size =', cfg['data']['batch_size'])
"
# 预期输出:epochs = 50 / batch_size = 64
如果
pip freeze | diff - requirements.lock 有输出,不要直接覆盖锁定文件。先确认差异来源:是手动安装了未声明的包,还是锁定文件过期。前者应补进 requirements.txt 后重新生成锁定文件,后者直接重新生成即可。常见目录误用与修正
- 把模型权重放进 src/:权重是产物不是源码,应放
artifacts/,并在 .gitignore 中排除大文件,用版本号或哈希命名(如model_v1_20240115.pt)。 - 配置写死在代码里:所有超参、路径、阈值都应外置到
configs/,代码只负责读取和校验,这样同一份代码可通过切换配置完成训练与推理。 - 测试目录为空:至少覆盖数据字段校验、模型前向形状、接口契约三类,否则重构时无法快速发现破坏性变更。
- 提交 .venv/:虚拟环境包含平台相关的二进制文件,提交后在其他机器上无法使用,必须写入 .gitignore。