1. 随机种子概述:什么是随机种子,为什么需要固定随机种子

做深度学习的人,大多遇到过这种情况:

明明代码没改,数据没动,跑出来的结果却不一样。你想想看,调了一周的参数,突然换台机器重跑,Loss 曲线对不上了……这种事遇上几次,心态就崩了。

嗯,根源就在随机种子。

1.1 什么是随机种子

先说概念。计算机里的随机,其实是伪随机。它不是真的随便变,而是通过算法算出来的。

每次“随机”,都从一个初始数字开始。这个初始数字,就是种子。

一句话版本:随机种子 = 随机数生成器的起点。种子固定,随机数序列就固定。种子变化,序列也跟着变。

拿 Python 举个例子。你运行下面这段代码两次,看看结果:

import random

random.seed(42)
print([random.random() for _ in range(3)])

random.seed(42)
print([random.random() for _ in range(3)])

你会发现两个列表一模一样。因为种子都是 42,随机数序列自然相同。

但如果你把第二个random.seed(42)改成random.seed(99),结果就全变了。说白了,种子就是决定“随机”走向的钥匙。

1.2 为什么机器学习离不开随机种子

训练模型时,“随机”无处不在。我调模型时发现,随便列一列,至少五个环节依赖随机种子。

环节为什么用随机种子影响什么
权重初始化打破对称性,让参数不重复初始参数值不同
数据打乱避免有序数据干扰训练每个 batch 的样本组成不同
Dropout随机丢弃神经元每次 Dropout 哪些节点不同
数据增强随机裁剪、翻转、噪声增强后的样本不同
Mini-batch 采样随机选子集每个 epoch 的 batch 分布不同

这些环节,看上去都该“随机”。但问题是——

如果所有环节的种子都不固定,实验结果就完全不可控。

我举个例子。你发现 A 方法比 B 方法好 1%。但换两次种子重跑,可能 A 反而比 B 差。你说,这个结论到底是对是错?

个人习惯:我跑对比实验时,至少固定三个种子各跑一次。结果一致,我才敢说“这个方案确实有效”。光跑一次就说结论,那是给自己埋坑。

1.3 不固定种子的后果

我曾经接过一个项目。同学给了一份 Baseline 代码,说效果挺好。我拿过来直接跑,结果差了一大截。

排查了三天——数据、环境、依赖版本……全部一样。最后发现,问题出在代码里没用固定种子。

我换了他的机器、他的 GPU、他跑的那个时间点……才复现出来。

这事挺耽误进度的。更麻烦的是,如果论文里不写种子,别人根本没法复现你的实验。

避坑指南:不要只在代码里加一行torch.manual_seed(0)就觉得够了。不同框架、不同设备,种子管理还可能不一致。

1.4 固定种子到底能控制什么

固定种子能控制的大致分两类。我把它们列出来:

  • 确定性行为(同一设备可复现):权重初始化、数据打乱顺序、Dropout 掩码、数据增强变换参数
  • 非确定性行为(无法完全控制):GPU 并行计算顺序、CUDNN 的卷积优化、多线程调度

所以别指望固定了种子就能全盘复现。不同 GPU 型号、不同 CUDNN 版本,结果仍可能有微小差异。

1.5 流程图:种子对训练的影响链路

下面这张图,展示了随机种子从设置到影响训练结果的完整链路。

随机种子影响链路图 固定随机种子 权重初始化 数据打乱顺序 Dropout / 增强 初始参数 训练过程(前向 + 反向) 样本变换 可复现的模型性能 结果不一致 / 不可复现 不同种子 → 种子控制影响初始参数 → 数据分布 → 训练路径 → 最终模型

从图上你可以看明白:种子从源头影响权重、数据、Dropout,这些又决定了训练过程的走向。种子一变,路径就变。结果自然跟着变。

1.6 怎么理解这个“随机”

有人会问:“如果模型对随机种子这么敏感,那它真的可靠吗?”

这个问题问得好。我个人观点是:可靠的模型,应该对不同种子有一定鲁棒性。但种子敏感不等于模型差——它也可能意味着你的训练方差大,需要更多次实验。

一个实用建议:每次实验,至少固定种子跑 3~5 次。看均值和标准差。如果不同种子下的结果波动 < 0.5%,说明模型稳定。如果波动 > 2%,说明你对种子太敏感了——要么是数据量不够,要么是训练需要稳定。

1.7 常见实践中的种子设置方法

我项目里一般用这种模式:

import random
import numpy as np
import torch

def set_seed(seed=42):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    
    # 提升确定性,但可能影响速度
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

# 然后每次实验前调一下
set_seed(2024)

这里我特别提一句:torch.backends.cudnn.benchmark = False 会让训练变慢一些,但能保证每次卷积计算的确定性。如果只是做对比实验,这个代价值得。

我的一个小习惯:每次跑实验,我会把种子值、框架版本、GPU 型号一起记进日志。这样后面复查,至少知道“这个结果是在什么条件下跑出来的”。

好了,第1章就到这里。核心概念就是一句话:种子变了,结果就变了。你理解了这一点,后续的章节就能跟上。