使用 PyTorch 实现可复现的深度学习

  • darinabal
  • 发布于 2022-09-23 15:50
  • 阅读 12

本文介绍了如何在 PyTorch 中实现可复现的深度学习结果。文章涵盖了随机种子设置、数据分割、数据加载和确定性操作等关键步骤,并提供了相应的代码示例,以确保实验结果的一致性和可比性。通过遵循这些方法,可以有效地调试代码、比较模型,并优化深度学习流程。

使用 PyTorch 实现可复现的深度学习

图片来自 Samer Daboul,来自 Pexels

你是否尝试过比较不同模型配置或各种模型的结果? 也许你尝试过重构你的深度学习(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 可能会停止执行。

注意:

  • 如果使用上述 cuDNN 设置不会复现你的结果,请使用 torch.backends.cudnn.enabled = False。它控制是否启用 cuDNN。禁用 cuDNN 可以解决可复现性问题。
  • 使用带有机器学习模型的 cuDNN 时,请注意所需的特定 cuDNN、CUDA 或任何 Python 库版本。使用不正确的版本可能会导致项目出现问题。

结论

为了站在“可复现的一边”,请按照上述说明保持你的“数据分割”和“数据加载”过程。

为了快速实现流程其他部分的确定性行为,我建议定义以下函数:

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.

并在算法的开头调用它。

参考

GitHub - NVIDIA/framework-determinism: 在深度学习框架中提供确定性 该存储库旨在提供与确定性相关的文档、状态、补丁和工具(bit-accurate...) github.com

开发者指南 :: NVIDIA Deep Learning cuDNN 文档 这份 cuDNN 8.5.0 开发者指南概述了 NVIDIA cuDNN 的特性,例如可定制的数据布局... docs.nvidia.com

可复现性 - PyTorch 1.12 文档 不能保证在 PyTorch 版本、单个提交或不同平台之间完全可复现的结果... pytorch.org

  • 原文链接: darinabal.medium.com/dee...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
darinabal
darinabal
江湖只有他的大名,没有他的介绍。