随机种子在Python中的实现

这一章,咱们来聊聊实际操作。

随机种子这个东西,说白了就是个「启动开关」。你把这个开关拧到同一个位置,后面跑出来的随机数序列就一模一样。我刚开始接触这玩意的时候,觉得它挺神秘的。后来踩过几次坑,才真正理解它的重要性。

Python生态里,搞随机性的库有好几个。每个库都有自己的种子管理系统。你得挨个设置,才能让整个实验变得可复现。

Python 标准库 random

这是最基础的随机数生成器。我平时写小脚本、做数据采样的时候经常用到它。

设置它的种子很简单:

import random

# 设置种子
random.seed(42)

# 生成随机数
print(random.random())    # 0.6394267984578837
print(random.randint(1, 100))  # 82

注意了,这个种子是全局的。你设一次,整个程序里所有 random 的调用都会受影响。

小技巧:如果只是想临时用一下固定随机数,可以用 random.getstate() 和 random.setstate() 保存和恢复状态。这样不会污染全局种子。

我曾经在写自动化测试脚本时犯过一个错:测试用例里调了 random.seed(),结果把后面所有依赖随机性的逻辑全打乱了。排查了好久才发现问题。

NumPy 的随机种子

做数据科学和深度学习,NumPy 是绕不开的。它的随机数生成器跟标准库是两套独立系统。

NumPy 早期版本和现在的 API 有点不同。我建议你用新的方式:

import numpy as np

# 创建一个独立的随机数生成器(推荐方式)
rng = np.random.default_rng(seed=42)

# 生成随机数组
print(rng.random(3))       # [0.77395605 0.43887844 0.85859792]
print(rng.normal(0, 1, 3)) # 正态分布采样

老版本的做法是全局设置:

# 全局设置(不推荐,但很多旧代码在用)
np.random.seed(42)
print(np.random.rand(3))
请注意:np.random.seed() 是全局的,而且它会影响所有使用 old-style API 的调用。在多线程或分布式场景下,这种方式非常容易出问题。

我个人习惯用新的 default_rng 方式。每个生成器实例独立管理状态,互不干扰。这在写大型实验框架的时候特别有用。

PyTorch 的种子设置

搞深度学习的朋友对这个应该很熟悉。PyTorch 的随机性来源很多:参数初始化、数据加载、dropout、数据增强……

要确保可复现,你得把能设的种子全设上:

import torch
import random
import numpy as np

# 设置所有种子
random.seed(42)
np.random.seed(42)
torch.manual_seed(42)

# 如果使用 CUDA
if torch.cuda.is_available():
    torch.cuda.manual_seed(42)
    torch.cuda.manual_seed_all(42)  # 多GPU

# 设置确定性算法(牺牲一些性能)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

设置 deterministic = True 这一步很重要。我遇到过这种情况:种子明明设了,每次跑结果还是不一样。最后发现是 CuDNN 的卷积算法选择有随机性。

核心要点:PyTorch 的随机种子只控制它自己的生成器。Python 的 random 和 NumPy 的随机数,你得单独设。很多人忘了这个,导致实验不可复现。

嗯,这里要注意 torch.cuda.manual_seed_all 这个函数。它是对所有 GPU 同时设种子。如果你单卡用 manual_seed 就行。

TensorFlow 的种子设置

TensorFlow 这边,种子设置入口不太一样。

import tensorflow as tf
import random
import numpy as np

# 设置基础种子
random.seed(42)
np.random.seed(42)
tf.random.set_seed(42)

# TF 2.x 的随机种子
tf.random.set_seed(42)

# 操作级别的种子(可选)
initializer = tf.keras.initializers.GlorotUniform(seed=42)

TensorFlow 有一个比较坑的地方:它的某些操作(比如 dropout 或 shuffle)默认不固定随机性。你得显式传 seed 参数。

我有个朋友,他训练一个模型,所有种子都设了,每次评估结果还是差几个点。排查了半天,发现是 tf.data.Dataset.shuffle() 没设 seed。

多库联合设置的最佳实践

做一个可复现的实验,你得像个强迫症患者一样把所有种子都管起来。

我一般会封装一个函数:

def set_all_seeds(seed=42):
    """统一设置所有随机种子"""
    import random
    import numpy as np
    import torch
    
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(seed)
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False
    
    # 如果用了 TensorFlow,再加上
    # import tensorflow as tf
    # tf.random.set_seed(seed)
    # tf.compat.v1.set_random_seed(seed)

把这个函数放在实验入口的第一行调用。这样就能保证从头到尾的随机性都受控制。

Python生态中随机种子设置全景图 随机种子配置全景 统一种子入口 random 模块 random.seed(42) NumPy np.random.seed(42) 深度学习框架 torch / tf 采样 / shuffle 数据增强 参数初始化 Dropout / 噪声 全库联合设置 → 可复现实验
避坑:我在项目中遇到过一件事。同事把所有种子都设了,但每次训练结果都不一样。最后发现是数据加载时用了多进程,每个worker有自己的随机状态。解决办法是在 DataLoader 里设置 worker_init_fn。

对比小结

不同库的种子设置方法,可以用这个表格快速对比:

库名种子函数作用域备注
randomrandom.seed(n)全局最基础
numpy (旧)np.random.seed(n)全局逐渐淘汰
numpy (新)np.random.default_rng(seed=n)实例独立推荐使用
PyTorchtorch.manual_seed(n)当前设备需配合cudnn设置
TensorFlowtf.random.set_seed(n)全局图级别操作级别也需设

每次写实验代码,我都会记得调用那个统一设置的函数。这已经成了我的肌肉记忆了。