什么是随机种子
在我们阅读别人代码的时候,经常会在代码中看到这样一段。
seed=42
ramdom.seed(seed)
np.random.seed(seed)
torch.manual.seed(seed)
torch.cuda.manual.seed(seed)
很多人会好奇这几行代码的作用。实际上,在深度学习任务中,很多情况下需要进行随机初始化参数、随机打乱数据集、随机选择样本等操作,这些操作的随机性可能会导致不同的运行结果,而这几行代码的作用是为了确保在进行上述随机性操作时,每次运行得到的结果都是可重复的,我们称之为随机种子。也就是说,设置了相同的随机种子后,每次运行程序得到的随即结果都是一样的,这样就保证了实验的可重复性,方便调试与验证模型。
随机种子的简单示例
下面我们将以numpy与pytorch的随机种子为例,来看一下随机种子的作用。
numpy
numpy中设置随机种子使用的是np.random.seed(seed),其中seed是一个取值范围在-2^32到2^32-1之间的任意整数。下面是一个随机种子的简单示例。
# 设置随机种子为7
np.random.seed(7)
# 生成一个长度为3的随机数组
print(np.random.rand(3))
# 生成一个长度为3的随机数组
print(np.random.rand(3))
# 重新设置随机种子为7,以确保再次生成相同的随机序列
np.random.seed(7)
# 生成一个长度为5的随机数组
print(np.random.rand(5))
# 生成一个长度为1的随机数组
print(np.random.rand(1))
# 重新设置随机种子为1
np.random.seed(1)
# 生成一个长度为3的随机数组,此时的种子不同,所以得到的结果也会不同
print(np.random.rand(3))
# 生成一个长度为3的随机数组,此时的种子不同,所以得到的结果也会不同
print(np.random.rand(3))
>>>[0.07630829 0.77991879 0.43840923]
>>>[0.72346518 0.97798951 0.53849587]
>>>[0.07630829 0.77991879 0.43840923 0.72346518 0.97798951]
>>>[0.53849587]
>>>[4.17022005e-01 7.20324493e-01 1.14374817e-04]
>>>[0.30233257 0.14675589 0.09233859]
在示例中,我们首先定义里随机种子为7,然后输出了两遍长度为3的随机数组,此时这两个随机数组的值是不一样的,这里很多人会好奇,不是说每次运行的结果都是一样的吗,原因我们在后面详细描述。第一次一共输出了6个随机值。而后我们重新定义了随机种子,此时再分别输出了长度为5与长度为1的随机数组,这里我们发现,虽然每次输出的随机数组长度与第一次不同,但整体输出的结果与第一次输出的结果相同,并且输出顺序保持一致。最后我们定义了随机种子为1,此时我们继续输出两个长度为3的随机数组,但对比前面的结果发现,输出结果已经发生了改变,因为随机种子已经发生了变化。
pytorch
在我们日常使用pytorch进行深度学习任务时,通常使用torch.manual_seed(seed)与torch.cuda.manual_seed(seed)来分别设置CPU与GPU中的随机种子。
import torch
# 创建一个简单的神经网络模型
model = torch.nn.Sequential(
torch.nn.Linear(10, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 2)
)
# 设置随机种子
seed = 42
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
# 定义输入数据
input_data1 = torch.randn(1, 10)
input_data2 = torch.randn(1, 10)
# 第一次输出结果
output1 = model(input_data1)
output2 = model(input_data2)
# 重新定义随机种子
seed = 43
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
# 定义输入数据
input_data3 = torch.randn(1, 10)
# 第二次运行模型
output3 = model(input_data3)
# 恢复第一次随机种子
seed = 42
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
# 定义输入数据
input_data4 = torch.randn(1, 10)
input_data5 = torch.randn(1, 10)
# 第三次运行模型
output4 = model(input_data4)
output5 = model(input_data5)
# 输出两次运行的结果进行对比
print("Output 1:")
print(output1)
print("Output 2:")
print(output2)
print("Output 3:")
print(output3)
print("Output 4:")
print(output4)
print("Output 5:")
print(output5)
Output 1:
tensor([[-0.0672, 0.1808]], grad_fn=<AddmmBackward0>)
Output 2:
tensor([[-0.0665, 0.5292]], grad_fn=<AddmmBackward0>)
Output 3:
tensor([[0.1638, 0.2211]], grad_fn=<AddmmBackward0>)
Output 4:
tensor([[-0.0672, 0.1808]], grad_fn=<AddmmBackward0>)
Output 5:
tensor([[-0.0665, 0.5292]], grad_fn=<AddmmBackward0>)
从结果可以看出,当随机种子一致时,模型输出的结果是一致的,当随机种子发生改变,模型结果也相应的发生改变。在深度学习任务中,如果加入随机种子,就能最大限度地保障了模型的可复现性,方便调试与验证模型。
其它固定操作
在深度学习任务时,通常我们还通过设置cudnn算法的确定性与benchmark模式,确保在使用GPU进行计算时,每次计算结果都是一致的,并且提高计算效率。在PyTorch中,cudnn是NVIDIA提供的一个用于深度学习加速的库,它实现了一系列高效的深度学习算法。在PyTorch中,还可以通过设置torch.backends.cudnn.deterministic和 torch.backends.cudnn.benchmark两个参数来控制cudnn库的行为。
确定性(Deterministic)模式: 当 torch.backends.cudnn.deterministic 设置为True时,PyTorch会强制让cudnn使用确定性算法,这意味着对于相同的输入,每次计算的输出都是确定的,即结果可重复。这对于调试和验证模型是非常有用的,因为可以确保每次运行得到相同的结果。
Benchmark模式: 当 torch.backends.cudnn.benchmark 设置为True时,PyTorch会尝试寻找最适合当前配置的cudnn卷积算法,以提高计算效率。它会在第一次运行时进行一些预处理,并记录每个卷积算法的运行时间。随后的运行将根据先前的运行结果选择最快的算法。这可以提高训练速度,特别是在大型模型和大批量数据上,但如果卷积层是一直变化的,将会导致程序不停的做优化,反而会耗费更多的时间。
随机种子的原理
前面我们发现当定义了随机种子后,如果不重新定义随机种子,那么当前随机种子下每次进行随机数获取的值是不一样的。这是为什么呢?这里就涉及到了随机数的原理。
实际上,我们生成的随机数都是伪随机数。随机不是靠人为或机能创造的,人们不可能靠算法真正做到随机。实际上的随机数,是依靠一种确定性的算法来计算出的一种在0-1上均匀分布的随机数序列算法,它并不是真正的随机。随机种子就是用来控制算法生成的伪随机数序列的。当设置相同的随机种子时,算法生成的随机数序列是相同的。 因此在相同的种子下,随机数生成的结果是可重复的。这种机制使得我们能够在需要随机性的场景中获得可控的、可重复的结果,从而方便调试、验证和比较实验结果。
当我们设置随机种子后,算法会生成一段随机数序列,在我们重新设置任意随机种子之前,每次取值,都会在随机数序列中顺序取值,比如先取三个随机数,再去三个随机数,那么实际上就是在随机数序列中顺序获取6个随机数。这就是为什么,上面我们设置随机种子后,连续两次取三个随机数,但结果不同的原因。当我们重新设置种子后,随机数序列会重新生成,如果随机种子一样,那么生成的序列会一样,此时我们再获取随机数,就会和之前的随机数保持一致了。
小结
在深度学习任务中,设置随机种子可以最大限度的保障我们结果的可复现性,方便我们调试与验证。感兴趣的可以进一步学习,并且对随机种子的作用进行测试。
往期精彩
本文由 mdnice 多平台发布