深度学习实验可复现问题 深度学习在训练过程中,由于随机初始化,样本读取的随机性,导致重复的实验结果会有差别,个别情况甚至波动较大。一般论文为了严谨,实验结论能够复现/可重复,通常采取固定随机种子使得结果确定。先给出我的完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 os.environ["CUDA_LAUNCH_BLOCKING" ] = "1" os.environ["CUBLAS_WORKSPACE_CONFIG" ] = ":16:8" seed = config["seed" ] device = config["cuda_device" ] random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) os.environ["PYTHONHASHSEED" ] = str (seed) if torch.cuda.is_available(): torch.cuda.set_device(device) torch.backends.cudnn.benchmark = False torch.backends.cudnn.deterministic = True torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.use_deterministic_algorithms(True ) def seed_worker (worker_id ): worker_seed = torch.initial_seed() % 2 **32 np.random.seed(worker_seed) random.seed(worker_seed) g = torch.Generator() g.manual_seed(seed) test_loader = DataLoader( test_set, collate_fn=test_set.collate, batch_size=config["batch_size" ]["test" ], shuffle=config["shuffle" ]["test" ], num_workers=config["num_workers" ]["test" ], worker_init_fn=seed_worker, generator=g, )
接下来我们再来详细解释这段代码的意义。
随机种子设置 随机函数是最大的不确定性来源,影响了模型参数的随机初始化,样本的shuffle等等操作。
PyTorch 随机种子
python 随机种子
numpy 随机种子
1 2 3 4 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) os.environ["PYTHONHASHSEED" ] = str (seed)
CPU版本下,上述随机种子设置完成之后,基本就可实现实验的可复现了。但是对于GPU版本,存在大量算法实现为不确定结果的算法,这种算法实现效率很高,但是每次返回的值会不完全一样。主要是由于浮点精度舍弃,不同浮点数以不同顺序相加,值可能会有很小的差异(小数点最末位)。此外,官方的文档提到,对于 RNN 类模型会因为 cuDNN 和 CUDA 的原因导致结果无法复现,可以通过设置环境变量来解决。
CUDA 10.1:设置环境变量 CUDA_LAUNCH_BLOCKING=1
CUDA 10.2 或者更高版本:设置环境变量 (注意两个冒号)CUBLAS_WORKSPACE_CONFIG=:16:8
或者 CUBLAS_WORKSPACE_CONFIG=:4096:2
.
1 2 os.environ["CUDA_LAUNCH_BLOCKING" ] = "1" os.environ["CUBLAS_WORKSPACE_CONFIG" ] = ":16:8"
GPU算法确定性实现 GPU算法的不确定来源有两个
CUDA convolution benchmarking
Nondeterministic algorithms
CUDA convolution benchmarking :这个设置是为了提升运行效率,对模型参数试运行后,选取最优实现。不同硬件以及benchmarking本身存在噪音,导致不确定性。
Nondeterministic algorithms :GPU最大优势就是并行计算,如果能够忽略顺序,就避免了同步要求,能够大大提升运行效率,所以很多算法都有非确定性结果的算法实现。通过设置use_deterministic_algorithms,就可以使得pytorch选择确定性算法。
1 2 3 4 torch.backends.cudnn.benchmark=False torch.use_deterministic_algorithms(True )
RUNTIME ERROR 对于一个PyTorch 的函数接口,没有确定性算法实现,只有非确定性算法实现,同时设置了use_deterministic_algorithms(),那么会导致运行时错误。比如:
1 2 3 4 5 6 7 >>> import torch>>> torch.use_deterministic_algorithms(True )>>> torch.randn(2 , 2 ).cuda().index_add_(0 , torch.tensor([0 , 1 ]), torch.randn(2 , 2 ))Traceback (most recent call last): File "<stdin>" , line 1 , in <module> RuntimeError: index_add_cuda_ does not have a deterministic implementation, but you set 'torch.use_deterministic_algorithms(True)' . ...
错误原因:
index_add没有确定性的实现,出现这种错误,一般都是因为调用了torch.index_select 这个api接口,或者直接调用tensor.index_add_。
解决方案:
自己定义一个确定性的实现,替换调用的接口。对于torch.index_select 这个接口,可以有如下的实现。
1 2 3 4 5 6 7 8 def deterministic_index_select (input_tensor, dim, indices ): """ input_tensor: Tensor dim: dim indices: 1D tensor """ tensor_transpose = torch.transpose(x, 0 , dim) return tensor_transpose[indices].transpose(dim, 0 )
样本读取随机
多线程情况下,设置每个线程读取的随机种子
设置样本generator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def seed_worker (worker_id ): worker_seed = torch.initial_seed() % 2 **32 numpy.random.seed(worker_seed) random.seed(worker_seed) g = torch.Generator() g.manual_seed(0 ) DataLoader( train_dataset, batch_size=batch_size, num_workers=num_workers, worker_init_fn=seed_worker, generator=g, )
nn.Upsample 在我自己的实验中,上述设置全部配置完成后依然出现了无法复现的问题。幸运的是 torch.use_deterministic_algorithms(True) 这个设置进行了报错,经实验发现 nn.Upsample 中调用了 F.interpolate,其 bilinear 插值模式是 nondeterministic 的,只有默认的 nearest 模式是 deterministic 的,但是效果比前者差了很多很多。在训练中可以使用转置卷积进行上采样,但是在深度监督中使用这个方法太过奇怪,放到 CPU 中跑完再送回 GPU 又太过耗时,目前仍没有很好地解决方法,因此暂时选择通过多次重复实验选取中位数或者均值作为实验结果。