Pytorch固定随机种子&&复现模型

官网 Reproducibility — PyTorch 1.11.0 documentation

在神经网络中,参数默认是进行随机初始化的。不同的初始化参数往往会导致模型的训练结果会存在一定的差异。当得到比较好的结果时我们通常希望这个结果是可以复现的,就需要保证每一次初始化的参数都不变,这就引入了随机种子。在PyTorch中,通过设置全局随机数种子可以实现这个目的。本文总结了PyTorch中固定随机种子的方法。

一  训练过程的不确定性

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

  1. PyTorch、Python、Numpy中的随机种子没有固定;
  2. cuDNN中大量nondeterministic的算法
  3. dataloader多个num_workers带来的随机性
  4. 向上采样和插值函数/类的向后是不确定的(PyTorch的问题)
  5. Dropout的存在(模型设计本身的问题)

例如,数据预处理、增强方式采用了概率,若没有设置固定的随机种子,结果可能不同。例如常用数据增强库albumentations就采用了Python的随机产生器;训练数据集被随机打乱了顺序,也可能用到 PyTorch、Python、Numpy 中的 shuffle,如果随机种子不固定,那么每次打乱顺序也不一样,导致最终结果不能复现。

当 向上采样和插值函数/类的向后是不确定的(BACKWARD of upsampling and interpolation functionals/classes is non-deterministic)。这意味着,如果你在训练图中使用这样的模块,无论你做什么,都永远不会得到确定性的结果。torch.nn.ConvTranspose2d函数是不确定的,除非你使用torch.backends.cudnn.deterministic = True (原文中说you can try to make the operation deterministic … by setting torch.backends.cudnn.deterministic = True,所以这样做是否能够得到正确结果也是待定的)。

另外,在Pytorch官方文档中说明了在Pytorch的不同提交、不同版本和不同平台上,不能保证完全可重现的结果。此外,即使使用相同的种子,因为存在不同的CPU和GPU,结果也不能重现。

但是对于一个特定的平台和PyTorch发行版上对您的特定问题进行确定性的计算,需要采取几个步骤。

can’t reproduce results even set all random seeds说明了两种解决方式:

can’t reproduce results even set all random seeds#7068 (comment1)建议采用下面方式解决:

在运行任何程序之前写入下面代码(可以放在主代码的开头)

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
    #torch.use_deterministic_algorithms(True)  # 有检查操作,看下文区别

seed_torch()

二  固定随机种子

1、Python & NumPy

seed为0的时候表示不用这个feature,也可以设置为整数,多以最好不要把seed设置为0

如果读取数据的过程采用了随机预处理(如RandomCrop、RandomHorizontalFlip等),那么对python、numpy的随机数生成器也需要设置种子。

import os
import random
import numpy as np
seed = 1024
random.seed(seed)     # python的随机性
np.random.seed(seed)  # np的随机性
os.environ['PYTHONHASHSEED'] = str(seed) # 设置python哈希种子,为了禁止hash随机化

environ是一个字符串所对应环境的映像对象,这里主要是为了设置python哈希种子,禁止hash随机化,使得实验可复现。解释:python里面有很多使用哈希算法完成的操作,例如对于一个数字的列表,使用set()来去重。 大家应该经历过,得到的结果中,顺序可能不一样,例如(1,2,3)(3,2,1)。

有时候在脚本代码中才固定 PYTHONHASHSEED 会太晚,因此需要在终端就把这个固定执行

PYTHONHASHSEED=123 python3 train.py 

2、PyTorch

seed = 1024
torch.manual_seed(seed)            # torch的CPU随机性,为CPU设置随机种子
torch.cuda.manual_seed(seed)       # torch的GPU随机性,为当前GPU设置随机种子
torch.cuda.manual_seed_all(seed)   # torch的GPU随机性,为所有GPU设置随机种子

3、dataloader

  • fastiai中用augmentation时,由于多线程的data loading,会带来随机性。

​When you use threads for data loading, the augmentation for each image is done inside different threads. So, even if you have set a random seed before, threads (since they share resources) will update the state of the random and share this state as they perform augmentations.

  • 对pytorch而言,DataLoader will reseed workers following Randomness in multi-process data loading algorithm(torch.utils.data — PyTorch 1.12 documentation). Use worker_init_fn() and generator to preserve reproducibility
  • 有人pytorch不能复现,加gradient clipping后可以复现了

如果dataloader采用了多线程(num_workers > 1), 那么由于读取数据的顺序不同,最终运行结果也会有差异。也就是说,改变num_workers参数,也会对实验结果产生影响。目前暂时没有发现解决这个问题的方法,但是只要固定num_workers数目(线程数)不变,基本上也能够重复实验结果。

在PyTorch的DataLoader函数中为不同的work设置初始化函数,确保您的dataloader在每次调用时都以相同的顺序加载样本(随机种子固定时)。如果进行裁剪或其他预处理步骤,请确保它们是确定性的。

对于不同线程的随机数种子设置,主要通过DataLoader的worker_init_fn参数来实现。默认情况下使用线程ID作为随机数种子。

# 设置每个读取线程的随机种子
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)

4、CUDNN

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

GPU算法的不确定来源有两个

  • CUDA convolution benchmarking(基准测试)

  • nondeterministic algorithms

1、CUDA convolution benchmarking => torch.backends.cudnn.benchmark

CUDA convolution benchmarking 是为了提升运行效率,对模型参数试运行后,选取最优实现。

CUDA卷积操作使用的cuDNN库可能是跨应用程序多次执行的不确定性的来源。当使用一组新的尺寸参数调用cuDNN卷积时,一个可选的特性可以运行多个卷积算法,对它们进行基准测试以找到最快的一个。然后,在剩下的过程中,对于相应的尺寸参数集,将一致地使用最快的算法

适用场景是网络结构固定(不是动态变化的),网络的输入形状(包括 batch size,图片大小,输入的通道)是不变的。反之,如果卷积层的设置一直变化,会导致 cnDNN 每次都会去寻找一遍最优配置,这样反而会降低运行效率。

由于benchmarking本身存在噪音和不同的硬件,即使是在同一台机器上benchmarking可能会在后续的运行中选择不同的算法,导致不确定性   

torch.backends.cudnn.benchmark=True :cuDNN使用非确定性算法寻找最高效算法。将会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速、增加运行效率。

torch.backends.cudnn.benchmark = False :禁用基准功能会导致 cuDNN 确定性地选择算法,可能以降低性能为代价。 #保证gpu每次都选择相同的算法,但是不保证该算法是deterministic的。

2、nondeterministic algorithms => torch.backends.cudnn.deterministic

GPU最大优势就是并行计算,如果能够忽略顺序,就避免了同步要求,能够大大提升运行效率,所以很多算法都有非确定性结果的算法实现。虽然禁用CUDA卷积基准(benchmarking ),确保每次运行时CUDA选择相同的算法应用程序,但算法本身可能是不确定的。通过设置deterministic就可以使得pytorch选择确定性算法。

torch.backends.cudnn.deterministic = True :每次返回的卷积算法将是确定的,即默认算法。如果配合上设置 Torch 的随机种子为固定值的话,应该可以保证每次运行网络的时候相同输入的输出是固定的。     

torch.use_deterministic_algorithms(true) 和 torch.backends.cudnn.deterministic = True 区别

torch.backends.cudnn.deterministic = True , 只设置控制选择确定性这种行为,而

torch.use_deterministic_algorithms(true) 允许配置PyTorch,将使其他PyTorch操作的行为具有确定性,在可用的情况下使用确定性算法,而不是非确定性算法,如果操作已知为非确定性算法(且没有确定性替代方案),则会抛出错误

torch.backends.cudnn.benchmark = False   # if benchmark=True, deterministic will be False
torch.backends.cudnn.deterministic = True # 选择确定性算法

# 或者

torch.backends.cudnn.benchmark=False  # 不需要benchmarking, False会确定性地选择算法,会降低性能
torch.use_deterministic_algorithms(True)  # 选择确定性算法

警告:不过实际上这个设置对精度影响不大,影响精度在小数点后几位。所以如果不是对精度要求极高,其实不太建议修改,因为会使计算效率降低。确定性模式可能会对性能产生影响,具体取决于您的型号。

三 测试过程

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

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

参考文章:1、https://blog.csdn.net/byron123456sfsfsfa/article/details/96003317

                   2、pytorch如何确保 可重复性/每次训练结果相同(固定了随机种子,为什么还不行)? - 知乎

  • 17
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
dataloader是PyTorch中用于加载数据的工具,它可以方便地对数据进行预处理、转换和加载。在使用dataloader加载数据时,我们可以通过设置随机种子来控制数据的洗牌(shuffle)过程。 为什么需要设置随机种子呢?在训练深度学习模型时,通常需要将数据集随机打乱来增加模型的泛化性能,减少模型对数据的依赖性。随机洗牌可以打破输入数据的顺序性,使模型更好地学习数据的特征和规律。但是,在同样的训练过程中,我们希望每次运行时的随机结果都是一致的,这样才能确保模型的可复现性,方便我们进行调试和比较实验结果。 因此,我们可以通过设置随机种子来控制dataloader加载数据时的洗牌过程。在使用dataloader加载数据时,可以通过设置参数shuffle=True来开启洗牌功能。同时,我们可以通过设置参数torch.manual_seed(seed)来设置随机种子的值。这样,每次运行时,dataloader在洗牌数据时都会使用相同的随机种子,从而保证了每次的洗牌结果都是一致的。 例如,我们可以使用以下代码来设置随机种子为10,并开启洗牌功能: torch.manual_seed(10) dataloader = DataLoader(dataset, shuffle=True) 这样,每次运行时,dataloader都会使用相同的随机种子10进行数据洗牌,从而保证了每次的洗牌结果都是一致的。这对于实验结果的比较和模型的可复现性非常重要。 总之,通过设置随机种子,我们可以在dataloader加载数据时控制洗牌过程,确保每次洗牌结果的一致性,从而提高模型的可复现性和实验的可比性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值