随机抽样、数据打乱和模拟计算都需要随机数。调试这类程序时,一个常见问题是:相同的代码,为什么每次得到的结果都不一样?

大多数用于常规计算的随机数生成器是伪随机数生成器。它们根据内部状态产生数值,再更新状态。固定种子,就是为这个过程设置一个可以再次使用的起点。

使用局部生成器

以 Python 标准库为例,可以创建独立的生成器对象:

import random

rng = random.Random(42)
items = ["A", "B", "C", "D"]
rng.shuffle(items)
print(items)

这里的 rng 由当前流程持有。其他模块使用各自的生成器时,就不必共同消耗同一份全局状态。函数也可以显式接收 rng 参数,方便调用者控制随机过程。

种子 42 没有特殊含义,只是一个便于重复使用的示例值。普通伪随机生成器也不适合生成密码或安全令牌,这类用途应使用专门的安全随机接口。

相同种子不代表任意代码都相同

如果在打乱列表之前额外取一次随机数,生成器状态就发生了变化:

rng = random.Random(42)
rng.random()
items = ["A", "B", "C", "D"]
rng.shuffle(items)

第二段代码虽然用了同样的种子,但后续得到相同排列已没有保证。因此,在一个随机流程里临时加入抽样式调试输出,也可能改变后续结果。

同时,种子只决定随机过程的起点。输入列表的顺序变了、算法变了,结果仍会变化。并行任务的调度顺序还可能影响随机调用和浮点数归约的顺序。

按职责管理随机状态

程序中如果有互不相关的随机任务,可以为它们分别创建生成器,并记录各自的种子。例如,将数据打乱和模拟噪声分开管理,避免调整一个步骤时牵动另一个步骤。

如果需要大量并行随机流,应采用所用数值库正式支持的派生或分流机制。随意给种子加一个编号,并不能自动证明不同随机流在统计上互不影响。

记录复现所需的上下文

一份简洁的运行记录可以包含:输入数据的版本或摘要、程序版本、关键配置、生成器类型、种子,以及运行时和依赖版本。运行中途恢复时,还可能需要保存生成器状态,而不只是初始种子。

升级运行环境后,应重新验证关键样例。即便底层生成器保持兼容,上层抽样方法的实现也可能变化;长期要求完全一致时,可以直接保存生成的排列或抽样结果。

可复现性的检查也很直接:在相同环境下重新运行一个小案例,比较约定的输出。这样能验证整条记录链路,而不只是确认配置文件中存在一个名为 seed 的字段。