PyTorch深度学习:模型优化与进阶技巧探索

PyTorch模型进阶技巧

1. 自定义损失函数

PyTorch在torch.nn模块提供了许多常用的损失函数,如:MSELoss、L1Loss、BCELoss等,但是随着深度学习的发展或研究的需求,需要提出一些新的损失函数,因此我们需要知道如何自定义损失函数。

关于损失函数的定义,可以:

  • 以函数方式定义
  • 以类方式定义,推荐使用
1.1 以函数方式定义损失函数

损失函数本质上就是Python中的函数,因此可以直接定义一个函数即可,如:

def my_loss(output, target):
	loss = torch.mean((output - target) ** 2)
    return loss

1.2 以类方式定义

在实际工程中,使用面向对象的思想可以提高代码的可用性,因此以类方式定义损失函数更加常用,因此这里观察torch中的Loss函数的继承关系,可以发现它们部分继承自
_loss
,部分继承自
_WeightedLoss
,而
_WeightedLoss
继承自
_loss

_loss
继承自nn.Module。

因此,本质上可以把Loss函数当作神经网络的一层对待,同样地,自定义损失函数类需要继承nn.Module,下面以DiceLoss讲解自定义损失函数。

Dice Loss是一种在分割领域常见的损失函数,定义如下:

D

S

C

=

2

X

Y

X

Y

DSC = \frac{2\left|X \cap Y\right|}{|X|+|Y|}

D

SC

=

X

Y

2

X

Y

实现代码如下:

class DiceLoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(DiceLoss, self).__init__()
    
    def forward(self, inputs, targets, smooth=1):
        # smooth is used to prevent denominator from being 0
        inputs = F.sigmoid(inputs)
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()
        dice = (2. * intersection + smooth) / (inputs.sum() + targets.sum() + smooth)
        

# usage
criterion = DiceLoss()
loss = criterion(input, targets)

当然,这里还有很多其它损失函数的定义方式,可以参考
kaggle

注:在自定义损失函数时,涉及到的数学运算,最好使用PyTorch提供的张量计算接口,这样就不需要自己实现自动求导功能,同时也可以调用cuda。

2. 动态调整学习率

深度学习中一个比较重要的问题就是学习率的选择。学习速率设置过小,会极大降低收敛速度,增加训练时间;设置过大,可能导致参数在最优解两侧来回振荡。

因此通常情况下,在模型训练过程中,学习率是动态变化的,即学习率有一个调整策略(学习率通常是随着模型训练逐渐下降的)。这种策略的设置方式在PyTorch中称为scheduler。这里给出
官方文档说明

2.1 使用官方scheduler

在训练神经网络的过程中,学习率是最重要的超参数之一,作为当前较为流行的深度学习框架,PyTorch已经在
torch.optim.lr_scheduler
中封装了一些动态调整学习率的方法,可以参考如下API:

2.2.2 自定义scheduler

虽然PyTorch官方已经提供了很多API,但是实际情况可能需要自定义学习率调整策略,这里通常使用自定义函数实现即可,举例如下:

def adjust_learning_rate(optimizer, epoch):
    lr = args.lr / (0.1 ** (epoch // 30))
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr

optimizer = torch.optim.SGD(model.parameters(), lr=args.lr, momentum=0.9)

for epoch in range(10):
    train()
    validate()
    adjust_learning_rate(optimizer, epoch)

3. 模型微调-torchvision

随着深度学习的发展,模型的参数越来越大,因此通常情况下需要的数据集也越来越大,因为越大的模型对数据量的要求越大,过拟合无法避免。

那么如果把适用于ImageNet数据集的复杂模型在一个数据量较小的图像数据集(如识别不同种类椅子的数据集,后面也以椅子数据集为例)中训练,可能就会出现过拟合,同时因为数据量有限,最终训练得到的模型的精度也可能达不到实用的要求。

这里有两种解决方案:

  • 收集更多的数据,但是该方法成本过高。例如,在收集ImageNet数据集时,研究人员花费了数百万美元的研究经费。
  • 应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet数据集的图像大多跟椅子无关,但在该数据集上训练的模型可以抽取比较通用的图像特征,这些类似的特征(如边缘、纹理、形状和物体组成等)对于识别椅子可能同样有效。

迁移学习的一大应用场景是模型微调(finetune)。简单来说,就是先找到一个同类的别人训练好的模型,把训练好的模型拿过来,换成自己的数据,通过训练调整一下参数。

在PyTorch中提供了许多预训练好的网络模型(VGG、ResNet系列、mobilenet系列等),都是PyTorch官方在相应的大型数据集训练好的。

因此学习如何进行模型微调,可以方便我们快速使用预训练模型完成自己的任务。

3.1 模型微调的流程
  1. 在源数据集(如ImageNet数据集)上预训练一个神经网络模型,即源模型
  2. 创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。这里假设这些模型参数包含了源数据集学习到的知识,且这些知识适用于目标数据集。还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型不予采用。
  3. 为目标模型添加一个输出大小为目标数据集类别个数的输出层,并随机初始化该层的模型参数
  4. 在目标训练集上训练目标模型,这里只重新训练输出层,其余层的参数都是基于源模型的参数微调得到的。

整体流程如下图:

3.2 使用已有模型结构

这里以torchvision中的常见模型为例,列出了如何在图像分类任务中使用PyTorch提供的常见模型结构和参数。对于其他任务和网络结构,使用方式类似:

  • 实例化网络:以下是torchvision中的常用模型
import torchvision.models as models

resnet18 = models.resnet18()
# resnet18 = models.resnet18(pretrained=False)  # equal to above one line
alexnet = models.alexnet()
vgg16 = models.vgg16()
squeezenet = models.squeezenet1_0()
densenet = models.densenet161()
inception = models.inception_v3()
googlenet = models.googlenet()
shufflenet = models.shufflenet_v2_x1_0()
mobilenet_v2 = models.mobilenet_v2()
mobilenet_v3_large = models.mobilenet_v3_large()
mobilenet_v3_small = models.mobilenet_v3_small()
resnext50_32x4d = models.resnext50_32x4d()
wide_resnet50_2 = models.wide_resnet50_2()
mnasnet = models.mnasnet1_0()

  • 传递pretrained参数

通过True或False来决定是否使用预训练好的权重,默认为False。因此如果我们想使用到模型微调,即使用预训练好的模型,需要设置pretrained为True

注:

  • PyTorch模型的扩展通常为.pt或.pth,程序运行时会首先检查默认路径是否有下载的模型权重,一旦模型被下载,下次加载就不需要下载了。
  • 一般情况下,预训练模型的下载会比较慢,可以查看模型的url(查看模型的
    model_urls
    )手动下载,下载好的模型通常放在用户根目录下的.cache文件夹。同时也可以通过torch.utils.model_zoo.load_url()设置权重的下载地址。
  • 也可以将权重下载道同文件夹下,将参数加载到网络
self.model = models.resnet50(pretrained=False)
self.model.load_state_dict(torch.load('./model/resnet50-19c8e357.pth'))

  • 如果下载模型出错或暂停,最好手动删除对应路径下的权重文件,否则可能会报错
3.3 训练特定层

默认情况下,参数的属性
.require_grad=True
,如果从头开始训练或微调不需要注意这里。但是如果我们正在提取特征并且只想为新初始化的层计算梯度,其它参数不变,则需要设置
.require_grads=False
来冻结部分层。在PyTorch官方中提供了一个例程:

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

这里以resnet18为例将1000类分为4类,仅改变最后一层的模型参数,不改变特征提取的模型参数。

因此需要将除了输出层外的所有层都冻结,这里需注意:需要先冻结模型参数的梯度,再对模型输出部分的全连接层进行修改,这样修改后的全连接层的参数就是可计算梯度的。如下:

import torchvision.models as models
# 冻结参数的梯度
feature_extract = True
model = models.resnet18(pretrained=True)
set_parameter_requires_grad(model, feature_extract)
# 修改模型
num_ftrs = model.fc.in_features
model.fc = nn.Linear(in_features=num_ftrs, out_features=4, bias=True)

之后在训练过程中,model仍会进行梯度回传,但是参数更新只会发生在fc层。

3.4 基于timm的模型微调

除了使用
torchvision.models
进行预训练,还有一个常见的预训练模型库,叫做
timm
,里面提供了许多计算机是觉得SOTA模型,可以当作torchvision的扩充版本,且模型的准确度也较高。因此这里会基于timm讲解模型预训练。

3.4.1 timm的安装
pip install timm
git clone https://github.com/rwightman/pytorch-image-models
cd pytorch-image-models && pip install -e .

3.4.2 查看预训练模型种类

References

  1. 深入浅出PyTorch第六章PyTorch进阶训练技巧
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值