4. 算法工程项目的目录结构与依赖管理

算法工程项目的目录结构与依赖管理 学习卡片

为什么算法项目需要固定目录结构

算法项目与普通业务代码的差异在于:它同时包含可执行代码(训练、推理、评估)、不可执行但必须版本化的资产(配置、模型权重、数据快照)以及环境契约(依赖版本、Python 版本、CUDA 版本)。如果目录随意堆放,会出现三类典型故障:训练脚本找不到配置、推理服务加载了错误版本的模型、本地能跑而部署环境因依赖漂移而崩溃。因此需要一套约定俗成的目录骨架,把"代码—配置—数据—模型—环境"五类对象分开放置,并让每一类都有明确的读写边界。

algo-project/ src/ 训练/推理/评估代码 configs/ YAML 超参与路径 tests/ 单元/契约/回归测试 scripts/ 训练/导出/部署入口 docs/ 接口说明/实验记录 data/ 加载与校验 models/ 网络定义 train/ 训练循环 infer/ 推理封装 base.yaml 公共默认值 train.yaml 训练覆盖项 infer.yaml 推理覆盖项 test_data.py 字段/缺失率校验 test_model.py 前向形状/数值 test_contract.py 接口契约回归 train.sh 激活环境+启动 export.sh 导出模型产物 serve.sh 启动推理服务 api.md 接口契约文档 experiments.md 实验记录 环境与产物层(不进入源码版本控制,但必须锁定) .venv/ 虚拟环境(本地隔离) requirements.txt 直接依赖(含版本) requirements.lock 全量锁定(含传递依赖) artifacts/ 模型权重/指标/日志

目录骨架的落地方式

下面给出一个可直接使用的目录创建脚本。它同时完成三件事:建立目录树、写入占位文件、生成依赖锁定文件。脚本使用 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.lockpip 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。