刚开始接触pytorch框架时,最让我觉得神奇的就是它居然可以–自 动 求 导 !

于是我开始尝试理解内部的运行机制,但很快放弃了,直接当成黑盒使用……

最近又遇到一个奇怪的bug,让我不得不去学一下相关知识,预防忘记,故记录下来,共勉之。

1 grad、grad_fn、requires_grad()

一个Tensor中通常会有以下属性:

data: 即存储的数据信息

requires_grad: 设置为True则表示该Tensor需要求导

grad: 该Tensor的梯度值,每次在计算backward时都需要将前一时刻的梯度归零,否则梯度值会一直累加。

grad_fn: 叶子节点通常为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪种类型。例如 y.grad_fn = <PowBackward0 at 0x213550af048>, z.grad_fn=<AddBackward0 at 0x2135df11be0>

is_leaf: 用来指示该Tensor是否是叶子节点。

这里要区分一下两个概念,参数更新和梯度更新。在pytorch框架里,参数由变量data保存,梯度由专门的变量grad保存。参数更新是靠梯度来实现的。

grad以矩阵的形式来存储梯度值,还有一个grad_fn指向梯度函数(fn指的是function),可以指导梯度更新参数。

大佬总结的详细介绍
Pytorch autograd,backward详解
leaf variable & with torch.no_grad & -=
Pytorch的aotugrad大白话理解及属性使用解释

关于leaf tensor

并不是每个requires_grad()设为True的值都会在backward的时候得到相应的grad,它还必须为leaf。如果想得到当前自己创建的,requires_grad为True的tensor在反向传播时的grad, 可以用retain_grad()这个属性

详细介绍 leaf tensor

2 with torch.no_grad():

众所周知,代码训练一般分为train和eval两部分,train负责训练模型,让模型尽可能拟合train_set,而eval负责检验模型,看看模型是不是收敛了,是不是可以结束训练了。

往往一个epoch的train后,就进行一次eval。在这个过程里,train需要进行参数更新,而eval不需要更新参数。

那怎么让模型知道自己需不需要更新呢?

很简单,用一行代码就行。当你需要让模型训练,可以用

    with torch.no_grad():

举个例子:

 with torch.no_grad():for batch_index, data_set in enumerate(val_loader):……logits = net(data)loss = criterion(logits, true_label)loss.backward()optimizer.step()……

with torch.no_grad():的原理是将grad和grad_fn设为None,强制不参与梯度更新。


图片来自with torch.no_grad() 详解

在此时,所有计算都不会导致梯度的累积参数的更新。通过debug,发现代码运行到with torch.no_grad() 代码块下时,参数权重的requires_grad()并不会改变。

3 net.train()和net.eval()的机制

我们常常见到两个函数

 net.train()net.eval()

举个例子:

     net.train()for batch_index, data_set in enumerate(val_loader):……logits = net(data)loss = criterion(logits, true_label)loss.backward()optimizer.step()……

经过研究,我发现net.train()和net.eval()会改变模型中的一个变量:

通过维护这个变量=True或者=False,模型就知道自己处于哪个阶段。

training=false时,不能调用loss.backward(),否则会报错。以此达到区分train和eval的效果。

net.train()和net.eval()到底在什么时候使用?如果一个模型有Dropout与BatchNormalization,那么它在训练时要以一定概率进行Dropout或者更新BatchNormalization参数,而在测试时不在需要Dropout或更新BatchNormalization参数。此时,要用net.train()和net.eval()进行区分。

在没有涉及到BN与Dropout的模型,这两个函数没什么用。

4 require_grad以冻结部分参数

当你想让模型的某些层不参与参数更新,可以手动将该层的requires_grad设为false,比如使用bert时,如果你不想调bert的参数,可以

      for p in self.bert_layer.parameters():p.requires_grad = False

5 debug

复现别人的代码做实验的时候,突然发现模型跑了好多个epoch后,accuracy一直很低没怎么变化。观察后发现,loss也是一直在20左右徘徊。

先是以为代码出错了,毕竟改了很多地方,仔细排查后还是没解决,搞到后来甚至怀疑是net.eval()、with torch.no_grad在作怪。

接着怀疑是 梯度消失 ,想做排查,无从下手。

研究了半天,最终我确定了还真是梯度消失。

排查过程如下:

我们都知道,loss.backward()时,会计算好梯度,保存到grad属性中,但不会更新参数。只有到了optimizer.step()后,才会根据grad更新参数。

所以第一步,在每一次loss.backward()前后,将grad打印出来;在每一次optimizer.step()前后将网络参数打印出来,观察是否发生变化。代码如下。

for named, parameters in net.named_parameters():print(named, parameters, parameters.grad)

对于一些非叶子tensor,你想查看梯度,可以使用 .retain_grad() 查看。

如果你会debug,将参数加入监视,通过打断点观察会更加方便。添加监视的其中一种方法如下图所示:

接着分析结果,分几种情况:

① 梯度变化,参数也变化

这时候,你得多让模型跑几个epoch(一个epoch内可能会更新几百次参数),很可能在几个epoch后,梯度就越来越小,从1e-2一直减到1e-8、1e-22,梯度矩阵也越来越稀疏,最后干脆等于0了!!我遇到的就属于这种情况,才1个epoch没结束,梯度就一直等于0了,参数也不更新了。

经过艰难的排查,最终发现是learning_rate设置太大了,虽然设置了学习率衰减,让学习率慢慢下降,但学习率还来不及下降呢,最开始的一个epoch内就把模型搞崩了。(我的模型用了BERT,学习率一般设为1e-5,否则会发生灾难性遗忘。)

附上参考方法:梯度消失、爆炸排查和解决方法

  • 预训练加微调
  • 梯度剪切、权重正则(针对梯度爆炸)
  • 使用不同的激活函数
  • 使用batchnorm
  • 使用残差结构
  • 使用LSTM网络
  • 学习率decay
  • 初始化参数,如用nn.init.xavier_normal_()

② 梯度变化、参数不变

如果连第一次optimizer.step()权重矩阵都没变,那可能是你的代码有问题,比如你的参数所在的类,没有加入optimal的优化参数序列中等等。

③ 梯度不变,参数不变

如果在第一次backward()的时候,梯度都一直是None或者0,说明梯度没有传到该变量,顺着代码往下一直输出变量的梯度,直到梯度出现为止,然后检查为啥梯度消失了。再检查一下学习率是不是太小了,导致超出范围?

④梯度不变,参数变

这基本不可能,因为每轮训练前一般都要加上optimizer.zero_grad()这个代码,把梯度清零,然后再backward()计算梯度,防止梯度累加。在梯度一直为0或者None的情况下,自动求导不会改变参数,只有可能是你手动的用代码改动了参数。


这次debug的经历很曲折,我一直以为只有学习率太小会导致梯度消失,参数不更新。今天算是长见识了,学习率太大,最终达到的效果居然也是参数不更新。深层次的原因还没弄懂,有大佬讲解一下嘛?

【pytorch】 grad、grad_fn、requires_grad()、with torch.no_grad() 、net.train()、net.eval():记录一次奇怪的debug经历相关推荐

  1. 【Pytorch】区分detach()和torch.no_grad()

    先上三个例子,可能你就已经懂得了.detach() 和 torch.no_grad() 都可以实现相同的效果,只是前者会麻烦一点,对每一个变量都要加上,而后者就不用管了. x = torch.tens ...

  2. 详解Pytorch中的requires_grad、叶子节点与非叶子节点、with torch.no_grad()、model.eval()、model.train()、BatchNorm层

    requires_grad requires_grad意为是否需要计算梯度 使用backward()函数反向传播计算梯度时,并不是计算所有tensor的梯度,只有满足下面条件的tensor的梯度才会被 ...

  3. with torch.no_grad()

    #with torch.no_grad() 详解 ''' 被该语句包裹起来的语句将不会被追踪梯度''' a=torch.tensor([1.1],requires_grad=True) b=a*2 p ...

  4. 通俗讲解Pytorch梯度的相关问题:计算图、torch.no_grad、zero_grad、detach和backward;Variable、Parameter和torch.tensor

    文章目录 with torch.no_grad()和requires_grad backward() Variable,Parameter和torch.tensor() zero_grad() 计算图 ...

  5. Pytorch的model.train() model.eval() torch.no_grad() 为什么测试的时候不调用loss.backward()计算梯度还要关闭梯度

    使用PyTorch进行训练和测试时一定注意要把实例化的model指定train/eval model.train() 启用 BatchNormalization 和 Dropout 告诉我们的网络,这 ...

  6. 【深度学习】pytorch自动求导机制的理解 | tensor.backward() 反向传播 | tensor.detach()梯度截断函数 | with torch.no_grad()函数

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.pytorch里自动求导的基础概念 1.1.自动求导 requires_grad=True 1.2.求导 requ ...

  7. pytorch之with torch.no_grad

    在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作).而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with t ...

  8. 【Pytorch】model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别

    model.train() 启用 Batch Normalization 和 Dropout 如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加model. ...

  9. pytorch的训练测试流程总结,以及model.evel(), model.train(),torch.no_grad()作用

    pytorch的 model.eval()和model.train()作用 pytorch中model.train()和model.eval()的区别主要在于Batch Normalization和D ...

最新文章

  1. 1.lamp网站构建
  2. log_softmax
  3. hdu 4545 魔法串 2013金山西山居创意游戏程序挑战赛——初赛(1)
  4. P2469 [SDOI2010]星际竞速
  5. 扩展极小值—lhMorpEMin
  6. ioca0中断 pic单片机_关于PIC单片机的模块和功能总结
  7. PHP curl 使用代码
  8. 阿里云centos 6.9安装oracle10g
  9. 构建复杂的应用程序 —— 重用与重构
  10. 机器学习、深度学习教程和代码资源帖
  11. DDR2与DDR的区别
  12. 南阳OJ 题目97 兄弟郊游问题
  13. 小型微型计算机系统杂志好投么,小型微型计算机系统杂志
  14. 3000亿茶行业市场如何乘势数字浪潮,跑出世界品牌
  15. day01 pathon基础
  16. 二维泊松方程数值解-五点差分法-共轭梯度法-python实现
  17. Rimworld Mod制作教程2 创建数据定义
  18. 计算机教师评语中职,中职期末评语
  19. Tarjan算法小结1——SCC
  20. 基于51单片机的多路多点温度检测两种供电方式proteus仿真原理图PCB

热门文章

  1. 超千万人同时在线,抖音快手,是怎么抗住高并发?
  2. 深度学习中使用Screen 指令进行离线训练
  3. ios 基于CAEmitterLayer的雪花,烟花,火焰,爱心等效果demo
  4. 海康java版本SDK中jna.jar的说明
  5. 60-硅谷课堂6-硅谷课堂-公众号消息和微信授权-- 笔记
  6. 身份信息认证服务器,在线身份认证解决方案
  7. bootstrap4 input-spinner数字增减框的使用
  8. Docker安装指定版本异常:Error: Package: docker-ce-17.03.1.ce-1.el7.centos.x86_64 (docker-ce-stable)
  9. Linux下的压缩解压缩工具(转载)
  10. POJ,3713 Transferring Sylla