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,这些又决定了训练过程的走向。种子一变,路径就变。结果自然跟着变。
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章就到这里。核心概念就是一句话:种子变了,结果就变了。你理解了这一点,后续的章节就能跟上。