Datawhale-深入浅出pytorch进阶训练技巧

目录

6.1 自定义损失函数

6.1.1 以函数方式定义

6.1.2 以类方式定义

6.2 动态调整学习率

6.2.1 使用官方scheduler

6.2.2 自定义scheduler

6.3 模型微调-torchvision

6.3.1 模型微调的流程

6.3.2 使用已有模型结构

6.3.3 训练特定层

6.3 模型微调 - timm

timm的安装

如何查看预训练模型种类

使用和修改预训练模型

模型的保存

6.4 半精度训练

6.4.1 半精度训练的设置

6.5 数据增强-imgaug

6.5.1 imgaug简介和安装

6.5.1.1 imgaug简介

6.5.1.2 imgaug的安装

6.5.2 imgaug的使用

单张图片处理

 对批次图片进行处理

对不同大小的图片进行处理

6.5.3 imgaug在PyTorch的应用

6.6 使用argparse进行调参

6.6.1 argparse简介

6.6.2 argparse的使用

6.6.3 更加高效使用argparse修改超参数

PyTorch模型定义与进阶训练技巧

Point 1:模型定义方式

Point 2:利用模型块快速搭建复杂网络(汽车识别) 

Point 3:模型修改

使用Carvana数据集,实现一个基本的U-Net训练过程

Point 5:自定义损失函数

Point 6:动态调整学习率

Point 7:模型微调

Point 8:半精度训练


6.1 自定义损失函数

torch.nn模块有许多常用的损失函数:MSELoss,L1Loss,BCELoss。但是自定义损失函数的技能是尤为重要的

6.1.1 以函数方式定义

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

6.1.2 以类方式定义

Loss函数部分继承自_loss, 部分继承自_WeightedLoss, 而_WeightedLoss继承自_loss_loss继承自 nn.Module。可以当作神经网络的一层,损失函数类需要继承自nn.Module类。

Dice Loss

 实现代码:

class DiceLoss(nn.Module):
    def __init__(self,weight=None,size_average=True):
        super(DiceLoss,self).__init__()
        
    def forward(self,inputs,targets,smooth=1):
        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)  
        return 1 - dice

# 使用方法    
criterion = DiceLoss()
loss = criterion(input,targets)

BCE-Dice Loss

Jaccard

Intersection over Union (IoU) Loss

Focal Loss

class DiceBCELoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(DiceBCELoss, self).__init__()

    def forward(self, inputs, targets, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()                     
        dice_loss = 1 - (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)  
        BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
        Dice_BCE = BCE + dice_loss
        
        return Dice_BCE
--------------------------------------------------------------------
    
class IoULoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(IoULoss, self).__init__()

    def forward(self, inputs, targets, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()
        total = (inputs + targets).sum()
        union = total - intersection 
        
        IoU = (intersection + smooth)/(union + smooth)
                
        return 1 - IoU
--------------------------------------------------------------------
    
ALPHA = 0.8
GAMMA = 2

class FocalLoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(FocalLoss, self).__init__()

    def forward(self, inputs, targets, alpha=ALPHA, gamma=GAMMA, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
        BCE_EXP = torch.exp(-BCE)
        focal_loss = alpha * (1-BCE_EXP)**gamma * BCE
                       
        return focal_loss

自定义损失函数,涉及到数学运算,全程使用PyTorch提供的张量计算接口,这样不需要实现自动求导功能,并直接调用cuda。

6.2 动态调整学习率

学习速率设置过小,会极大降低收敛速度,增加训练时间;

学习率太大,可能导致参数在最优解两侧来回振荡。

选定了一个合适的学习率后,经过许多轮的训练后,可能会出现准确率震荡或loss不再下降等情况,说明当前学习率已不能满足模型调优的需求。

Scheduler:通过适当的学习率衰减策略改善这种现象,提高精度。

6.2.1 使用官方scheduler

代码解释:

# 选择一种优化器
optimizer = torch.optim.Adam(...) 
# 选择上面提到的一种或多种动态调整学习率的方法
scheduler1 = torch.optim.lr_scheduler.... 
scheduler2 = torch.optim.lr_scheduler....
...
schedulern = torch.optim.lr_scheduler....
# 进行训练
for epoch in range(100):
    train(...)
    validate(...)
    optimizer.step()
    # 需要在优化器参数更新之后再动态调整学习率
	scheduler1.step() 
	...
    schedulern.step()

 使用官方的torch.optim.lr_scheduler,将scheduler.step()放在optimizer.step()后面使用。

6.2.2 自定义scheduler

自定义学习率调整策略:自定义函数adjust_learning_rate来改变param_grouplr的值

学习率每30轮下降为原来的1/10:

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

adjust_learning_rate函数的定义,实现学习率的动态变化:

def adjust_learning_rate(optimizer,...):
    ...
optimizer = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
for epoch in range(10):
    train(...)
    validate(...)
    adjust_learning_rate(optimizer,epoch)

6.3 模型微调-torchvision

开源模型都是在较大数据集上进行训练:

Imagenet-1k,Imagenet-11k,ImageNet-21k

越大的模型对数据量的要求越大,过拟合无法避免

应用迁移学习(transfer learning):源数据集学到的知识迁移到目标数据集。例如,ImageNet数据集图像大多跟椅子无关,但该数据集上训练的模型可以抽取较通用的图像特征,从而能够帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别椅子也可能同样有效。

模型微调(finetune):找到一个同类的别人训练好的模型,把别人现成的训练好了的模型拿过来,换成自己的数据,通过训练调整一下参数。 

预训练好的网络模型:

VGG,ResNet系列,mobilenet系列

6.3.1 模型微调的流程

  1. 在源数据集(如ImageNet数据集)上预训练一个神经网络模型。

  2. 创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。

  3. 为目标模型添加一个输出⼤小为⽬标数据集类别个数的输出层,并随机初始化该层的模型参数。

  4. 在目标数据集上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。

 

6.3.2 使用已有模型结构

实例化网络

import torchvision.models as models
resnet18 = models.resnet18()
# resnet18 = models.resnet18(pretrained=False)  等价于与上面的表达式
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来决定是否使用预训练好的权重

pretrained = False,不使用预训练得到的权重,

pretrained = True,使用在一些数据集上预训练得到的权重。

import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
alexnet = models.alexnet(pretrained=True)
squeezenet = models.squeezenet1_0(pretrained=True)
vgg16 = models.vgg16(pretrained=True)
densenet = models.densenet161(pretrained=True)
inception = models.inception_v3(pretrained=True)
googlenet = models.googlenet(pretrained=True)
shufflenet = models.shufflenet_v2_x1_0(pretrained=True)
mobilenet_v2 = models.mobilenet_v2(pretrained=True)
mobilenet_v3_large = models.mobilenet_v3_large(pretrained=True)
mobilenet_v3_small = models.mobilenet_v3_small(pretrained=True)
resnext50_32x4d = models.resnext50_32x4d(pretrained=True)
wide_resnet50_2 = models.wide_resnet50_2(pretrained=True)
mnasnet = models.mnasnet1_0(pretrained=True)
  1. 通常PyTorch模型的扩展为.pt.pth,程序运行时会首先检查默认路径中是否有已经下载的模型权重,一旦权重被下载,下次加载就不需要下载了。

  2. 一般情况下预训练模型的下载会比较慢,我们可以直接通过迅雷或者其他方式去 这里 查看自己的模型里面model_urls,然后手动下载,预训练模型的权重在LinuxMac的默认下载路径是用户根目录下的.cache文件夹。在Windows下就是C:\Users\<username>\.cache\torch\hub\checkpoint。我们可以通过使用 torch.utils.model_zoo.load_url()设置权重的下载地址。

  3. 如果觉得麻烦,还可以将自己的权重下载下来放到同文件夹下,然后再将参数加载网络。

self.model = models.resnet50(pretrained=False)
self.model.load_state_dict(torch.load('./model/resnet50-19c8e357.pth'))

     4.  中途强行停止下载,对应路径下将权重文件删除干净,要不然可能会报错。

6.3.3 训练特定层

在默认情况下,参数的属性.requires_grad = True,从头开始训练或微调不需要注意这里。

正在提取特征并且只想为新初始化的层计算梯度,其他参数不进行改变。需要通过设置requires_grad = False来冻结部分层。

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层。通过设定参数的requires_grad属性,完成指定训练模型的特定层的目标,对实现模型微调非常重要。

6.3 模型微调 - timm

Ross Wightman创建。提供许多计算机视觉的SOTA模型,可以当作是torchvision的扩充版本,并且里面的模型在准确度上也较高。

  • Github链接:https://github.com/rwightman/pytorch-image-models

  • 官网链接:https://fastai.github.io/timmdocs/ https://rwightman.github.io/pytorch-image-models/

timm的安装

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

如何查看预训练模型种类

timm.list_models()查看timm提供的预训练模型。

import timm
avail_pretrained_models = timm.list_models(pretrained=True)
len(avail_pretrained_models)

Resnet系列就包括了ResNet18,50,101等模型,我们可以在timm.list_models()传入想查询的模型名称(模糊查询)

all_densnet_models = timm.list_models("*densenet*")
all_densnet_models

返回:

['densenet121',
 'densenet121d',
 'densenet161',
 'densenet169',
 'densenet201',
 'densenet264',
 'densenet264d_iabn',
 'densenetblur121d',
 'tv_densenet121']

查看模型的具体参数,可以通过访问模型的default_cfg属性进行查看

model = timm.create_model('resnet34',num_classes=10,pretrained=True)
model.default_cfg
{'url': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnet34-43635321.pth',
 'num_classes': 1000,
 'input_size': (3, 224, 224),
 'pool_size': (7, 7),
 'crop_pct': 0.875,
 'interpolation': 'bilinear',
 'mean': (0.485, 0.456, 0.406),
 'std': (0.229, 0.224, 0.225),
 'first_conv': 'conv1',
 'classifier': 'fc',
 'architecture': 'resnet34'}

使用和修改预训练模型

通过timm.create_model()进行模型创建,通过传入参数pretrained=True,使用预训练模型。

也可以使用跟torchvision里面的模型一样方法查看模型参数,类型

import timm
import torch

model = timm.create_model('resnet34',pretrained=True)
x = torch.randn(1,3,224,224)
output 
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值