基础知识:
为什么需要设置随机种子?因为在需要生成随机数(例如网络权重随机初始化)的实验中,如果能确保每次运行.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数目(线程数)不变,基本上也能够重复实验结果。对于不同线程的随机数种子设置,主要通过
DataLoader
的worker_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任务来说,测试集在实验过程中是固定的,先测某张图、后测某张图对于最后的结果没影响。
使用梯度下降法最终得到的局部最优解对于初始位置点的选择很敏感。
最优随机种子不应该去找,随机性的存在应该用来评估模型的鲁棒性。一个优秀的模型,不会因为随机初始的位置略微不同,而找不到最优的位置。这是模型本身应该要解决的工作,而不是人为选择一个随机数使模型达到最优(这属于炼丹哈哈哈)。
实战结果
- 可以看到,seed=3407时,两次的PLCC和SROCC曲线分别重合,loss下降过程也完全一致。