pytorch_tricks
目录:
指定GPU编号
查看模型每层输出详情
梯度裁剪
扩展单张图片维度
独热编码
防止验证模型时爆显存
学习率衰减
冻结某些层的参数
对不同层使用不同学习率
模型相关操作
Pytorch内置one hot函数
1、指定GPU编号
- 设置当前使用的GPU设备仅为0号设备,设备名称为
/gpu:0:os.environ["CUDA_VISIBLE_DEVICES"] = "0"
- 设置当前使用的GPU设备为0,1号两个设备,名称依次为
/gpu:0、/gpu:1:os.environ["CUDA_VISIBLE_DEVICES"] = "0,1"
,根据顺序表示优先使用0号设备,然后使用1号设备。
指定GPU的命令需要放在和神经网络相关的一系列操作的前面。
2、查看模型每层输出详情
Keras有一个简洁的API来查看模型的每一层输出尺寸,这在调试网络时非常有用。现在在PyTorch中也可以实现这个功能。
使用很简单,如下用法:
1 | from torchsummary import summary |
input_size
是根据你自己的网络模型的输入尺寸进行设置。
3、梯度裁剪(Gradient Clipping)
1 | import torch.nn as nn |
nn.utils.clip_grad_norm_
的参数:
- parameters – 一个基于变量的迭代器,会进行梯度归一化
- max_norm – 梯度的最大范数
- norm_type – 规定范数的类型,默认为L2
4、扩展单张图片维度
因为在训练时的数据维度一般都是(batch_size, c, h, w)
,而在测试时只输入一张图片,所以需要扩展维度,扩展维度有多个方法:
方法一
1 | import cv2 |
方法二
1 | import cv2 |
方法三
1 | import cv2 |
tensor.unsqueeze(dim)
:扩展维度,dim指定扩展哪个维度。tensor.squeeze(dim)
:去除dim指定的且size为1的维度,维度大于1时,squeeze()不起作用,不指定dim时,去除所有size为1的维度。
5、独热编码
在PyTorch中使用交叉熵损失函数的时候会自动把label转化成onehot,所以不用手动转化,而使用MSE需要手动转化成onehot编码。
1 | import torch |
注:第11条有更简单的方法。
6、防止验证模型时爆显存
验证模型时不需要求导,即不需要梯度计算,关闭autograd,可以提高速度,节约内存。如果不关闭可能会爆显存。
1 | with torch.no_grad(): |
Pytorch 训练时无用的临时变量可能会越来越多,导致 out of memory ,可以使用下面语句来清理这些不需要的变量。
官网 上的解释为:
Releases all unoccupied cached memory currently held by the caching allocator so that those can be used in other GPU application and visible innvidia-smi.
torch.cuda.empty_cache()
意思就是PyTorch的缓存分配器会事先分配一些固定的显存,即使实际上tensors并没有使用完这些显存,这些显存也不能被其他应用使用。这个分配过程由第一次CUDA内存访问触发的。
而 torch.cuda.empty_cache()
的作用就是释放缓存分配器当前持有的且未占用的缓存显存,以便这些显存可以被其他GPU应用程序中使用,并且通过nvidia-smi
命令可见。注意使用此命令不会释放tensors占用的显存。
对于不用的数据变量,Pytorch 可以自动进行回收从而释放相应的显存。
7、学习率衰减
1 | import torch.optim as optim |
关键语句为lr_scheduler.StepLR(optimizer, 10, 0.1)
,表示每过10个epoch,学习率乘以0.1。
8、冻结某些层的参数
参考:Pytorch 冻结预训练模型的某一层(https://www.zhihu.com/question/311095447/answer/589307812)
在加载预训练模型的时候,我们有时想冻结前面几层,使其参数在训练过程中不发生变化。
我们需要先知道每一层的名字,通过如下代码打印:
1 | net = Network() # 获取自定义网络结构 |
假设前几层信息如下:
1 | name: cnn.VGG_16.convolution1_1.weight, grad: True |
后面的True表示该层的参数可训练,然后我们定义一个要冻结的层的列表:
1 | no_grad = [ |
冻结方法如下:
1 | net = Net.CTPN() # 获取网络结构 |
冻结后我们再打印每层的信息:
1 | name: cnn.VGG_16.convolution1_1.weight, grad: False |
可以看到前两层的weight和bias的requires_grad
都为False,表示它们不可训练。
最后在定义优化器时,只对requires_grad为True的层的参数进行更新。
1 | optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01) |
9、对不同层使用不同学习率
我们对模型的不同层使用不同的学习率。
还是使用这个模型作为例子:
1 | net = Network() # 获取自定义网络结构 |
对 convolution1 和 convolution2 设置不同的学习率,首先将它们分开,即放到不同的列表里:
1 | conv1_params = [] |
我们将模型划分为两部分,存放到一个列表里,每部分就对应上面的一个字典,在字典里设置不同的学习率。当这两部分有相同的其他参数时,就将该参数放到列表外面作为全局参数,如上面的weight_decay
。
也可以在列表外设置一个全局学习率,当各部分字典里设置了局部学习率时,就使用该学习率,否则就使用列表外的全局学习率。
10、模型相关操作
PyTorch 中模型的使用参考(https://zhuanlan.zhihu.com/p/73893187)
一、保存加载模型基本用法
(1) 保存加载整个模型
保存整个网络模型(网络结构+权重参数)。
1 | torch.save(model, 'net.pkl') |
直接加载整个网络模型(可能比较耗时)。
1 | model = torch.load('net.pkl') |
(2) 只保存加载模型参数
只保存模型的权重参数(速度快,占内存少)。
1 | torch.save(model.state_dict(), 'net_params.pkl') |
因为我们只保存了模型的参数,所以需要先定义一个网络对象,然后再加载模型参数。
1 | # 构建一个网络结构 |
保存模型进行推理测试时,只需保存训练好的模型的权重参数,即推荐第二种方法
。
主要用法就是上面这些,接下来讲一下PyTorch中保存加载模型内部的一些原理,以及我们可能会遇到的一些特殊的需求。
二、保存加载自定义模型
上面保存加载的 net.pkl 其实一个字典,通常包含如下内容:
- 网络结构:输入尺寸、输出尺寸以及隐藏层信息,以便能够在加载时重建模型。
- 模型的权重参数:包含各网络层训练后的可学习参数,可以在模型实例上调用
state_dict()
方法来获取,比如前面介绍只保存模型权重参数时用到的model.state_dict()
。 - 优化器参数:有时保存模型的参数需要稍后接着训练,那么就必须保存优化器的状态和所其使用的超参数,也是在优化器实例上调用 state_dict() 方法来获取这些参数。
- 其他信息:有时我们需要保存一些其他的信息,比如 epoch,batch_size 等超参数。
知道了这些,那么我们就可以自定义需要保存的内容,比如:
1 | # saving a checkpoint assuming the network class named ClassNet |
上面的 checkpoint 是个字典,里面有4个键值对,分别表示网络模型的不同信息。
然后我们要加载上面保存的自定义的模型:
1 | def load_checkpoint(filepath): |
如果加载模型只是为了进行推理测试,则将每一层的 requires_grad
置为 False,即固定这些权重参数;还需要调用 model.eval()
将模型置为测试模式,主要是将 dropout 和 batch normalization 层进行固定,否则模型的预测结果每次都会不同。
如果希望继续训练,则调用model.train()
,以确保网络模型处于训练模式。
state_dict()
也是一个Python字典对象,model.state_dict()
将每一层的可学习参数映射为参数矩阵,其中只包含具有可学习参数的层(卷积层、全连接层等)。
比如下面这个例子:
1 | # Define model |
输出为:
1 | Model's state_dict: |
可以看到 model.state_dict()
保存了卷积层,BatchNorm层和最大池化层的信息;而 optimizer.state_dict()
则保存的优化器的状态和相关的超参数。
三、跨设备保存加载模型
(1) 在 CPU 上加载在 GPU 上训练并保存的模型(Save on GPU, Load on CPU):
1 | device = torch.device('cpu') |
map_location
:a function, torch.device, string or a dict specifying how to remap storage locations
令 torch.load()
函数的 map_location
参数等于torch.device('cpu')
即可。 这里令map_location
参数等于 ‘cpu’ 也同样可以。
(2) 在 GPU 上加载在 GPU 上训练并保存的模型(Save on GPU, Load on GPU):
1 | device = torch.device("cuda") |
在这里使用 map_location
参数不起作用,要使用model.to(torch.device("cuda"))
将模型转换为CUDA优化的模型。
还需要对将要输入模型的数据调用 data = data.to(device)
,即将数据从CPU转移到GPU。请注意,调用 my_tensor.to(device)
会返回一个my_tensor
在 GPU 上的副本,它不会覆盖 my_tensor
。因此需要手动覆盖张量:my_tensor = my_tensor.to(device)
。
(3) 在 GPU 上加载在 GPU 上训练并保存的模型(Save on CPU, Load on GPU)
1 | device = torch.device("cuda") |
当加载包含GPU tensors的模型时,这些tensors 会被默认加载到GPU上,不过是同一个GPU设备。
当有多个GPU设备时,可以通过将map_location
设定为 cuda:device_id
来指定使用哪一个GPU设备,上面例子是指定编号为0的GPU设备。
其实也可以将torch.device("cuda")
改为 torch.device("cuda:0")
来指定编号为0的GPU设备。
最后调用 model.to(torch.device('cuda'))
来将模型的tensors转换为 CUDA tensors。
下面是PyTorch官方文档上的用法,可以进行参考:
1 | 'tensors.pt') torch.load( |
四、CUDA 的用法
在PyTorch中和GPU相关的几个函数:
1 | import torch |
输出为:
1 | True |
有时我们需要把数据和模型从cpu移到gpu中,有以下两种方法:
1 | use_cuda = torch.cuda.is_available() |
个人比较习惯第二种方法,可以少一个 if 语句。而且该方法还可以通过设备号指定使用哪个GPU设备,比如使用0号设备:
1 | device = torch.device("cuda:0" if use_cuda else "cpu") |
11、Pytorch内置one_hot函数
Pytorch 1.1后,one_hot可以直接用torch.nn.functional.one_hot
。然后我将Pytorch升级到1.2版本,试用了下 one_hot 函数,确实很方便。
具体用法如下:
1 | import torch.nn.functional as F |
F.one_hot
会自己检测不同类别个数,生成对应独热编码。我们也可以自己强行指定类别数:
1 | tensor = torch.arange(0, 5) % 3 # tensor([0, 1, 2, 0, 1]) |
升级 Pytorch (cpu版本)的命令:conda install pytorch torchvision -c pytorch
(希望Pytorch升级不会影响项目代码)
- 本文作者: Jason
- 本文链接: https://caicaijason.github.io/2019/11/21/PyTorch-Tricks集合/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!