深度学习中的随机数种子


本文参考网络上对于seed函数的讲解,记录一下我对于这个函数作用的理解。

seed()函数的作用

解释

官方解释:

1 seed() 用于指定随机数生成时所用算法开始的整数值,如果使用相同的seed()值,则每次生成的随机数都相同。
2 设置随机数种子不设置的话按系统根据时间作为参数 设置后可以保证我们每一个产生的随机数是一样的

什么意思?这里先来了解一下随机数的背景,随机数就是随机产生的数,既然叫做随机数,那么应当具有随机性,如果随机种子能固定随机数,那么“随机数”的说法是否是个悖论。这里就要引入一个“伪随机数”的概念。在机器控制的算法或过程中,所产生的都是伪随机数,是通过一定规律产生的,也就是过程可控的。这种规律叫做随机数生成器,但是规律很复杂,就像一个黑盒,我们可以近似地将其视为随机数的模拟。随机种子就可以认为是在机器模拟过程中固定随机数产生过程的船锚。

随机数生成器:早期使用的线性同余产生器、经典斐波那契产生器和反馈移位寄存器产生器;上世纪八九十年代发展的非线性同余产生器,多步线性递推产生器,以及进位借位运算产生器;目前常用的组合产生器(前述都属于单一随机数产生器)等等。具体会涉及到密码学以及信息安全的知识。

我们可以将产生随机数的过程视为一个固定的黑盒,把设定好的种子扔进去,黑盒将会返回所想要的随机数,以及保证生成下一个随机数的新的种子,再将这颗种子继续丢进黑盒,循环迭代,从而进入随机数生成的无限循环。因此,只要初始随机种子不改变,我们将会得到固定的随机数序列。
这里的随机数种子其实没什么实际的含义,相当于某个伪随机数序列的id,只要保证种子值相同,那么就会相同的随机种子下生成相同的随机数序列,如果seed()不设置任何值,则系统根据时间来自己选择这个值,此时每次生成的随机数因时间差异而不同。
根据上面的理解,我们来实验一下,这里以numpy的随机数种子为例:

例子1

import numpy as np
np.random.seed(5)
print(np.random.random())
print(np.random.random())
print(np.random.random())
print(np.random.random())

产生的结果是:

0.22199317108973948
0.8707323061773764
0.20671915533942642
0.9186109079379216

只要我设置了随机数种子为5,那么我下次再运行这个脚本,产生的结果会和上面一模一样
看官不妨自己试一试。
此外,如果在代码中,多次设置随机数种子,那么后续再产生随时数序列时,就会从最近设置的随机数种子开始重新输出,比如:

例子2

np.random.seed(5)
print(np.random.random())
print(np.random.random())
np.random.seed(5)
print(np.random.random())
print(np.random.random())

产生的结果是:

0.22199317108973948
0.8707323061773764
0.22199317108973948
0.8707323061773764

深度学习中的随机数种子

随机种子和神经网络训练没有直接关系,随机种子的作用就是产生权重为初始条件的随机数。神经网络效果的好坏直接取决于学习率和迭代次数
此外,最优随机种子不应去找,随机性的存在正好用来评估模型的鲁棒性。一个优秀的模型,不会因为随机初始的位置略微不同,而找不到最优的位置。这是模型本身应该要化解的工作,而不是人为选择一个随机数,之所以要设置随机数种子,一方面是为了在得到比较好的结果时可以复现这个结果,另一方面,每次随机的初始权重一样,有利于实验的比较和改进。

训练过程

在训练过程中,若相同的数据数据集,相同的训练集、测试集划分方式,相同的权重初始化,但是每次训练结果不同,可能有以下几个原因:

  • Dropout的存在 ;
  • PyTorch、Python、Numpy中的随机种子没有固定;
  • 数据预处理、增强方式采用了概率,若没有设置固定的随机种子,结果可能不同。例如常用数据增强库albumentations就采用了Python的随机产生器;
  • 训练数据集被随机打乱了顺序; 向上采样和插值函数/类的向后是不确定的(PyTorch的问题)

在PyTorch官方文档中说明了在Pytorch的不同提交、不同版本和不同平台上,不能保证完全可重现的结果。此外,即使使用相同的种子,因为存在不同的CPU和GPU,结果也不能重现。
但是对于一个特定的平台和PyTorch发行版上对于特定问题可以进行确定性的计算,其中包括:

  • 设置cuDNN:cudnn中对卷积操作进行了优化,牺牲了精度来换取计算效率。如果需要保证可重复性,可以使用如下设置:
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True

不过实际上这个设置对精度影响不大,仅仅是小数点后几位的差别。所以如果不是对精度要求极高,不太建议修改,因为会使计算效率降低。

  • 对PyTorch设置随机种子;
  • 对Python &NumPy设置随机种子:如果读取数据的过程采用了随机预处理(如RandomCrop、RandomHorizontalFlip等),那么对python、numpy的随机数生成器也需要设置种子;
  • 对dataloader设置随机种子:如果dataloader采用了多线程(num_workers > 1),那么由于读取数据的顺序不同,最终运行结果也会有差异。也就是说,改变num_workers参数,也会对实验结果产生影响。目前暂时没有发现解决这个问题的方法,但是只要固定num_workers数目(线程数)不变,基本上也能够重复实验结果。对于不同线程的随机数种子设置,主要通过DataLoader的worker_init_fn参数来实现。默认情况下使用线程ID作为随机数种子。

1、在运行任何程序之前写入下面代码(可以放在主代码的开头,在导入模块之后):

torch.manual_seed(seed) # 为CPU设置随机种子
torch.cuda.manual_seed(seed) # 为当前GPU设置随机种子
torch.cuda.manual_seed_all(seed)  # if you are using multi-GPU,为所有GPU设置随机种子
np.random.seed(seed)  # Numpy module.
random.seed(seed)  # Python random module.	
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True

将它封装一个函数:

def seed_torch(seed=1029):
	random.seed(seed)
	os.environ['PYTHONHASHSEED'] = str(seed) # 为了禁止hash随机化,使得实验可复现
	np.random.seed(seed)
	torch.manual_seed(seed)
	torch.cuda.manual_seed(seed)
	torch.cuda.manual_seed_all(seed) # if you are using multi-GPU.
	torch.backends.cudnn.benchmark = False
	torch.backends.cudnn.deterministic = True

seed_torch()

2、PyTorch的DataLoader函数中填入为不同的work设置初始化函数,确保您的dataloader在每次调用时都以相同的顺序加载样本(随机种子固定时)。

def _init_fn(worker_id):
    np.random.seed(int(seed)+worker_id)
trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True, worker_init_fn=_init_fn)

3、当上采样和插值函数/类的向后是不确定的(BACKWARD of upsampling and interpolation functionals/classes is non-deterministic)。这意味着,如果你在训练图中使用这样的模块,无论你做什么,都永远不会得到确定性的结果。torch.nn.ConvTranspose2d函数是不确定的,除非你使用torch.backends.cudnn.deterministic = True

测试过程

在测试过程中,相同的权重,相同的测试数据集,结果不同,可能有以下几个原因:

  • 未设定eval()模式,因为模型中的Dropout和Batchnorm存在,导致结果不固定;
  • PyTorch、Python、Numpy中的随机种子没有固定,可能运行时依赖的一些第三方库; 有随机性 数据预处理方式中含有概率;
  • 向上采样和插值函数/类的向后是不确定的(Pytorch的问题)

代码随机种子的设定

有的时候,不同的随机种子对应的神经网络结果不同,我们并不想固定随机种子,使其能够搜索最优结果。但是又想能够根据复现最优结果,所以我们需要每次运行代码都根据当前时间设定不同的随机种子,并将随机种子保存下来。

可以使用下面代码产生随机种子,用于固定Pytorch、Python、Numpy中的随机种子,你可以将这个值保存到特定的文件中,用于之后使用。

seed = int(time.time() * 256)

参考文献:
PyTorch固定随机数种子
【调参侠的修炼笔记2】随机种子Seed的讲人话解释

  • 16
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值