• 【PyTorch】模型进阶训练技巧


    技巧一:自定义损失函数

    • 方式一:直接定义函数
    def my_loss(output, target):
        loss = torch.mean((output - target)**2)
        return 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)
    

    该方法像一个网络一样定义,更常用。
    为什么要定义成类的形式而不是函数的形式?
    更多的损失函数定义可以看这里

    技巧二:动态调整学习率

    2.1 使用官方的Scheduler

    学习速率设置过小,会极大降低收敛速度,增加训练时间;学习率太大,可能导致参数在最优解两侧来回振荡。时我们就可以通过一个适当的学习率衰减策略来改善这种现象,提高我们的精度。这种设置方式在PyTorch中被称为scheduler,也是我们本节所研究的对象。

    • lr_scheduler.LambdaLR
    • lr_scheduler.MultiplicativeLR
    • lr_scheduler.StepLR
    • lr_scheduler.MultiStepLR
    • lr_scheduler.ExponentialLR
    • lr_scheduler.CosineAnnealingLR
    • lr_scheduler.ReduceLROnPlateau
    • lr_scheduler.CyclicLR
    • lr_scheduler.OneCycleLR
    • lr_scheduler.CosineAnnealingWarmRestarts
    # 选择一种优化器
    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()后面进行使用。

    2.2 自定义

    自定义函数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
    '
    运行
    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)
    

    技巧四:模型微调

    4.1 使用已有的模型

    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)
    

    4.2 对特定的层进行训练

    如果我们正在提取特征并且只想为新初始化的层计算梯度,其他参数不进行改变。那我们就需要通过设置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)
    

    技巧五:半精度训练

    GPU的性能主要分为两部分:算力和显存,前者决定了显卡计算的速度,后者则决定了显卡可以同时放入多少数据用于计算。batch size越大,训练效率越高。另外,有时候数据本身也比较大(比如3D图像、视频等),显存较小的情况下可能甚至batch size为1的情况都无法实现。因此,合理使用显存也就显得十分重要。

    PyTorch默认的浮点数存储方式用的是torch.float32,有时候并不需要这么精确,torch.float16也不会影响结果。由于数位减了一半,因此被称为“半精度”,具体如下图:

    在这里插入图片描述

    • import autocast
    from torch.cuda.amp import autocast
    
    
    • 模型设置
      在模型定义中,使用python的装饰器方法,用autocast装饰模型中的forward函数。关于装饰器的使用,可以参考这里:
    @autocast()   
    def forward(self, x):
        ...
        return x
    
    • 训练过程
      在训练过程中,只需在将数据输入模型及其之后的部分放入“with autocast():“即可:
    for x in train_loader:
    	x = x.cuda()
    	with autocast():
            output = model(x)
            ...
    

    半精度训练主要适用于数据本身的size比较大(比如说3D图像、视频等)。当数据本身的size并不大时(比如手写数字MNIST数据集的图片尺寸只有28*28),使用半精度训练则可能不会带来显著的提升。

    技巧六:使用argparse进行调参

    • 创建ArgumentParser()对象
    • 调用add_argument()方法添加参数
    • 使用parse_args()解析参数 在接下来的内容中,我们将以实际操作来学习argparse的使用方法。
    # demo.py
    import argparse
    
    # 创建ArgumentParser()对象
    parser = argparse.ArgumentParser()
    
    # 添加参数
    parser.add_argument('-o', '--output', action='store_true', 
        help="shows output")
    # action = `store_true` 会将output参数记录为True
    # type 规定了参数的格式
    # default 规定了默认值
    parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3') 
    
    parser.add_argument('--batch_size', type=int, required=True, help='input batch size')  
    # 使用parse_args()解析函数
    args = parser.parse_args()
    
    if args.output:
        print("This is some output")
        print(f"learning rate:{args.lr} ")
    
    

    在命令行使用python demo.py --lr 3e-4 --batch_size 32,可以看到以下输出:

    This is some output
    learning rate: 3e-4
    

    argparse的参数主要可以分为可选参数和必选参数,可选参数就跟我们的lr参数相类似,未输入的情况下会设置为默认值。必选参数就跟我们的batch_size参数相类似,当我们给参数设置required =True后,我们就必须传入该参数,否则就会报错。

  • 相关阅读:
    java毕业设计——基于java+Servlet+jsp的网上花店销售系统设计与实现(毕业论文+程序源码)——网上花店销售系统
    ThreadLocal及InheritableThreadLocal基本原理及注意项
    vue报错sockjs-node/info?t=或者报错info?t=
    vue3组件小结table案例经典!
    Visual Studio (VS2017)提交代码到Git服务器流程(GitCode)
    Prototype(原型模式)
    如何使用libavfilter库给pcm音频采样数据添加音频滤镜?
    ios打包,证书获取
    LeetCode刷题日记:135. 分发糖果
    C#基础知识
  • 原文地址:https://blog.csdn.net/qq_42251120/article/details/126955010