本文目录:

  • 1. 自定义损失函数
  • 2. 动态调整学习率
  • 3. 模型微调-torchvision
    • 3.1 使用已有模型
    • 3.2 训练特定层

1. 自定义损失函数

虽然pytorch提供了许多常用的损失函数,但很多时候我们需要自定义一些新的损失函数来满足某些特定任务的需求,这时就需要我们自己写损失函数了。

pytorch 自定义损失函数主要有两种方式:

  • 自定义函数:这种方法比较简单,写个函数即可。但需要注意的是输入输出的变量都得是torch.tensor类型的数据,且尽量采用torch自带的函数计算loss,否则可能会无法求导。
import torchdef my_loss(x, y):loss = torch.mean((x - y)**2)return loss
  • 自定义类:这种方式虽然需要提前实例化类,但更为常用,显得专业。自定义类继承于nn.Module,相当于定义了一个网络层,可以维护状态和存储参数信息。自定义类有两个关键要点,一是定义初始化方法(init),二是定义前向计算方式(forward)
    以下给出一个我自己写的分位数回归损失函数——pinball loss:
import torch.nn as nn
import torchclass PinballLoss(nn.Module):""" Pinball loss at all confidence levels."""def __init__(self, quantiles):""" InitializeParameters----------quantiles : the list of quantiels, [1, n], list or ndarray. For example, quantiles = [0.025 0.05  0.075 0.925 0.95  0.975]."""super(PinballLoss, self).__init__()self.quantiles = quantilesdef forward(self, preds, target):""" Compute the pinball lossParameters----------preds : the predictions at all quantiles, torch.tensor, [n, len(quantiles)].target : the ground truth of y, torch.tensor, [n, 1].Returns-------loss : the value of the pinball loss."""assert preds.shape[0] == target.shape[0]if target.ndim == 1:target = target.unsqueeze(dim=1)losses = []for i, q in enumerate(self.quantiles):errors = target[:, 0] - preds[:, i]losses.append(torch.max((q - 1) * errors, q * errors).unsqueeze(1))loss = torch.mean(torch.sum(torch.cat(losses, dim=1), dim=1))return loss

2. 动态调整学习率

学习率(learning rate)本质上就是优化方法里的搜索步长,不同优化方法之间最大的差异在于他们的搜索方向和搜索步长不同。深度学习通过不断更新优化参数来减小loss,该过程的本质其实也是最优化里寻找全局最优解(最小的loss)的过程,只是实际情况中经常无法寻找到全局最优解或成本太高,往往采用表现还行的局部最优解。

那么,为什么要动态调整学习率呢?来看下图


(图源见水印)

我们的目标是到达这条曲线的“谷底”(局部最优解),如果学习率太大,会经常跨过这个谷底,难以收敛到局部最优解,导致模型表现一般;如果学习率太小,虽然最终也能到达谷底,但是这个过程耗时漫长,等得花都谢了。这说明太大或太小的学习率都不合适,但这个合适的学习率到底多大合适呢?我们无从得知,只能凭经验判断。神经网络参数的初始权重不同,且每一次优化过程的初始起点、搜索方向都可能不同,这就导致每一次跑出来的结果都有所不同,论文里的完美结果经常难以复现(指定随机数种子可以改善),这也是深度学习有时候像玄学、炼丹的原因。尽管如此,在实际工作中我们只要不断调参得到一个较为满意的结果,得益于神经网络的强大拟合能力,其结果往往就已经超越了许多传统方法。

咳咳,扯远了。在实际的模型训练中,我们一般采用动态调整学习率,也称学习率衰减的策略来加速算法收敛。其思想为:先取一个较大的学习率加速收敛,然后随着遍历次数(epoch)的增加逐步减小学习率,防止跳出局部最优解。

pytorch官方在torch.optim.lr_schduler里提供了许多学习率衰减的方法,这里列出常用的几种方式:

Scheduler 说明
StepLR 等间隔调整学习率,如每隔10个epoch衰减一次学习率
MultiStepLR 多间隔调整学习率,如epoch分别在10,30,70时衰减一次学习率
ExponentialLR 指数衰减学习率,每训练一个epoch调整一次学习率,即lr∗=lr×gammaepochlr^*=lr \times gamma^{epoch}lr∗=lr×gammaepoch
CosineAnnealingLR 余弦退火函数调整学习率,随着epoch增加,学习率呈余弦函数状衰减

更多学习率衰减方法参考pytorch官网。

以下给出一个学习率衰减的模板:

import torchvision.models as models
from torch.optim import SGD
from torch.optim.lr_scheduler import ExponentialLRmodel = models.vgg16()  #可更换为任意模型
optimizer = SGD(model, 0.1)  #参数优化器
scheduler = ExponentialLR(optimizer, gamma=0.9)  #学习率衰减控制器for epoch in range(20):for input, target in dataset:optimizer.zero_grad()output = model(input)loss = loss_fn(output, target)loss.backward()optimizer.step()scheduler.step()

以上是pytorch官网给出的方法。当然,如果官方给的方法无法满足我们的需求,那我们可以自己写一个函数来调整学习率。

假设我们现在需要学习率每隔30轮下降为原来的1/10,假设官网没有符合我们需求的schduler(实际上StepLR符合),那我们可以自己写一个函数:

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)

3. 模型微调-torchvision

深度学习和计算机算力的不断发展催生出了许多包含巨量参数的模型,如GPT-3等,这些大模型花费了创造它们的公司大量的算力、时间成本,我们普通人想要训练出这样的大模型困难重重:

  • 缺少数据。这些大模型通常是在大量数据的基础上训练出来的,动不动就几百G几百T的,普通电脑难以存下这么多数据。
  • 缺少算力。大模型通常是用服务器或数据中心的几百张NVIDIA显卡上跑出来的,光这硬件成本就得至少几百上千万,电费也不是一笔小数目(怎么有点像挖矿呢)。
  • 缺少技术(大佬请无视)。综上,想要训练出大模型,需要大量数据和显卡,要驱动模型在大数据和大量显卡上正常训练需要一点技术,普通人如果对这方面的知识接触少的话一时半会儿很难跑得起来模型。

那我们的普通人就没法用大模型了吗?答案是否定的,有许多已经训练好的模型已经开源分享出来了,比如torchvision.models里就有许多预训练好的模型,我们只需要下载下来就能用了。但这存在一个问题,假设我们要做的识别猫的种类,这些预训练好的模型通常是针对特定任务的,比如图像识别,他们采用的数据集里面包含猫的照片可能就只有一小部分,无法满足我们的需求。要解决这个问题,我们可以采用迁移学习(transfer learning)

迁移学习可以将源数据集上学到的知识迁移到目标数据集上。例如,虽然ImageNet数据集的图像里包含的猫的图片不是很多,但在该数据集上训练的模型可以抽取比较通用的图像特征,从而能够帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别猫也可能同样有效。

迁移学习的一大应用场景是模型微调(finetune)。简单来说,就是我们先找到一个同类的别人训练好的模型,把别人现成的训练好了的模型拿过来,换成自己的数据,通过训练调整一下参数。 在PyTorch中提供了许多预训练好的网络模型(VGG,ResNet系列,mobilenet系列…),这些模型都是PyTorch官方在相应的大型数据集训练好的。

模型微调的主要流程是:复制预训练好的模型,微调除输出层外的其它层参数,修改输出层参数并随机初始化,在目标数据集上上训练模型。

3.1 使用已有模型

torchvision.models为例,里面列出了许多用于图像分类任务的模型,我们下载导入一下即可使用。

但要注意的是,Windows环境下 torchvision 下载的预训练模型保存在C:\Users\<username>\.cache,这一点对于C盘空间较小的用户来说很不友好,也不符合使用习惯,如果.cahe里的文件不小心被清理掉了还得重新下载一遍。为此,我们需要修改一下预训练模型的保存路径,以下提供一种修改方式:

import torchvision.models as models
import os
os.environ['TORCH_HOME']='E:\pytorch\Data'  #修改模型保存路径,下载好后重新加载模型就会在这个目录下加载了# pretrained = True表示下载使用预训练得到的权重,False表示不使用预训练得到的权重
resnet34 = models.resnet34(pretrained=True)

3.2 训练特定层

在默认情况下,参数的属性.requires_grad = True,如果我们从头开始训练或微调不需要注意这里。但如果我们正在提取特征并且只想为新初始化的层计算梯度,其他参数不进行改变。那我们就需要通过设置requires_grad = 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层。通过设定参数的requires_grad属性,我们完成了指定训练模型的特定层的目标,这对实现模型微调非常重要。

参考资料:
[1] Datawhale_深入浅出pytorch
[2] https://blog.csdn.net/qq_27825451/article/details/95165265
[3] pytorch官方文档
[4] https://www.jianshu.com/p/26a7dbc15246
[5] https://blog.csdn.net/yanxiangtianji/article/details/112256618

【pytorch笔记】(五)自定义损失函数、学习率衰减、模型微调相关推荐

  1. pytorch两种常用的学习率衰减方法

    阶梯式衰减 torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1) 每个训练step_size个 ...

  2. PyTorch笔记: GPU上训练的模型加载到CPU/错误处理Attempting to deserialize object on a CUDA device but torch.cuda.is_a

    我之前在GPU上训练了一个模型,同时把模型的参数记录在resnet18_ultimate.pkl上 在本地的CPU上,我想把参数加载,于是一开始我是这么写代码的: import torch impor ...

  3. 【深度学习】(10) 自定义学习率衰减策略(指数、分段、余弦),附TensorFlow完整代码

    大家好,今天和大家分享一下如何使用 TensorFlow 自定义 指数学习率下降.阶梯学习率下降.余弦学习率下降 方法,并使用 Mnist数据集验证自定义的学习率下降策略. 创建的自定义学习率类方法, ...

  4. Pytorch 学习率衰减 之 余弦退火与余弦warmup 自定义学习率衰减scheduler

    学习率衰减,通常我们英文也叫做scheduler.本文学习率衰减自定义,通过2种方法实现自定义,一是利用lambda,另外一个是继承pytorch的lr_scheduler import math i ...

  5. pytorch优化器,学习率衰减学习笔记

    目录 LAMB优化器 AdaBelief 优化器 Adam和SGD的结合体 lookahead Ranger RAdam和LookAhead合二为一 余弦退火学习率衰减

  6. pytorch 学习率代码_DL知识拾贝(Pytorch)(五):如何调整学习率

    知识导图 学习率对于深度学习是一个重要的超参数,它控制着基于损失梯度调整神经网络权值的速度,大多数优化算法(SGD.RMSprop.Adam)对其都有所涉及.学习率过下,收敛的太慢,网络学习的也太慢: ...

  7. Pytorch 学习率衰减方法

    Pytorch 学习率衰减方法 1.什么是学习率衰减 2.使用库函数进行调整 2.1.有序调整 2.1.1等间隔调整学习率 2.1.2.多间隔调整学习率 2.1.3.指数衰减调整学习率 Exponen ...

  8. asp文本框输入控制是5的倍数_DL知识拾贝(Pytorch)(五):如何调整学习率

    知识导图 学习率对于深度学习是一个重要的超参数,它控制着基于损失梯度调整神经网络权值的速度,大多数优化算法(SGD.RMSprop.Adam)对其都有所涉及.学习率过下,收敛的太慢,网络学习的也太慢: ...

  9. 垃圾分类、EfficientNet模型B0~B7、Rectified Adam(RAdam)、Warmup、带有Warmup的余弦退火学习率衰减

    日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) 垃圾分类.EfficientNet模型.数据增强(ImageD ...

最新文章

  1. windows10中git 的下载安装
  2. 2022就业新风向:上海新发职位居首,北京招聘薪资最高
  3. ESP32-S模块转接板设计与实现
  4. 每日一皮:高级开发员 VS 菜鸟开发员
  5. SVN服务端的搭建及客户端连接(云上搭建服务端)
  6. insightface和facenet效果+性能比较
  7. android p版本 字符串常量池,Android OOM 问题
  8. 信息学奥赛一本通 2023:【例4.8】数据统计
  9. RabbitMQ基本管理(下)
  10. Live Meeting 音频视频使用端口设置
  11. halcon 仿射变换和区域跟随
  12. 7.3数据类型及内置方法(一)
  13. C# CRC16 modbus
  14. Android 自定义View 三板斧之三——重写View来实现全新控件
  15. 本科毕业论文EndNote格式
  16. Debian, Ubuntu 和 Linux Mint 中安装WPS
  17. Android 身份证号码校验
  18. oracle异步备份,网络存储导论第七章:异步数据复制容灾方式
  19. 应用计算机怎么弹ink,ink是什么文件,手把手教你怎么打开ink文件
  20. Windows 此电脑->设备与驱动器->图标管理

热门文章

  1. Windows下postgresql安装步骤(超级详细)
  2. 同包同名的类的时候,使用哪个,哪个优先
  3. java实现:使用递归编写一个程序,逆序输出一个非负整数。例如输入1234,输出4321(不含前导0)。
  4. 电脑超实用的六款软件推荐(一天一个实用小技巧)
  5. excel的frequency函数的用法和实例
  6. COGS 2. 旅行计划
  7. C语言之前的A、B语言又是什么?(计算机语言发展史)
  8. 百度快速收录WordPress新站方法汇总
  9. 用普通指令编写西门子S7-1200PLC的循环程序
  10. 行政事业单位收入体系分类