提高工作效率的PyTorch技巧

PyTorch技巧提高效率

 

介绍

 

你是否曾经花费数小时来调试一个机器学习模型,却找不到准确率不提高的原因?你是否曾经觉得一切都应该完美运行,但由于某种神秘的原因你却没有得到出色的结果?

不再有这种情况了。作为初学者,探索PyTorch可能令人望而生畏。在本文中,您将探索经过验证的工作流程,这些工作流程肯定会改善您的结果并提高模型的性能。

 

1. 过度拟合单批次数据

 

是否曾经在大型数据集上训练了数小时的模型,却发现损失不再下降,准确率也趋于平稳?那么,请先进行一次合理性检查。

在大型数据集上进行训练和评估可能很耗时,而且在数据的一个小子集上首先调试模型更容易。一旦我们确定模型可以工作,我们就可以轻松地将训练扩展到完整的数据集。

不要在整个数据集上进行训练,始终在单个批次上进行训练以进行合理性检查

batch = next(iter(train_dataloader)) # 获取单个批次

# 对于所有的epochs,持续在单个批次上训练。
for epoch in range(num_epochs):
    inputs, targets = batch    
    predictions = model.train(inputs)

 

考虑上面的代码片段。假设我们已经有了一个训练数据加载器和一个模型。我们可以轻松地获取数据集的第一个批次,而不是遍历整个数据集。然后,我们可以在单个批次上进行训练,以检查模型是否能够学习数据的模式和变化。

如果损失减小到一个非常小的值,我们就知道模型可以过度拟合这些数据,并且可以确信它可以在短时间内进行学习。然后,我们可以通过仅更改一行代码来将其训练到完整的数据集上:

# 对于所有的epochs,遍历所有的数据批次。
for epoch in range(num_epochs):
    for batch in iter(dataloader):
        inputs, targets = batch    
        predictions = model.train(inputs)

 

如果模型可以过度拟合单个批次数据,它应该能够学习完整数据集中的模式。这种过度拟合批次的方法可以更容易地进行调试。如果模型甚至不能过度拟合一个单个批次,我们可以确定问题出在模型实现上,而不是数据集上。

 

2. 数据归一化和洗牌

 

对于数据序列不重要的数据集,洗牌数据是很有帮助的。例如,对于图像分类任务,如果将不同类别的图像放入单个批次中,模型会更好地适应数据。 如果传递的数据按照相同的顺序,我们将面临模型基于数据传递的顺序学习模式的风险,而不是学习数据内在的变化。因此,最好传递洗牌后的数据。为此,我们可以简单地使用PyTorch提供的DataLoader对象,并将shuffle设置为True。

from torch.utils.data import DataLoader

dataset = # 加载数据
dataloder = DataLoader(dataset, shuffle=True)

 

此外,当使用机器学习模型时,归一化数据是很重要的。当我们的数据存在较大的方差,并且某个特定参数的值高于数据集中所有其他属性时,归一化就显得尤为重要。这可能导致其中一个参数主导其他所有参数,从而降低准确性。我们希望所有输入参数都处于相同的范围内,最好是具有0的均值和1.0的方差。为此,我们必须对数据集进行转换。通过了解数据集的均值和方差,我们可以简单地使用torchvision.transforms.Normalize函数。

import torchvision.transforms as transforms

image_transforms = transforms.Compose([
    transforms.ToTensor(),
    # 对我们的数据中的值进行归一化
    transforms.Normalize(mean=(0.5,), std=(0.5))
])

 

我们可以将每个通道的均值和标准差传递给transforms.Normalize函数,它将自动将数据转换为具有0均值和标准差为1的数据。

 

3. 梯度裁剪

 

梯度爆炸是RNN和LSTM中已知的问题。然而,它不仅限于这些架构。任何具有深层的模型都可能遭受梯度爆炸的问题。在高梯度上的反向传播可能导致发散而不是逐渐减少的损失。

考虑下面的代码片段。

for epoch in range(num_epochs):
    for batch in iter(train_dataloader):
        inputs, targets = batch
        predictions = model(inputs)
     
     
        optimizer.zero_grad() # 移除所有先前的梯度
        loss = criterion(targets, predictions)
        loss.backward() # 计算模型权重的梯度
     
        # 将模型权重的梯度裁剪到指定的max_norm值
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)
     
        # 裁剪后优化模型权重
        optimizer.step()

 

为了解决梯度爆炸问题,我们使用梯度裁剪技术,将梯度值裁剪在指定的范围内。例如,如果我们将1作为裁剪或范数值,所有梯度将被裁剪在[-1, 1]范围内。如果我们有一个梯度值为50的梯度爆炸,它将被裁剪为1。因此,梯度裁剪解决了梯度爆炸问题,使得模型能够缓慢地优化朝着收敛方向。

 

4. 切换训练/评估模式

 

这一行代码肯定会提高模型的测试准确性。几乎所有深度学习模型都会使用dropout和归一化层。这些只在稳定的训练中才需要,以确保模型不会因数据的变化而过拟合或发散。诸如BatchNorm和Dropout的层在训练期间为模型参数提供了正则化。然而,一旦训练完毕,它们就不再需要了。 将模型切换到评估模式会禁用仅在训练中需要的层,并使用完整的模型参数进行预测。

为了更好地理解,考虑下面的代码片段。

for epoch in range(num_epochs):
    
    # 在训练数据集上进行迭代时使用训练模式
    model.train()
    for batch in iter(train_dataloader):
            # 训练代码和损失优化
    
    # 在验证数据集上进行准确性检查时使用评估模式
    model.eval()
    for batch in iter(val_dataloader):
            # 仅进行预测和损失计算,没有反向传播
            # 没有优化器步骤,因此我们可以省略不需要的层。

 

在评估过程中,我们不需要对模型参数进行任何优化。我们在验证步骤中不计算任何梯度。为了更好地进行评估,我们可以省略Dropout和其他归一化层。例如,它将启用所有模型参数,而不仅仅是像Dropout层中的一部分权重。这将大大提高模型的准确性,因为您将能够使用完整的模型。

 

5. 使用Module和ModuleList

 

PyTorch模型通常继承自torch.nn.Module基类。根据文档:

以这种方式分配的子模块将被注册,并且在调用to()等函数时会转换其参数。

模块基类允许注册模型中的每个层。然后我们可以使用model.to()和类似的函数,如model.train()和model.eval(),它们将应用于模型中的每个层。如果不这样做,将不会更改模型中包含的每个层的设备或训练模式。您将不得不手动执行此操作。 一旦您简单地在模型对象上使用一个函数,模块基类将自动为您进行转换。

此外,一些模型包含类似的连续层,可以使用for循环轻松初始化,并包含在列表中。这简化了代码。然而,它引起了与上述相同的问题,因为简单的Python列表中的模块不会自动注册到模型中。 我们应该使用ModuleList来包含模型中的类似连续层。

import torch
import torch.nn as nn


# 从Module基类继承
class Model(nn.Module):
      def __init__(self, input_size, output_size):
            # 初始化Module的父类
            super().__init__()

             self.dense_layers = nn.ModuleList()

            # 添加5个线性层,并将它们包含在ModuleList中
            for i in range(5):
                self.dense_layers.append(
                    nn.Linear(input_size, 512)
                )

            self.output_layer = nn.Linear(512, output_size)

    def forward(self, x):

            # 简化前向传播。
            # 使用循环而不是为每个层重复一行代码
            for layer in range(len(self.dense_layers)):
            x = layer(x)

            return self.output_layer(x)

 

上面的代码片段展示了使用模型和子层创建模型的正确方式。在训练和评估模型时,使用Module和ModuleList可以避免意外错误。

 

结论

 

上述方法是PyTorch机器学习框架的最佳实践。它们被广泛使用,并且得到了PyTorch文档的推荐。使用这些方法应该是机器学习代码流程的主要方式,肯定会提高结果。

穆罕默德·阿尔哈姆(Muhammad Arham)是一名深度学习工程师,专注于计算机视觉和自然语言处理。他曾在Vyro.AI部署和优化多个生成式人工智能应用,这些应用在全球排行榜上名列前茅。他热衷于构建和优化智能系统的机器学习模型,并坚信不断改进。