本文介绍了如何在 PyTorch 中实现可复现的深度学习结果。文章涵盖了随机种子设置、数据分割、数据加载和确定性操作等关键步骤,并提供了相应的代码示例,以确保实验结果的一致性和可比性。通过遵循这些方法,可以有效地调试代码、比较模型,并优化深度学习流程。
你是否尝试过比较不同模型配置或各种模型的结果? 也许你尝试过重构你的深度学习(DL)代码,而不是为了改变它的底层功能。例如:重写混乱的代码,集成现有代码与你的特定任务需求,优化特定的代码段等等。你如何确保没有添加错误?
神经网络项目充满了非确定性的过程,导致每次执行的结果都不同。要进行公平的比较,你需要启用可复现性。
在本文中,我将解释如何实现它。让我们开始吧!
“绝对决定论的假设是每一个科学探究的必要基础”,马克斯·普朗克
在机器学习过程中获得可复现的功能涉及几个方面的考虑: 随机种子,数据分割,数据加载和确定性操作。
当你设置随机种子时,你确保各种伪随机数生成器(PRNGs)是可复现的。这样做如下:
import random
import numpy as np
from numpy.random import MT19937
from numpy.random import RandomState, SeedSequence
import torch
SEED = 12345
rs = RandomState(MT19937(SeedSequence(SEED)))
random.seed(SEED)
torch.manual_seed(SEED)
SEED
可以是你选择的任何整数。RandomState(MT19937(SeedSequence()))
创建一个新的 BitGenerator。你也可以使用 np.seed()
初始化 python RNG (重新 seeds BitGenerator) 并为自定义运算符设置种子,但请注意 NumPy 建议将第一种选择作为最佳实践。np.random.seed()
重新 seed BitGenerator。如果任何库或代码依赖于 NumPy,请对其进行 seed。torch.manual_seed()
设置用于生成随机数的种子。当你“随机”地将数据分割成训练和验证子集时,你必须确保下次运行和评估模型时,可以复现相同的数据分割。为此,你按如下方式设置种子:
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=SEED)
random_state
控制 在应用分割之前应用于数据的洗牌,从而可以在多个函数调用中实现可复现的输出。
你需要确保你的数据加载过程是可复现的。每次算法执行时加载到模型中的数据应该是相同的,以使结果具有可比性。
正如 NVIDIA GitHub 建议的那样,为了每个 epoch 洗牌 不同但可复现,你应该通过在 torch.utils.data.DataLoader
中创建 torch.Generator
的实例 ( self.g
) 来重置生成器,并按如下方式使用它:
def set_epoch(self, epoch):
self.epoch = epoch
if self.shuffle:
self.g.manual_seed(123456789 + self.epoch)
set_epoch
应该在每个 epoch 的开始调用。
请注意,该函数是作为 DataLoader 类中的一个方法实现的,但是你可以实现一种对你来说更方便的方式。
此外,正如 PyTorch 文档 所建议的那样,在多进程数据加载算法中,DataLoader
将使用 worker_init_fn()
重新为 workers 设置 seed,以按以下方式保持可复现性:
import torch
import numpy as np
import random
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(0)
DataLoader(
train_dataset,
batch_size=batch_size,
num_workers=num_workers,
worker_init_fn=seed_worker,
generator=g,
)
请注意,在创建 torch.Generator()
对象时,应提及设备,如下所示:
import torch
g_cpu = torch.Generator()
g_cuda = torch.Generator(device='cuda')
我鼓励你尝试不同的数据分割和加载的随机状态,并检查结果的差异。
NVIDIA CUDA Deep Neural Network (cuDNN) 是一个用于深度神经网络的 GPU 加速库。CUDA 是一个框架,是 cuDNN 的基础,它使用 CUDA 来加速计算任务并针对它进行优化。CuDNN 是一种使用 CUDA 的深度学习库,CUDA 是联系 GPU 的方式。
CUDA
设置当前 GPU 生成随机数的种子:
import torch.cuda
SEED = 12345
torch.cuda.manual_seed(SEED)
或者对于所有 GPU:
import torch.cuda
SEED = 12345
torch.cuda.manual_seed_all(SEED)
cuDNN
正如 NVIDIA cuDNN 文档 中声明的那样,同一版本的 cuDNN 的大多数例程都设计为在相同的架构 GPU 上执行时,跨运行生成相同的按位(bit-wise)结果。但是,也存在例外情况,例如 ConvolutionBackwardFilter,ConvolutionBackwardData,PoolingBackward,SpatialTfSamplerBackward,CTCLoss 等,即使在相同的架构上运行,也不能保证可复现的结果。原因是原子操作(以完全独立于任何其他进程的方式运行的程序操作)的使用,这种使用引入了真正的随机浮点舍入误差。而且,在不同的架构之间,没有任何 cuDNN 例程保证按位可复现性。
那么,你应该怎么做?
import torch.backends.cudnn
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
导致 cuDNN 仅使用确定性Rollup算法。如果存在其他非确定性函数,则不能保证你的训练过程是确定性的。另一方面,torch.use_deterministic_algorithms(True)
影响所有通常非确定性的操作。正如文档所述,某些列出的操作没有确定性的实现,并且会引发错误。如果需要使用非确定性操作,解决方案是自己编写自定义的确定性实现。
torch.backends.cudnn.benchmark = True
导致 cuDNN 对多个Rollup算法进行基准测试并选择最快的算法。因此,当设置为 False
时,它将禁用 cuDNN Rollup算法的动态选择,并确保算法选择本身是可复现的。如果你的模型没有变化并且你的输入大小保持不变 —— 那么你可能会受益于设置 torch.backends.cudnn.benchmark = True
。如果输入大小发生变化,cuDNN 每次出现新的输入大小时都会进行基准测试,这将导致更差的性能。
但是,如果你的模型更改了输入大小,则某些层在某些条件下被激活等,那么设置为 True
可能会停止执行。
注意:
torch.backends.cudnn.enabled = False
。它控制是否启用 cuDNN。禁用 cuDNN 可以解决可复现性问题。为了站在“可复现的一边”,请按照上述说明保持你的“数据分割”和“数据加载”过程。
为了快速实现流程其他部分的确定性行为,我建议定义以下函数:
import random
import numpy as np
from numpy.random import MT19937
from numpy.random import RandomState, SeedSequence
import torch
import torch.backends.cudnn
import torch.cuda
def set_determenistic_mode(SEED, disable_cudnn):
torch.manual_seed(SEED) # Seed the RNG for all devices (both CPU and CUDA).
random.seed(SEED) # Set python seed for custom operators.
rs = RandomState(MT19937(SeedSequence(SEED))) # If any of the libraries or code rely on NumPy seed the global NumPy RNG.
np.random.seed(SEED)
torch.cuda.manual_seed_all(SEED) # If you are using multi-GPU. In case of one GPU, you can use # torch.cuda.manual_seed(SEED).
if not disable_cudnn:
torch.backends.cudnn.benchmark = False # Causes cuDNN to deterministically select an algorithm,
# possibly at the cost of reduced performance
# (the algorithm itself may be nondeterministic).
torch.backends.cudnn.deterministic = True # Causes cuDNN to use a deterministic convolution algorithm,
# but may slow down performance.
# It will not guarantee that your training process is deterministic
# if you are using other libraries that may use nondeterministic algorithms
else:
torch.backends.cudnn.enabled = False # Controls whether cuDNN is enabled or not.
# If you want to enable cuDNN, set it to True.
并在算法的开头调用它。
可复现性 - PyTorch 1.12 文档 不能保证在 PyTorch 版本、单个提交或不同平台之间完全可复现的结果... pytorch.org
- 原文链接: darinabal.medium.com/dee...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!