汇总 Pytorch 踩过的10个坑

2020 年 8 月 19 日 极市平台
↑ 点击 蓝字  关注极市平台

作者丨土豆@知乎
来源丨https://zhuanlan.zhihu.com/p/180020358
本文仅用于学术分享,著作权归作者所有。如有侵权,请联系后台作删文处理。

极市导读

 

本文总结了10大Pytorch操作中的“坑”,能够帮助使用者规避不必要的麻烦。>>>极市七夕粉丝福利活动:炼丹师们,七夕这道算法题,你会解吗?


pytorch中的交叉熵

pytorch的交叉熵nn.CrossEntropyLoss在训练阶段,里面是内置了softmax操作的,因此只需要喂入原始的数据结果即可,不需要在之前再添加softmax层。这个和tensorflow的tf.softmax_cross_entropy_with_logits如出一辙.[1][2]pytorch的交叉熵nn.CrossEntropyLoss在训练阶段,里面是内置了softmax操作的,因此只需要喂入原始的数据结果即可,不需要在之前再添加softmax层。这个和tensorflow的tf.softmax_cross_entropy_with_logits如出一辙.[1][2]

pytorch中的MSELoss和KLDivLoss

在深度学习中,MSELoss均方差损失和KLDivLossKL散度是经常使用的两种损失,在pytorch中,也有这两个函数,如:

loss = nn.MSELoss()input = torch.randn(3, 5, requires_grad=True)target = torch.randn(3, 5)output = loss(input, target)output.backward()

这个时候我们要注意到,我们的标签target是需要一个不能被训练的,也就是requires_grad=False的值,否则将会报错,出现如:

AssertionError: nn criterions don’t compute the gradient w.r.t. targets - please mark these variables as volatile or not requiring gradients

我们注意到,其实不只是MSELoss,其他很多loss,比如交叉熵,KL散度等,其target都需要是一个不能被训练的值的,这个和TensorFlow中的tf.nn.softmax_cross_entropy_with_logits_v2不太一样,后者可以使用可训练的target,具体见[3]

在验证和测试阶段取消掉梯度(no_grad)

一般来说,我们在进行模型训练的过程中,因为要监控模型的性能,在跑完若干个epoch训练之后,需要进行一次在验证集[4]上的性能验证。一般来说,在验证或者是测试阶段,因为只是需要跑个前向传播(forward)就足够了,因此不需要保存变量的梯度。保存梯度是需要额外显存或者内存进行保存的,占用了空间,有时候还会在验证阶段导致OOM(Out Of Memory)错误,因此我们在验证和测试阶段,最好显式地取消掉模型变量的梯度。pytroch 0.4及其以后的版本中,用torch.no_grad()这个上下文管理器就可以了,例子如下:

model.train()# here train the model, just skip the codesmodel.eval() # here we start to evaluate the modelwith torch.no_grad(): for each in eval_data: data, label = each logit = model(data) ... # here we just skip the codes

如上,我们只需要在加上上下文管理器就可以很方便的取消掉梯度。这个功能在pytorch以前的版本中,通过设置volatile=True生效,不过现在这个用法已经被抛弃了。

显式指定model.train()model.eval()

我们的模型中经常会有一些子模型,其在训练时候和测试时候的参数是不同的,比如 dropout[6]中的丢弃率和 Batch Normalization[5]中的 等,这个时候我们就需要显式地指定不同的阶段(训练或者测试),在 pytorch中我们通过 model.train()model.eval()进行显式指定,具体如:
     
     
       
model = CNNNet(params) # here we start the training model.train() for each in train_data: data, label = each logit = model(data) loss = criterion(logit, label) ... # just skip # here we start the evaluation
model.eval() with torch.no_grad(): # we dont need grad in eval phase for each in eval_data: data, label = each logit = model(data) loss = criterion(logit, label) ... # just skip
注意,在模型中有BN层或者dropout层时,在训练阶段和测试阶段必须显式指定train()和eval()

关于retain_graph的使用

在对一个损失进行反向传播时,在 pytorch中调用 out.backward()即可实现,给个小例子如:
     
     
       
import torch import torch.nn as nn import numpy as np class net(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(10,2) self.act = nn.ReLU() def forward(self,inputv): return self.act(self.fc1(inputv)) n = net() opt = torch.optim.Adam(n.parameters(),lr=3e-4) inputv = torch.tensor(np.random.normal(size=(4,10))).float() output = n(inputv) target = torch.tensor(np.ones((4,2))).float() loss = nn.functional.mse_loss(output, target) loss.backward() # here we calculate the gradient w.r.t the leaf
loss进行反向传播就可以求得 ,即是损失对于每个叶子节点的梯度。我们注意到,在 .backward()这个API的文档中,有几个参数,如:
     
     
       
backward(gradient=None, retain_graph=None, create_graph=False)
这里我们关注的是 retain_graph这个参数,这个参数如果为 False或者 None则在反向传播完后,就释放掉构建出来的graph,如果为 True则不对graph进行释放[7][8]。
我们这里就有个问题,我们既然已经计算忘了梯度了,为什么还要保存graph呢?直接释放掉等待下一个迭代不就好了吗,不释放掉不会白白浪费内存吗?我们这里根据[7]中的讨论,简要介绍下为什么在某些情况下需要保留graph。如下图所示,我们用代码构造出此graph:
     
     
       
import torch from torch.autograd import Variable a = Variable(torch.rand(1, 4), requires_grad=True) b = a**2 c = b*2 d = c.mean() e = c.sum()
如果我们第一次需要对末节点 d进行求梯度,我们有:
     
     
       
d.backward()
问题是在执行完反向传播之后,因为没有显式地要求它保留graph,系统对graph内存进行释放,如果下一步需要对节点 e进行求梯度,那么将会因为没有这个graph而报错。因此有例子:
     
     
       
d.backward(retain_graph=True) # fine e.backward(retain_graph=True) # fine d.backward() # also fine e.backward() # error will occur!
利用这个性质在某些场景是有作用的,比如在对抗生成网络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进行分步的训练了。

进行梯度累积,实现内存紧张情况下的大batch_size训练

在上面讨论的 retain_graph参数中,还可以用于 累积梯度,在GPU显存紧张的情况下使用可以等价于用更大的 batch_size进行训练。首先我们要明白,当调用 .backward()时,其实是对损失到各个节点的梯度进行计算,计算结果将会保存在各个节点上,如果不用 opt.zero_grad()对其进行清0,那么只要你一直调用 .backward()梯度就会一直累积,相当于是在大的 batch_size下进行的训练。我们给出几个例子阐述我们的观点。
     
     
       
import torch import torch.nn as nn import numpy as np class net(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(10,2) self.act = nn.ReLU() def forward(self,inputv): return self.act(self.fc1(inputv)) n = net() inputv = torch.tensor(np.random.normal(size=(4,10))).float() output = n(inputv) target = torch.tensor(np.ones((4,2))).float() loss = nn.functional.mse_loss(output, target) loss.backward(retain_graph=True) opt = torch.optim.Adam(n.parameters(),lr=0.01) for each in n.parameters(): print(each.grad)
第一次输出:
     
     
       
tensor([[ 0.0493, -0.0581, -0.0451, 0.0485, 0.1147, 0.1413, -0.0712, -0.1459, 0.1090, -0.0896], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]) tensor([-0.1192, 0.0000])
在运行一次 loss.backward(retain_graph=True),输出为:
     
     
       
tensor([[ 0.0987, -0.1163, -0.0902, 0.0969, 0.2295, 0.2825, -0.1424, -0.2917, 0.2180, -0.1792], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]) tensor([-0.2383, 0.0000])
同理,第三次:
     
     
       
tensor([[ 0.1480, -0.1744, -0.1353, 0.1454, 0.3442, 0.4238, -0.2136, -0.4376, 0.3271, -0.2688], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]) tensor([-0.3575, 0.0000])
运行一次 opt.zero_grad(),输出为:
     
     
       
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) tensor([0., 0.])
现在明白为什么我们一般在求梯度时要用 opt.zero_grad()了吧,那是为什么不要这次的梯度结果被上一次给影响,但是在某些情况下这个‘影响’是可以利用的。

调皮的dropout

这个在利用 torch.nn.functional.dropout的时候,其参数为:
     
     
       
torch.nn.functional.dropout(input, p=0.5, training=True, inplace=False)
注意这里有个 training指明了是否是在训练阶段,是否需要对神经元输出进行随机丢弃,这个是需要自行指定的,即便是用了 model.train()或者 model.eval()都是如此,这个和 torch.nn.dropout不同,因为后者是一个 (Layer),而前者只是一个函数,不能纪录状态[9]。

嘿,检查自己,说你呢, index_select

torch.index_select()是一个用于索引给定张量中某一个维度中元素的方法,其API手册如:
     
     
       
torch.index_select(input, dim, index, out=None) → Tensor Parameters: input (Tensor) – 输入张量,需要被索引的张量 dim (int) – 在某个维度被索引 index (LongTensor) – 一维张量,用于提供索引信息 out (Tensor, optional) – 输出张量,可以不填
其作用很简单,比如我现在的输入张量为 1000 * 10的尺寸大小,其中 1000为样本数量, 10为特征数目,如果我现在需要指定的某些样本,比如第 1-100, 300-400等等样本,我可以用一个 index进行索引,然后应用 torch.index_select()就可以索引了,例子如:
     
     
       
>>> x = torch.randn(3, 4) >>> x tensor([[ 0.1427, 0.0231, -0.5414, -1.0009], [-0.4664, 0.2647, -0.1228, -1.1068], [-1.1734, -0.6571, 0.7230, -0.6004]]) >>> indices = torch.tensor([0, 2]) >>> torch.index_select(x, 0, indices) # 按行索引 tensor([[ 0.1427, 0.0231, -0.5414, -1.0009], [-1.1734, -0.6571, 0.7230, -0.6004]]) >>> torch.index_select(x, 1, indices) # 按列索引 tensor([[ 0.1427, -0.5414], [-0.4664, -0.1228], [-1.1734, 0.7230]])
然而有一个问题是, pytorch似乎在使用 GPU的情况下,不检查 index是否会越界,因此如果你的 index越界了,但是报错的地方可能不在使用 index_select()的地方,而是在后续的代码中,这个似乎就需要留意下你的 index了。同时, index是一个 LongTensor,这个也是要留意的。

悄悄地更新,BN层就是个小可爱

在trainning状态下,BN层的统计参数 running_meanrunning_var是在调用 forward()后就更新的,这个和一般的参数不同,容易造成疑惑,考虑到篇幅较长,请移步到[11]。

F.interpolate的问题

我们经常需要对图像进行插值,而 pytorch的确也是提供对以 tensor形式表示的图像进行插值的功能,那就是函数 torch.nn.functional.interpolate[12],但是我们注意到这个插值函数有点特别,它是对以 batch为单位的图像进行插值的,如果你想要用以下的代码去插值:
     
     
       
image = torch.rand(3,112,112) # H = 112, W = 112, C = 3的图像 image = torch.nn.functional.interpolate(image, size=(224,224))
那么这样就会报错,因为此处的 size只接受一个整数,其对W这个维度进行缩放,这里, interpolate会认为3是 batch_size,因此如果需要对图像的H和W进行插值,那么我们应该如下操作:
     
     
       
image = torch.rand(3,112,112) # H = 112, W = 112, C = 3的图像 image = image.unsqueeze(0) # shape become (1,3,112,112) image = torch.nn.functional.interpolate(image, size=(224,224))

Reference

[1]. Why does CrossEntropyLoss include the softmax function?https://discuss.pytorch.org/t/why-does-crossentropyloss-include-the-softmax-function/4420
[2]. Do I need to use softmax before nn.CrossEntropyLoss()?https://discuss.pytorch.org/t/do-i-need-to-use-softmax-before-nn-crossentropyloss/16739/2
[3]. tf.nn.softmax_cross_entropy_with_logits 将在未来弃用,https://blog.csdn.net/LoseInVain/article/details/80932605
[4]. 训练集,测试集,检验集的区别与交叉检验,https://blog.csdn.net/LoseInVain/article/details/78108955
[5]. Ioffe S, Szegedy C. Batch normalization: Accelerating deep network training by reducing internal covariate shift[J]. arXiv preprint arXiv:1502.03167, 2015.
[6]. Hinton G E, Srivastava N, Krizhevsky A, et al. Improving neural networks by preventing co-adaptation of feature detectors[J]. arXiv preprint arXiv:1207.0580, 2012. 
[7]. What does the parameter retain_graph mean in the Variable's backward() method?https://stackoverflow.com/questions/46774641/what-does-the-parameter-retain-graph-mean-in-the-variables-backward-method
[8]. https://pytorch.org/docs/stable/autograd.html?highlight=backward#torch.Tensor.backward
[9] https://pytorch.org/docs/stable/nn.html?highlight=dropout#torch.nn.functional.dropout
[10]. https://github.com/pytorch/pytorch/issues/571
[11]. Pytorch的BatchNorm层使用中容易出现的问题,https://blog.csdn.net/LoseInVain/article/details/86476010
[12]. https://pytorch.org/docs/stable/nn.functional.html#torch.nn.functional.interpolate


推荐阅读



    添加极市小助手微信(ID : cvmart2),备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳),即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群:月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~

    △长按添加极市小助手

    △长按关注极市平台,获取 最新CV干货

    觉得有用麻烦给个在看啦~   
    登录查看更多
    0

    相关内容

    基于Lua语言的深度学习框架 github.com/torch
    【IJCAI2020-华为诺亚】面向深度强化学习的策略迁移框架
    专知会员服务
    27+阅读 · 2020年5月25日
    一网打尽!100+深度学习模型TensorFlow与Pytorch代码实现集合
    【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
    专知会员服务
    20+阅读 · 2019年12月31日
    《动手学深度学习》(Dive into Deep Learning)PyTorch实现
    专知会员服务
    119+阅读 · 2019年12月31日
    【GitHub实战】Pytorch实现的小样本逼真的视频到视频转换
    专知会员服务
    35+阅读 · 2019年12月15日
    开源书:PyTorch深度学习起步
    专知会员服务
    50+阅读 · 2019年10月11日
    TensorFlow 2.0 学习资源汇总
    专知会员服务
    66+阅读 · 2019年10月9日
    Github 项目推荐 | PyTorch 实现的 GAN 文本生成框架
    AI研习社
    35+阅读 · 2019年6月10日
    如何给你PyTorch里的Dataloader打鸡血
    极市平台
    15+阅读 · 2019年5月21日
    PyTorch 学习笔记(七):PyTorch的十个优化器
    极市平台
    8+阅读 · 2019年5月19日
    PyTorch 学习笔记(六):PyTorch的十七个损失函数
    极市平台
    47+阅读 · 2019年5月13日
    基于PyTorch/TorchText的自然语言处理库
    专知
    28+阅读 · 2019年4月22日
    PyTorch 到底好用在哪里?
    AI研习社
    3+阅读 · 2017年10月27日
    干货| PyTorch相比TensorFlow,存在哪些自身优势?
    全球人工智能
    15+阅读 · 2017年10月4日
    手把手教你由TensorFlow上手PyTorch(附代码)
    数据派THU
    5+阅读 · 2017年10月1日
    教程 | 如何从TensorFlow转入PyTorch
    深度学习世界
    38+阅读 · 2017年9月30日
    Arxiv
    0+阅读 · 2020年10月13日
    Arxiv
    1+阅读 · 2020年10月12日
    Arxiv
    2+阅读 · 2020年10月12日
    Arxiv
    5+阅读 · 2019年8月22日
    Sparse Sequence-to-Sequence Models
    Arxiv
    5+阅读 · 2019年5月14日
    Arxiv
    15+阅读 · 2018年2月4日
    Arxiv
    5+阅读 · 2018年1月18日
    VIP会员
    相关VIP内容
    【IJCAI2020-华为诺亚】面向深度强化学习的策略迁移框架
    专知会员服务
    27+阅读 · 2020年5月25日
    一网打尽!100+深度学习模型TensorFlow与Pytorch代码实现集合
    【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
    专知会员服务
    20+阅读 · 2019年12月31日
    《动手学深度学习》(Dive into Deep Learning)PyTorch实现
    专知会员服务
    119+阅读 · 2019年12月31日
    【GitHub实战】Pytorch实现的小样本逼真的视频到视频转换
    专知会员服务
    35+阅读 · 2019年12月15日
    开源书:PyTorch深度学习起步
    专知会员服务
    50+阅读 · 2019年10月11日
    TensorFlow 2.0 学习资源汇总
    专知会员服务
    66+阅读 · 2019年10月9日
    相关资讯
    Github 项目推荐 | PyTorch 实现的 GAN 文本生成框架
    AI研习社
    35+阅读 · 2019年6月10日
    如何给你PyTorch里的Dataloader打鸡血
    极市平台
    15+阅读 · 2019年5月21日
    PyTorch 学习笔记(七):PyTorch的十个优化器
    极市平台
    8+阅读 · 2019年5月19日
    PyTorch 学习笔记(六):PyTorch的十七个损失函数
    极市平台
    47+阅读 · 2019年5月13日
    基于PyTorch/TorchText的自然语言处理库
    专知
    28+阅读 · 2019年4月22日
    PyTorch 到底好用在哪里?
    AI研习社
    3+阅读 · 2017年10月27日
    干货| PyTorch相比TensorFlow,存在哪些自身优势?
    全球人工智能
    15+阅读 · 2017年10月4日
    手把手教你由TensorFlow上手PyTorch(附代码)
    数据派THU
    5+阅读 · 2017年10月1日
    教程 | 如何从TensorFlow转入PyTorch
    深度学习世界
    38+阅读 · 2017年9月30日
    相关论文
    Arxiv
    0+阅读 · 2020年10月13日
    Arxiv
    1+阅读 · 2020年10月12日
    Arxiv
    2+阅读 · 2020年10月12日
    Arxiv
    5+阅读 · 2019年8月22日
    Sparse Sequence-to-Sequence Models
    Arxiv
    5+阅读 · 2019年5月14日
    Arxiv
    15+阅读 · 2018年2月4日
    Arxiv
    5+阅读 · 2018年1月18日
    Top
    微信扫码咨询专知VIP会员