Random Seed
🖊️

Random Seed

Category
深度学习 DL
技术分享 Tech
Author
Huilin Zhang
Date
Sep 16, 2022

基础知识:

为什么需要设置随机种子?因为在需要生成随机数(例如网络权重随机初始化)的实验中,如果能确保每次运行.py文件时,生成的随机数都是固定的,这样每次实验结果显示也就一致了(实验的可复现性)。
  • random随机数是这样生成的:我们将这套复杂的算法(就叫随机数生成器吧)看成一个黑盒,把我们准备好的种子扔进去,它会返给你两个东西,一个是你想要的随机数,另一个是保证能生成下一个随机数的新的种子,把新的种子放进黑盒,又得到一个新的随机数和一个新的种子,从此在生成随机数的路上越走越远。
  • 参数就是种子的数字(int),例如args.seed = 1。种子的数字相同,生成的随机数就是固定的。(可以理解为选择了随机数生成函数的某组结果,而这个结果是固定的)
  • 设置随机种子后,是每次运行.py文件的输出结果都一样,而不是每次随机函数生成的结果一样。例如:
import torch torch.manual_seed(0) print(torch.rand(1)) print(torch.rand(1)) >>>tensor([0.4963]) tensor([0.7682])
  • 如果你就是想要每次运行随机函数生成的结果都一样,那你可以在每个随机函数前都设置一模一样的随机种子:
import torch torch.manual_seed(0) print(torch.rand(1)) torch.manual_seed(0) print(torch.rand(1)) >>>tensor([0.4963]) tensor([0.4963])
  • 一共有五种:
    • torch.cuda.manual_seed(args.seed)
    • torch.cuda.manual_seed_all(args.seed)
    • torch.manual_seed(args.seed)
    • random: random.seed(args.seed)
    • numpy: numpy.random.seed(args.seed)

在训练时使用:

如果代码涉及到了设置随机数,一定要使用下面的代码(放在train.py的最前面)将所有种类的种子全部设置一遍,防止实验环境不同(PyTorch版本不一样)导致实验结果差距很大,做消融实验、平时验证想法时,都得用这个。(可参考Reference)
  • 关于CUDNN的设置,cudnn中对卷积操作进行了优化,牺牲了精度来换取计算效率。如果需要保证可重复性,可以使用如下设置。不过实际上这个设置对精度影响不大,仅仅是小数点后几位的差别。所以如果不是对精度要求极高,其实不太建议修改,因为会使模型处理速度大概降到原来的1/10。 我用的PyTorch1.12,加上之后没有变慢,而且必须加这两行,否则就不固定。有可能后来的版本优化了这个问题。
    • torch.backends.cudnn.benchmark = False
    • torch.backends.cudnn.deterministic = True
  • 如果读取数据的过程采用了随机预处理(如RandomCrop、RandomHorizontalFlip等),那么对python、numpy的随机数生成器也需要设置种子。即下面的np.random.两种。
  • 关于dataloader,如果dataloader采用了多线程(num_workers > 1), 那么由于读取数据的顺序不同,最终运行结果也会有差异。也就是说,改变num_workers参数,也会对实验结果产生影响。目前暂时没有发现解决这个问题的方法,但是只要固定num_workers数目(线程数)不变,基本上也能够重复实验结果。对于不同线程的随机数种子设置,主要通过DataLoaderworker_init_fn参数来实现。默认情况下使用线程ID作为随机数种子。如果需要自己设定,可以参考下面的代码,还有这个csdn里也有代码可以参考。
  • 所以也不要换服务器,不同的CPU导致不同的dataloader。
if cfg.seed is not None: # 修改cudnn的模式 torch.backends.cudnn.benchmark = False torch.backends.cudnn.deterministic = True random.seed(cfg.seed) # 为py设置随机种子 np.random.seed(cfg.seed) # 为numpy设置随机种子 torch.manual_seed(cfg.seed) # 为CPU设置随机种子 torch.cuda.manual_seed(cfg.seed) # 为当前GPU设置随机种子 torch.cuda.manual_seed_all(cfg.seed) # 如果使用多个GPU,应该为所有GPU设置种子 # 除此之外,当data_loader的num_workers>1时,也需要进行随机种子的设置 def _init_fn(): np.random.seed(cfg.seed) print('random seed is: ', cfg.seed) train_loader = DataLoader(data_sets, batch_size=8, shuffle=True, num_workers=8, worker_init_fn=_init_fn()) # _init_fn是一个函数,所以调用时要加括号 # 测试集也可以设置随机种子,虽然对于IQA任务来说,测试集在实验过程中是固定的,先测某张图、后测某张图对于最后的结果没影响。
Deterministic,确定的
💡
使用梯度下降法最终得到的局部最优解对于初始位置点的选择很敏感 最优随机种子不应该去找,随机性的存在应该用来评估模型的鲁棒性。一个优秀的模型,不会因为随机初始的位置略微不同,而找不到最优的位置。这是模型本身应该要解决的工作,而不是人为选择一个随机数使模型达到最优(这属于炼丹哈哈哈)。

实战结果

  • 可以看到,seed=3407时,两次的PLCC和SROCC曲线分别重合,loss下降过程也完全一致。
notion image
notion image

Reference:

不同PyTorch版本训练同一个代码结果差异巨大_yyywxk的博客-CSDN博客_pytorch版本不同 结果差距大
笔者在训练一个深度学习网络时,发现使用不同的PyTorch版本运行同一个训练代码,训练出来的网络结果差异巨大。具体来说,笔者训练得到的结果如下所示: 网络的参数以及训练的设置完全相同,但是却得到了差异巨大的结果。 发现在 Torchvision>0.6.0 时,模型的训练出现了很大的问题,特别是后面两个版本的训练中,损失函数一直无法降下去,因此断定在预处理部分代码可能存在Bug。 经过检查,发现在 Torchvision=0.7.0 版本时出现了一个 更新 : [Transforms] Use torch.rand instead of random.random() for random transforms (#2520) 而我的代码中,预训练部分只设置了 random.seed(seed) 和 np.random.seed(seed) ,由此导致图像和标签的预训练产生的随机不一致,故导致高版本时训练的损失函数迟迟不能下降。 Torchvision=0.7.0 及以后的版本中 torchvision.transforms.RandomHorizontalFlip() 源码为: Torchvision=0.6.0 及以前的版本中 torchvision.transforms.RandomHorizontalFlip() 源码为: 显然,它们产生随机数的函数是不同的。 在 Torchvision=0.6.0 及以前的版本中使用 torchvision.transforms 下的函数时,可以只设置 random 函数的随机种子,但是这样不保险,程序在更高版本时则会出现Bug。 由于在 Torchvision=0.7.0 及以后的版本中预处理的随机数改为 torch.rand ,因此需要对 PyTorch 设置随机种子: 统一起来可以这样设置: 设置完之后再次用四种版本进行训练,结果如下: 写代码时,首先考虑同一环境下同一代码的可重复性,即让Pytorch是Deterministic(确定)的。可以参考如下文章: PyTorch的可重复性问题 (如何使实验结果可复现) Deterministic Pytorch: pytorch如何保证可重复性 把这一个工作做踏实也避免了后续的Bug。
不同PyTorch版本训练同一个代码结果差异巨大_yyywxk的博客-CSDN博客_pytorch版本不同 结果差距大
PyTorch的可重复性问题 (如何使实验结果可复现)_hyk_1996的博客-CSDN博客_pytorch 可重复
由于在模型训练的过程中存在大量的随机操作,使得对于同一份代码,重复运行后得到的结果不一致。因此,为了得到可重复的实验结果,我们需要对随机数生成器设置一个固定的种子。 许多博客都有介绍如何解决这个问题,但是很多都不够全面,往往不能保证结果精确一致。我经过许多调研和实验,总结了以下方法,记录下来。 全部设置可以分为三部分: cudnn中对卷积操作进行了优化,牺牲了精度来换取计算效率。如果需要保证可重复性,可以使用如下设置: from torch.backends import cudnn cudnn.benchmark = False # if benchmark=True, deterministic will be False cudnn.deterministic = True 不过实际上这个设置对精度影响不大,仅仅是小数点后几位的差别。所以如果不是对精度要求极高,其实不太建议修改,因为会使计算效率降低。 torch.manual_seed(seed) # 为CPU设置随机种子 torch.cuda.manual_seed(seed) # 为当前GPU设置随机种子 torch.cuda.manual_seed_all(seed) # 为所有GPU设置随机种子 如果读取数据的过程采用了随机预处理(如RandomCrop、RandomHorizontalFlip等),那么对python、numpy的随机数生成器也需要设置种子。 import random import numpy as np random.seed(seed) np.random.seed(seed) 最后,关于dataloader: 注意,如果dataloader采用了多线程(num_workers > 1), 那么由于读取数据的顺序不同,最终运行结果也会有差异。 也就是说,改变num_workers参数,也会对实验结果产生影响。 目前暂时没有发现解决这个问题的方法,但是只要固定num_workers数目(线程数)不变,基本上也能够重复实验结果。 对于不同线程的随机数种子设置,主要通过DataLoader的worker_init_fn参数来实现。默认情况下使用线程ID作为随机数种子。如果需要自己设定,可以参考以下代码: GLOBAL_SEED = 1 def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) GLOBAL_WORKER_ID = None def worker_init_fn(worker_id): global GLOBAL_WORKER_ID GLOBAL_WORKER_ID = worker_id set_seed(GLOBAL_SEED + worker_id) dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=2, worker_init_fn=worker_init_fn)
PyTorch的可重复性问题 (如何使实验结果可复现)_hyk_1996的博客-CSDN博客_pytorch 可重复