提高工作效率的PyTorch技巧

2023年08月24日 由 camellia 发表 223 0

介绍


你是否花费了几个小时来调试机器学习模型,但是却找不到为什么准确率不提升的原因?你是否曾经觉得一切应该工作得完美,但是出于一些神秘的原因你没有获得卓越的结果?


那么现在不会再有这种情况了。作为初学者来探索PyTorch可能会让人感到困惑。在本文中,你将探索经过验证的工作流程,这些工作流程一定会改善你的结果并提升模型的性能。


arham_pytorch_tips_boost_productivity_1


1.过度拟合单个批次


在大型数据集上训练了几个小时的模型,发现损失没有减少,准确率只是趋于平稳?那么,首先进行一个“健全性检查”。


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


与其在整个数据集上进行训练,不如始终在单个批次上训练以进行“健全性检查”。

batch = next(iter(train_dataloader)) # Get a single batch

# For all epochs, keep training on the single batch.
for epoch in range(num_epochs):
inputs, targets = batch
predictions = model.train(inputs)


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


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

# For all epochs, iterate over all batches of data.
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 = # Loading Data
dataloder = DataLoader(dataset, shuffle=True)


此外,在使用机器学习模型时,对数据进行归一化非常重要。当数据的变化范围很大并且某个参数的值比数据集中的其他属性都要大时,进行归一化是必要的。这可能会导致某个参数主导其他参数,从而降低准确性。我们希望所有的输入参数都在同一范围内,最好是拥有0的均值和1.0的方差。为此,我们必须转换我们的数据集。通过知道数据集的均值和方差,我们可以简单地使用torchvision.transforms.Normalize函数进行归一化。

import torchvision.transforms as transforms

image_transforms = transforms.Compose([
transforms.ToTensor(),
# Normalize the values in our data
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() # Remove all previous gradients
loss = criterion(targets, predictions)
loss.backward() # Computes Gradients for model weights

# Clip the gradients of model weights to a specified max_norm value.
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)

# Optimize the model weights AFTER CLIPPING
optimizer.step()


为了解决梯度爆炸的问题,我们使用梯度裁剪技术,它将梯度值裁剪在指定范围内。例如,如果我们将裁剪值或范数值设为1,则所有梯度都将被裁剪在[-1, 1]范围内。如果我们有一个梯度爆炸值为50,则它将被裁剪为1。因此,梯度裁剪可以解决梯度爆炸的问题,允许模型进行缓慢地优化,使其趋同。


4.切换训练/评估模式


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


为了更好地理解,请考虑此代码片段。

for epoch in range(num_epochs):

# Using training Mode when iterating over training dataset
model.train()
for batch in iter(train_dataloader):
# Training Code and Loss Optimization

# Using Evaluation Mode when checking accuarcy on validation dataset
model.eval()
for batch in iter(val_dataloader):
# Only predictions and Loss Calculations. No backpropogation
# No Optimzer Step so we do can omit unrequired layers.


在评估过程中,我们不需要对模型参数进行任何优化。我们不会在验证步骤中计算任何梯度。为了进行更好的评估,我们可以省略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


# Inherit from the Module Base Class
class Model(nn.Module):
def __init__(self, input_size, output_size):
# Initialize the Module Parent Class
super().__init__()

self.dense_layers = nn.ModuleList()

# Add 5 Linear Layers and contain them within a 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):

# Simplifies Foward Propogation.
# Instead of repeating a single line for each layer, use a loop
for layer in range(len(self.dense_layers)):
x = layer(x)

return self.output_layer(x)


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


结论


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

文章来源:https://www.kdnuggets.com/2023/08/pytorch-tips-boost-productivity.html
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消