PyTorch 20. PyTorch技巧(持续更新)

查看模型每层输出详情

from torchsummary import summary
summary(your_model, input_size=(channels, H, W))

input_size是根据自己的网络模型的输入尺寸进行设置

梯度裁剪(Gradient Clipping)

import torch.nn as nn
outputs = model(data)
loss = loss_fn(outputs, target)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), max_norm=20, norm_type=2)
optimizer.step()

nn.utils.clip_grad_norm_的参数:

  • parameters 一个基于变量的迭代器,会进行梯度归一化
  • max_norm 梯度的最大范数
  • norm_type 规定范数的类型,默认为2

扩展单张图片的维度

view()实现

import cv2
import torch
image = cv2.imread(img_path)
image = torch.tensor(image)
print(image.size)

img = image.view(1, *image.size())
print(img.size())

np.newaxis实现

import cv2
import torch

image = cv2.imread(img_path)
print(image.shape)
img = image[np.newaxis, :, :, :]
print(img.shape)

unsqueeze()实现

import cv2
import torch

iamge = cv2.imread(img_path)
image = torch.tensor(image)
print(img.size())

img = image.unsqueeze(dim=0)
print(img.size())

独热编码

在PyTorch中使用交叉熵损失函数的时候会自动把label转换成onehot,所以不用手动转化,而使用MSE需要手动转化成onehot编码

import torch.nn.functional as F
import torch

tensor = torch.arange(0, 5)
one_hot = F.one_hot(tensor)
# 输出:
# tensor([[1, 0, 0],
#         [0, 1, 0],
#         [0, 0, 1],
#         [1, 0, 0],
#         [0, 1, 0]])

F.one_hot会自己检测不同类别个数,生成对应独热编码,我们也可以自己指定类别数:

tensor =  torch.arange(0, 5) % 3  # tensor([0, 1, 2, 0, 1])
one_hot = F.one_hot(tensor, num_classes=5)

# 输出:
# tensor([[1, 0, 0, 0, 0],
#         [0, 1, 0, 0, 0],
#         [0, 0, 1, 0, 0],
#         [1, 0, 0, 0, 0],
#         [0, 1, 0, 0, 0]])

防止验证模型时爆显存

验证模型时不需要求导,即不需要梯度计算,关闭autograd,可以提高速度,节约内存,如果不关闭可能会爆显存

with torch.no_grad():
	# 使用model进行预测的代码
	pass

对于使用torch.cuda.empty_cache()的原因,是因为随着Pytorch的训练,无用的临时变量可能会越来越多,导致out of memory

意思就是Pytorch的缓存分配器会事先分配一些固定的显存,即使实际上tensors并没有使用完这些显存,这些显存也不能被其他应用使用。这个分配过程由第一次CUDA内存访问触发的。

torch.cuda.empty_cache()的作用就是释放缓存分配器当前持有的且未占用的缓存显存,以便这些显存可以被其他GPU应用程序中使用,注意使用此命令不会释放tensors占用的显存。

监控工具

sudo apt-get install htop #监控内存(-d为更新频率)
htop -d=0.1
watch -n 0.1 nvidia-smi #监控显存(-n为更新频率,每0.1s更新一次)

Pytorch-Memory-Utils监控显存占用

显存占用

显存占用=模型参数+计算产生的中间变量
减少显存占用的方法

  1. inplace替换
  2. 用del一边计算一边清除中间变量
  3. 减少batch_size,避免用全连接,多用下采样
  4. 因为每次迭代都会引入点临时变量,会导致训练速度越来越慢,基本呈线性增长。但是如果周期性的使用torch.cuda.empty_cache()的话就可以解决这个问题。

冻结某些层的参数

在加载预训练模型的时候,我们有时想冻结前面几层,使其参数在训练过程中不发生变化。
我们需要先知道每一层的名字,通过以下代码打印:

net = Network() #获取自定义网络结构
for name, value in net.named_parameters():
	print('name: {0},\t grad: {1}'.format(name, value.requires_grad))

假设前几层信息如下:

name: cnn.VGG_16.convolution1_1.weight,	 grad: True
name: cnn.VGG_16.convolution1_1.bias,	 grad: True
name: cnn.VGG_16.convolution1_2.weight,	 grad: True
name: cnn.VGG_16.convolution1_2.bias,	 grad: True
name: cnn.VGG_16.convolution2_1.weight,	 grad: True
name: cnn.VGG_16.convolution2_1.bias,	 grad: True
name: cnn.VGG_16.convolution2_2.weight,	 grad: True
name: cnn.VGG_16.convolution2_2.bias,	 grad: True

后面的True表示该层的参数可训练,然后我们定义一个要冻结的层的列表

no_grad = [
    'cnn.VGG_16.convolution1_1.weight',
    'cnn.VGG_16.convolution1_1.bias',
    'cnn.VGG_16.convolution1_2.weight',
    'cnn.VGG_16.convolution1_2.bias'
]

冻结方法如下:

net = Net.CTPN()  # 获取网络结构
for name, value in net.named_parameters():
    if name in no_grad:
        value.requires_grad = False
    else:
        value.requires_grad = True

最后在定义优化器时,只对requires_grad为True的层的参数进行更新

optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01)

显式指定model.train()和model.eval()
我们的模型中经常会有一些子模型,其在训练时候和测试时候的参数是不同的,比如dropout中的丢弃率和Batch Normalization中的 γ \gamma γ β \beta β等,这时我们需要显式地指定不同阶段,在pytorch中我们通过model.train()model.eval()进行显式指定(因为BN的running_mean等并非nn.Parameter,用requires_grad冻不住,需要调用BN的.eval()

对不同层使用不同学习率

以如下模型为例:

net = Network()  # 获取自定义网络结构
for name, value in net.named_parameters():
    print('name: {}'.format(name))

# 输出:
# name: cnn.VGG_16.convolution1_1.weight
# name: cnn.VGG_16.convolution1_1.bias
# name: cnn.VGG_16.convolution1_2.weight
# name: cnn.VGG_16.convolution1_2.bias
# name: cnn.VGG_16.convolution2_1.weight
# name: cnn.VGG_16.convolution2_1.bias
# name: cnn.VGG_16.convolution2_2.weight
# name: cnn.VGG_16.convolution2_2.bias

对convolution1和convolution2设置不同的学习率,首先将它们分开,放到不同的列表中:

conv1_params = []
conv2_params = []

for name, params in net.named_parameters():
	if "convolution1" in name:
		conv1_params += [params]
	else:
		conv2_params += [params]
# 然后在优化器中进行如下操作:
optimizer = optim.Adam(
[
	{"params": conv1_params, 'lr':0.01},
	{"params": conv2_params, 'lr':0.001},
],
weight_decay = 1e-3
)

我们将模型划分为两部分,存放到一个列表里,每部分就对应上面的一个字典,在字典里设置不同的学习率,当着两部分有相同的其他参数时,就将参数放到列表外面作为全局参数,如上面的‘weight_decay’。

也可以在列表外设置一个全局学习率,当各部分字典里设置了局部学习率时,就使用该学习率,否则就使用列表外的全局学习率。

retain_graph使用

在对一个损失进行反向传播时,在pytorch中调用out.backward()即可实现,
对loss进行反向传播就可以求得损失函数对于学习参数的梯度,在.backward()中,

backward(gradient=None, retain_graph=None, create_graph=False)

这里我们关注的是retain_graph这个参数,这个参数如果为False或者None则在反向传播完后,就释放掉构建出来的graph,如果为True则不对graph进行释放。

但是我们已经计算完梯度了,为什么还要保存graph呢?这里举个例子,比如在对抗生成网络GAN中需要先对某个模块比如生成器进行训练,后对判别器进行训练,这个时候整个网络就会存在两个以上的loss,

G_loss = ...
D_loss = ...

opt.zero_grad() #对所有梯度清0
D_loss.backward(retain_graph=True) #保存graph结构,后续使用
opt.step() # 更新梯度,只更新D的梯度,因为只有D的梯度不为0

opt.zero_grad() # 对所有梯度清0
G_loss.backward(retain_graph=False) #不保存graph结构,可以释放graph
# 下一个迭代中通过forward还可以build出来
opt.step() #更新梯度,只更新G的,因为只有G的不为0

这个时候就可以对网络中多个loss进行分步训练了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值