4. 可复现性核心原则
在自动驾驶实验中,“可复现”不是一句口号,而是一组可以被工程化验证的约束条件。一个训练实验如果今天跑出 mAP 0.412、明天跑出 0.398,即使代码没改,也不能称为可复现。可复现性的本质,是把实验中所有会影响结果的“自由度”逐一收敛:让计算过程确定、让代码与数据版本可追溯、让运行环境可重建、让随机性可控制、让依赖可锁定,最后用等级划分来度量复现的强度。
一、核心原理:把“不确定性”逐层剥离
自动驾驶训练链路长、组件多,不确定性来源可分为五层。第一层是算法随机性:权重初始化、数据打乱、数据增强、Dropout、采样策略。第二层是计算非确定性:GPU 并行归约顺序、cuDNN 自动算法选择、原子加操作、多线程调度。第三层是环境漂移:CUDA、驱动、PyTorch、Python 版本变化,甚至同一版本在不同机器上的编译差异。第四层是数据漂移:数据集被追加、清洗、重新标注,但版本号未变。第五层是代码漂移:未提交的本地修改、分支切换、依赖隐式升级。
可复现性工程就是针对这五层分别建立控制手段:随机种子控制第一层,确定性开关控制第二层,环境隔离控制第三层,数据与代码版本控制控制第四、五层,依赖锁定则贯穿第三、五层。下图给出五层不确定性与五类控制手段的对应关系。
二、方法流程:从种子到锁定的落地顺序
落地时建议按“先固定随机性,再固定计算,再固定环境,最后固定版本”的顺序推进,因为前面的问题会掩盖后面的问题。典型流程如下:
- 统一入口设置种子:在训练脚本最开头设置 Python、NumPy、PyTorch、CUDA 的种子,并记录到实验元数据。
- 开启确定性计算:设置
torch.use_deterministic_algorithms(True),关闭cudnn.benchmark,必要时设置CUBLAS_WORKSPACE_CONFIG。 - 固定数据顺序与增强:DataLoader 的 shuffle 使用带种子的 generator,数据增强使用可复现的随机源。
- 环境隔离:用容器镜像或 conda 环境固化 Python、CUDA、驱动、框架版本。
- 依赖锁定:生成 lock 文件并记录哈希,禁止运行时隐式升级。
- 版本记录:记录 Git commit、数据集指纹、配置哈希,写入实验元数据。
# 训练入口的种子与确定性设置示例
import os, random, numpy as np, torch
SEED = 20240517
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.cuda.manual_seed_all(SEED)
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
torch.use_deterministic_algorithms(True)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
g = torch.Generator()
g.manual_seed(SEED)
# DataLoader(..., shuffle=True, generator=g)
三、案例/场景:一次“指标漂移”的复盘
某团队在 BEV 检测模型上遇到问题:同一份代码、同一份数据,两次训练 mAP 相差 0.9 个点。排查发现三个原因叠加:一是 DataLoader 的 shuffle 未使用固定 generator,导致每个 epoch 数据顺序不同;二是 cudnn.benchmark=True 让卷积算法随输入尺寸自动选择,不同 batch 组合下选择了不同算法;三是数据增强中的随机裁剪使用了全局随机源,受前序代码调用次数影响。修复后,两次训练指标完全一致,复现等级从 L1 提升到 L2。
四、可复现性等级划分与检查清单
为了团队协作和验收,建议把可复现性分为五级,每级有明确的判定标准。等级不是越高越好,而是要与实验目的匹配:日常调参 L2 足够,论文复现和量产交付至少 L4。
| 等级 | 名称 | 判定标准 | 适用场景 |
|---|---|---|---|
| L1 | 可重跑 | 同机同环境可再次运行,指标近似但不保证一致 | 快速探索、临时验证 |
| L2 | 可复现 | 固定种子与确定性开关后,同机指标完全一致 | 消融实验、调参对比 |
| L3 | 可重建 | 环境与依赖锁定,换机器可重建并得到一致结果 | 团队协作、CI 回归 |
| L4 | 可审计 | 代码、数据、配置、环境全链路版本可追溯 | 论文复现、量产交付 |
| L5 | 可迁移复现 | 跨硬件/跨集群复现,误差在预设容差内 | 多平台部署、异构训练 |
torch.manual_seed 就认为可复现。实际上 NumPy、Python random、CUDA、DataLoader worker、数据增强库都可能有独立随机源;此外,多卡训练中的梯度归约顺序、混合精度缩放、分布式采样器也会引入非确定性。必须逐项检查,而不是只设一个种子。
最后给出一个可落地的检查清单:种子是否覆盖所有随机源;确定性算法是否开启;cuDNN benchmark 是否关闭;DataLoader 是否使用固定 generator;环境是否容器化;依赖是否有 lock 文件;Git commit 与数据集指纹是否写入实验元数据;是否定义了可接受的误差容差。把这八项做成实验启动前的自动检查,可复现性才能从“靠人记得”变成“靠系统保证”。