2. 实验参数的生命周期与分类

在科学计算实验中,“参数”并不是一个笼统的概念。一次实验能否被准确复现,往往取决于我们是否清楚地区分了不同性质的参数:哪些是实验开始前就必须固定的输入,哪些是研究者主动调节的超参数,哪些是运行环境留下的痕迹,哪些只是为了保证随机过程可重复的种子,以及哪些是实验结束后才产生的输出元数据。把这些参数混在一起管理,是复现失败最常见的根源之一。

本节围绕两个核心问题展开:第一,如何对实验参数进行合理分类;第二,这些参数在实验设计、执行、归档三个阶段中分别处于什么状态、发生什么变化。理解这条“生命周期”,才能设计出真正可用的参数管理方案。

五类参数:性质不同,管理方式也不同

从复现的角度看,实验参数可以划分为五类,它们的来源、可变性和归档要求各不相同。

输入参数是实验所处理的数据或外部给定条件的描述,例如数据集路径、样本规模、输入文件校验值、外部配置文件名。它们决定了“实验吃什么”,通常应在实验开始前固定,并在归档时记录其精确标识(如文件哈希),因为同一路径下的数据可能被替换。

超参数是研究者主动设定、用于控制算法行为的量,例如学习率、迭代次数、正则化系数、网格搜索范围、求解器容差。超参数是实验设计的核心自由度,也是调参过程中变化最频繁的部分。管理要点是:每一次实验必须记录当次实际生效的超参数组合,而不是记录“计划使用的范围”。

环境参数描述实验运行所依赖的软硬件条件,例如操作系统版本、编译器或解释器版本、依赖库版本、CPU/GPU 型号、线程数、并行策略。环境参数常常被忽视,但数值结果的微小差异、甚至无法运行,往往就来自这里。它们应在执行阶段自动采集,而非手工填写。

随机种子是控制随机过程可重复性的特殊参数。它既不是输入也不是超参数,而是一个“可重复性开关”。需要区分全局种子与各随机源(数据打乱、权重初始化、采样)的局部种子,并明确记录种子设置的位置与生效顺序。

输出元数据是实验执行后产生的描述性信息,例如运行时间戳、耗时、输出文件路径、结果指标、日志摘要、退出状态。它们不属于实验的“输入条件”,而是实验的“身份档案”,归档时应与输入侧参数一并保存,形成完整记录。

判断一个参数属于哪一类,可以问三个问题:它是在实验前由人给定、由程序自动采集,还是实验后产生?它变化时是否意味着换了一个实验?复现时是否必须原样还原?

参数在三个阶段中的状态变化

参数不是静态的,它在实验设计、执行、归档三个阶段中经历“确定—生效—固化”的状态迁移。下面的图示概括了五类参数在这条生命周期上的位置与状态变化。

设计阶段 执行阶段 归档阶段 输入参数 数据/文件标识 确定路径与校验值 状态:已固定 读取并校验哈希 状态:已加载/校验通过 写入清单,附哈希 状态:已固化 超参数 学习率/迭代数 设定候选与范围 状态:待定/候选 当次取值生效 状态:已生效(唯一组合) 记录实际生效值 状态:已固化 环境参数 版本/硬件/线程 声明依赖约束 状态:已声明 自动采集实际环境 状态:已采集(可能偏离声明) 与声明比对并记录差异 状态:已固化 随机种子 全局/局部种子 指定种子策略 状态:已指定 按序注入各随机源 状态:已注入/已生效 记录种子及注入位置 状态:已固化 输出元数据 耗时/指标/状态 预留字段与格式 状态:待生成 运行中写入日志与指标 状态:生成中 汇总为实验档案 状态:已归档

从图中可以看到一条清晰的规律:设计阶段解决“参数是什么、取什么值”,执行阶段解决“参数是否真的按预期生效”,归档阶段解决“参数是否被完整固化”。三个阶段中,执行阶段最容易出现“声明值与实际值不一致”的问题,例如声明使用 8 线程但实际被限制为 4 线程,声明依赖某版本库但环境中被升级。因此,环境参数与超参数都应以“实际生效值”为准进行记录。

一个可操作的参数记录示例

下面给出一个实验参数清单的示例结构(数值为教学示例,非实测结果)。它把五类参数放在同一份记录中,但用不同字段区分其性质,便于执行时自动填充、归档时整体保存。

experiment_id: exp-2024-0317-lr-sweep
# 输入参数(设计阶段固定,执行时校验)
input:
  dataset: data/train_v3.parquet
  dataset_sha256: "9f2c...a71d"
  sample_count: 50000
# 超参数(设计阶段候选,执行时取唯一值)
hyper:
  learning_rate: 0.003
  batch_size: 256
  max_epochs: 40
  optimizer: adam
# 环境参数(执行阶段自动采集)
env:
  python: "3.11.6"
  numpy: "1.26.2"
  device: "cpu"
  threads: 4
# 随机种子(执行阶段按序注入)
seed:
  global: 20240317
  data_shuffle: 20240317
  weight_init: 20240318
# 输出元数据(执行后生成,归档时汇总)
output:
  started_at: "2024-03-17T09:12:44"
  duration_sec: 1832
  final_loss: 0.0417
  exit_status: 0
  artifact: runs/exp-2024-0317-lr-sweep/model.bin

这份记录的关键设计在于:输入参数带哈希,超参数只记录当次生效值,环境参数由程序采集,种子区分全局与局部,输出元数据在执行后回填。归档时,整份记录与输出产物一起保存,任何一次实验都能凭此还原“当时到底跑了什么”。

生命周期管理中的三个易错点

第一,把“计划值”当成“实际值”。设计阶段写下的超参数范围、声明的依赖版本,都不等于执行时真正生效的内容。正确做法是在执行入口处打印或落盘实际生效的配置,并与计划值比对,差异要显式记录。

第二,种子设置不完整。只设全局种子,却忽略了数据加载器的打乱种子或并行采样种子,会导致结果仍不可复现。应逐一列出实验涉及的随机源,明确每个随机源的种子来源与注入顺序。

第三,归档只存结果不存参数。只保存模型文件或指标表,而不保存输入哈希、环境版本与种子,等于放弃了复现能力。归档的最小完整单元应是“参数记录 + 输出产物 + 日志”,三者缺一不可。

参数管理不是实验结束后补写的文档工作,而应嵌入实验脚本本身:设计阶段生成参数模板,执行阶段自动采集与校验,归档阶段整体打包。手工补录的参数记录,其可信度远低于程序自动生成的记录。