【pytorch】深度学习模型调参策略(四):鉴别并解决训练中的不收敛及不稳定的情况

66 篇文章 1 订阅
44 篇文章 4 订阅

识别不稳定的工作负载

如果学习率过大,任何工作负载都会变得不稳定。当不稳定性迫使我们使用学习率过小时,它才成为一个问题。

可以区分至少两种值得注意的训练不稳定性:

初始化/训练早期的不稳定性。

在训练中期突然出现的不稳定性。

梯度爆炸

梯度爆炸指的是在反向传播过程中,梯度的值变得非常大,从而导致模型参数的更新值也变得非常大。梯度爆炸可能会对模型的训练和性能造成以下几个方面的负面影响:

模型不稳定:梯度爆炸可能导致模型的参数更新值变得非常大,从而使模型变得不稳定,难以收敛到最优解。

过拟合:梯度爆炸可能导致模型的参数值变得非常大,从而使模型的复杂度增加,容易过拟合。

学习速度下降:梯度爆炸会导致模型参数的更新值变得非常大,从而使学习速度变慢,难以达到最优解。

数值不稳定:梯度爆炸可能导致数值不稳定,比如出现NaN或者Inf等异常值,从而导致模型无法正常训练。
因此,梯度爆炸是一种非常有害的现象,需要通过梯度裁剪等方法对其进行控制,以保证模型的训练和性能。

常见不稳定性模式的潜在解决方法

应用学习率预热

学习率预热是指在训练神经网络的初始阶段,先使用较小的学习率进行一定的训练,然后再逐步地增加学习率,以达到更好的训练效果。这样做可以避免在开始训练时,学习率过大导致的训练不稳定或梯度爆炸等问题。通过逐步增加学习率,可以让模型逐渐适应更大的梯度更新,从而更好地优化模型的参数。

LambdaLR是PyTorch中的一个学习率调整器,它可以根据自定义的函数来调整优化器的学习率。具体来说,LambdaLR函数的作用是根据给定的函数lr_lambda计算每个epoch的学习率,并将其应用于优化器中。其中,lr_lambda是一个函数,用于计算每个epoch的学习率。该函数接受一个整数作为输入,表示当前的epoch数,返回一个浮点数作为输出,表示当前epoch的学习率。例如,一个简单的lr_lambda函数可以如下所示:

import torch.optim as optim
import torch.nn as nn
from torch.optim.lr_scheduler import LambdaLR
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        # 定义模型结构
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(64 * 8 * 8, 512)
        self.fc2 = nn.Linear(512, 10)
    def forward(self, x):
        # 前向传播
        x = self.conv1(x)
        x = self.relu(x)
        x = self.conv2(x)
        x = self.relu(x)
        x = self.pool(x)
        x = x.view(-1, 64 * 8 * 8)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x
# 实例化模型
model = MyModel()
# 定义优化器和学习率预热函数
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
warmup_epochs = 5
warmup_factor = 0.1
#最终学习率,为lr*warmup_lambda函数计算出的值
def warmup_lambda(epoch):
    if epoch < warmup_epochs:
        #epoch从0开始,避免为0
        return (epoch+1) / warmup_epochs
    return 1
# 定义学习率调度器
scheduler = LambdaLR(optimizer, lr_lambda=warmup_lambda)
# 训练过程中按照epoch调用学习率调度器
for epoch in range(1,11):
    print("第%d个epoch的学习率:%f 调整后为 %f " % (epoch, optimizer.param_groups[0]['lr'], scheduler.get_last_lr()[0]))
    # 先使用学习率更新参数
    optimizer.step()  
    # 更新学习率
    scheduler.step()

应用梯度剪裁

梯度裁剪(Gradient Clipping)是一种在训练神经网络时用于限制梯度值的技术。它可以解决梯度爆炸的问题,即在训练过程中,由于梯度值过大,导致权重参数更新过度,最终导致模型不稳定甚至无法收敛的情况。梯度裁剪通过设置一个梯度阈值来限制梯度的大小,使得梯度在一定范围内,从而避免梯度爆炸的问题。常见的梯度裁剪方法包括L2范数裁剪和L1范数裁剪。在实际应用中,梯度裁剪通常与其他优化算法一起使用,如Adam、SGD等,以提高模型的稳定性和收敛速度。

import torch.optim as optim
import torch.nn as nn
from torch.optim.lr_scheduler import LambdaLR
import torch.utils.data as data
import torchvision.datasets as datasets
import torchvision.transforms as transforms
import torch.optim as optim
import torch.nn.functional as F
import torch
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = data.DataLoader(train_dataset, batch_size=64, shuffle=True)
import torchvision.models as models
# 加载预训练的ResNet50模型
resnet = models.resnet50(pretrained=True)
# 将ResNet50的最后一层替换为新的全连接层
num_ftrs = resnet.fc.in_features
resnet.fc = nn.Linear(num_ftrs, 10)  # 假设我们的任务是分类10个类别
# 将模型移动到GPU上进行训练
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
resnet.to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# 实例化模型
import torch.nn.utils as utils
# 定义优化器和梯度裁剪阈值
optimizer = optim.SGD(resnet.parameters(), lr=0.1, momentum=0.9)
max_grad_norm = 10
# 训练过程中执行梯度裁剪
for inputs, labels in train_loader:
    optimizer.zero_grad()
    inputs, labels = inputs.to(device), labels.to(device)
    loss = criterion(resnet(inputs), labels)
    print(loss)
    #梯度计算
    loss.backward()
     # 执行梯度裁剪
    utils.clip_grad_norm_(resnet.parameters(), max_grad_norm)
    #更新梯度
    optimizer.step()

原理:

new_grad = grad * max_grad_norm / max(grad_norm, max_grad_norm)

其中,grad为原始梯度,max_grad_norm为指定的梯度阈值,grad_norm为所有参数的梯度的L2范数。new_grad为裁剪后的梯度。最后,utils.clip_grad_norm_()函数会将裁剪后的梯度应用于模型参数,从而避免梯度爆炸的问题。

||x||2 = sqrt(x1^2 + x2^2 + ... + xn^2)

L2范数能够有效地衡量梯度的大小,因为它是对梯度各元素的平方和进行开方,而平方和能够很好地表示梯度的大小,而开方能够将梯度的大小映射到一个非负的标量值。此外,使用L2范数进行梯度裁剪还有一个好处,就是它能够保持梯度的方向不变,从而不会影响模型的收敛性能。

max_grad_norm是指梯度裁剪的阈值,通常取值在1到10之间,可根据情况进行调整,max_grad_norm的值越大,梯度裁剪的程度越小,模型训练的稳定性相对较差,但可能会取得更好的性能。相反,max_grad_norm的值越小,梯度裁剪的程度越大,模型训练的稳定性相对较好,但可能会导致性能下降。在实际应用中,max_grad_norm的值通常需要根据具体场景进行调整,以获得最佳的模型性能和训练稳定性。

尝试新的优化器

有时候Adam可以处理Momentum无法处理的不稳定性,这是一个活跃的研究领域。

降低学习率

这是最后的手段。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颢师傅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值