qinghuan
← 回到知识探索

AI 直觉 / 动手实验

随机种子都设成 17,结果为什么还会变?

给随机过程相同起点,还得保持取数方式和运行条件;用四行输出看清种子究竟控制了什么。

展开本文目录
  1. 相同起点,还要按相同顺序往前走
  2. 只多取一次,看看后两项怎样移动
  3. 训练结果还经过了别的环节
  4. 先说明你想重现到什么程度
  5. 记住这三点
  6. 别把例子用过头
  7. 自己试一小步
  8. 查原始资料
  9. 找一份资料继续练
开始前,会这些就够了
  • 能阅读 Python 函数和列表
  • 知道程序可以按顺序生成随机数
遇到不熟悉的词,先看这 1 个解释
随机种子
随机种子用来初始化指定随机数生成器的状态。相同起点和相同取数方式,可以让这条随机过程按相同顺序重放。
打开完整术语小抄 ↗

你照着教程把 seed 写成 17,重新运行,结果一致。后来加了一段随机挑选展示内容的代码,实验结果却变了。种子没有消失,改变的是程序从同一串数字里取走了哪些位置。

训练程序也会用随机数,例如决定初始参数或打乱样本顺序。先用一个不训练模型的小例子,分清“重放一串随机数”与“重放整个实验”之间还隔着哪些条件。

相同起点,还要按相同顺序往前走

普通伪随机生成器根据内部状态算出下一个数,同时推进自己的状态。种子(seed)用来初始化这份状态。可以把它想成一台按固定规则吐数字的机器:同样起点、同样取法,才会走过同样的过程。

同一个生成器被两段代码共用时,任意一段多取一次,另一段接下来拿到的位置就可能改变。固定种子没有冻结调用次数,也没有给每个用途自动分配独立的一串数字。

只多取一次,看看后两项怎样移动

代码每次都为任务新建 Random(17),先取一个数,再取两个。第三行在中间额外取走一个数;第四行让额外用途改用自己的 Random(91)。输出保留六位小数,原始数值没有先取整。

比较结果时,要看第三行后两项对应原序列的什么位置。第四行恢复的是任务这条随机流不受额外调用影响;这不证明两条流在统计上独立,更没有测试模型训练或 GPU 的可复现性。

小实验 · python
import random


def draw(extra_call=False, separate_stream=False):
    task_rng = random.Random(17)
    other_rng = random.Random(91) if separate_stream else task_rng
    values = [task_rng.random()]
    if extra_call:
        other_rng.random()
    values.extend(task_rng.random() for _ in range(2))
    return values


def show(values):
    return ', '.join(f'{value:.6f}' for value in values)


if __name__ == '__main__':
    print('original:', show(draw()))
    print('same setup:', show(draw()))
    print('shared extra call:', show(draw(extra_call=True)))
    print('separate extra call:', show(draw(True, True)))

怎样运行:保存为 seed_streams.py,用 Python 3.10 或更新版本运行:python seed_streams.py。仅用标准库,不联网,不训练模型。

运行后对照结果
original: 0.521984, 0.806691, 0.960495
same setup: 0.521984, 0.806691, 0.960495
shared extra call: 0.521984, 0.960495, 0.289625
separate extra call: 0.521984, 0.806691, 0.960495

训练结果还经过了别的环节

真实项目里,Python、训练框架和数据加载进程可能各用各的随机来源。只设置 Python 的一个种子,不会自动设置所有库的生成器。即使随机数相同,换了输入内容或处理顺序,后面的计算也可能不同。

PyTorch 官方还区分了随机来源与运算实现:某些运算需要另选确定性的实现。文档明确不保证跨版本、跨平台,或 CPU 与 GPU 之间的结果完全一致。“不保证”也不等于一定不同;要在准备支持的环境中实际核对。

先说明你想重现到什么程度

调试时,你可能希望同一环境重跑后每个数都相同;比较算法时,你也可能关心换几组随机条件后,结论是否仍站得住。这是不同的检查。把同一次固定结果重放十遍,不能当作十次新的随机实验来估计波动。

留下一份能照做的记录:代码版本、数据版本与顺序、各处种子、库版本、设备,以及判定一致的方法。Python 对兼容播种方式下的 random() 序列有再现性约定,但不能据此推断所有随机接口、所有版本和整个训练程序都有同样保证。

记住这三点

  • 种子控制指定生成器的初始状态;调用顺序改变,任务取到的数字也可能改变。
  • 隔离随机流可以减少不同用途互相干扰,完整实验仍需记录数据、代码与环境。
  • 先定义一致的标准,再实际复跑;逐项相同与多次实验结论稳定是两种检查。

别把例子用过头

  • 本例只执行 Python 标准库随机数调用,没有训练模型或验证 GPU、跨版本、跨平台结果。
  • 重新从种子开始与恢复中途状态不是同一操作;断点续训还要恢复模型、优化器和数据进度等状态。
  • 可重放的 random 不适合生成登录令牌等保密值;这类用途应使用 secrets 等安全随机接口。

自己试一小步

如果展示模块一次要抽取五个数字,怎样确认它没有改变任务拿到的三项?

  1. 把额外用途改成连续取五次,先记录你对共用生成器结果的预测。
  2. 分别运行共用和分开生成器的版本,比较完整返回列表,而不只比较格式化后的文字。
  3. 写下本次使用的 Python 版本、种子、调用次数,以及你实际验证的一致范围。

做到这些,就算完成

  • 能否指出额外调用推进的是哪一个生成器?
  • 是否区分了本机示例重放与真实模型跨设备复现?
  • 记录是否足以让另一个人按同样步骤复查?

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • Python:random 的状态与再现性约定 (新标签页)↗Python · 英文

    Random 实例各自持有状态,seed 初始化状态,getstate/setstate 保存与恢复状态,以及 random() 的兼容播种再现性约定。

    2026-09-26 已读模块开头、Bookkeeping functions 与 Notes on Reproducibility。文档显示 Python 3.14.7;本文只用标准库 Random.random。

  • PyTorch 2.14:Reproducibility (新标签页)↗PyTorch · 英文

    跨版本/平台/CPU 与 GPU 不保证完全重现;不同随机来源、确定性运算与数据加载进程需要分别考虑。

    2026-09-26 已读开头限制、Controlling sources of randomness、Avoiding nondeterministic algorithms 和 DataLoader 小节。未执行文档中的 PyTorch 或 CUDA 示例。

  • Python:secrets 的适用范围 (新标签页)↗Python · 英文

    安全凭据应采用密码学安全随机来源,模拟用途的默认 random 不适合该目的。

    2026-09-26 已读模块用途、Random numbers 与 Generating tokens 开头;本文没有生成或保存真实凭据。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。