pytorch是动态图,即计算图的搭建和运算是同时的,随时可以输出结果;而tensorflow是静态图。

在pytorch的计算图里只有两种元素:数据(tensor)和运算(operation)

运算包括:加减乘除、开方、幂指对、三角函数等可求导运算。

数据可分为:叶子节点和非叶子节点;叶子节点是用户创建的节点,不依赖其他节点;叶子节点和非叶子节点的区别在于反向传播结束后,非叶子节点的梯度会被释放掉,只保留叶子节点的梯度,这样就节省了内存。如果想要保留非叶子节点的梯度,可以使用retain_grad()方法。

torch.tensor具有以下属性:

  • 查看是否可以求导:requires_grad
  • 查看运算名称:grad_fn
  • 查看是否为叶子节点:is_leaf
  • 查看导数值:grad

对于requires_grad属性,自己定义的叶子节点默认为False,而非叶子节点默认为True,神经网络中的权重默认为True。判断哪些节点是True/False的一个原则就是从我们需要求导的叶子节点到loss节点之间是一条可求导的通路。

当我们想要对某个Tensor变量求梯度时,需要先指定requires_grad属性为True,指定方式主要有以下两种:

x=torch.tensor(1.).requires_grad_()x=torch.tensor(1.,requires_grad=True)

pytorch提供两种求梯度的方法:backward()torch.autograd.grad(),这两种方法的区别在于前者是给叶子节点填充.grad字段,而后者是直接返回梯度。需要知道的一点是y.backward()其实等同于torch.autograd.backward(y)

一个简单的求导例子是y=(x+1)×(x+2)y=(x+1)\times(x+2)y=(x+1)×(x+2),计算∂y∂x\frac{\partial y}{\partial x}∂x∂y​,假定x=2x=2x=2,先画出计算图:


整个链式求导过程为:

∂y∂x=∂y∂a∂a∂x+∂y∂b∂b∂x=(x+2)×1+(x+1)×1→7\frac{\partial y}{\partial x}=\frac{\partial y}{\partial a}\frac{\partial a}{\partial x}+\frac{\partial y}{\partial b}\frac{\partial b}{\partial x}=(x+2)\times1+(x+1)\times1\to7∂x∂y​=∂a∂y​∂x∂a​+∂b∂y​∂x∂b​=(x+2)×1+(x+1)×1→7

使用backward():

x = torch.tensor(2., requires_grad=True)a = torch.add(x, 1)
b = torch.add(x, 2)
y = torch.mul(a, b)y.backward()
print(x.grad)
>>>tensor(7.)

观察下这几个tensor的属性:

print("requires_grad: ", x.requires_grad, a.requires_grad, b.requires_grad, y.requires_grad)
print("is_leaf: ", x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
print("grad: ", x.grad, a.grad, b.grad, y.grad)>>>requires_grad:  True True True True
>>>is_leaf:  True False False False
>>>grad:  tensor(7.) None None None

使用backward()函数反向传播计算tensor的梯度时,并不计算所有tensor的梯度,而是只计算满足这几个条件的tensor的梯度:1.类型为叶子节点、2.requires_grad=True、3.依赖该tensor的所有tensor的requires_grad=True。所有满足条件的变量梯度会自动保存到对应的grad属性里。(?)

使用autograd.grad()

x = torch.tensor(2., requires_grad=True)a = torch.add(x, 1)
b = torch.add(x, 2)
y = torch.mul(a, b)grad = torch.autograd.grad(outputs=y, inputs=x)
print(grad)# (tensor(7.),)

因为指定了输出y,输入x,所以返回值就是∂y∂x\frac{\partial y}{\partial x}∂x∂y​这一梯度,注意整个返回值是一个元组,但我们仅需要元组的第一个元素。

再举另外一个复杂一点且高阶求导的例子:z=x2yz=x^2yz=x2y,计算∂z∂x,∂z∂y,∂2z∂2x\frac{\partial z}{\partial x},\frac{\partial z}{\partial y},\frac{\partial^2 z}{\partial^2 x}∂x∂z​,∂y∂z​,∂2x∂2z​,假设给定x=2,y=3x=2,y=3x=2,y=3

通过链式求导法则,我们可以得到:

∂z∂x=2xy→12,∂z∂y=x2→4,∂2z∂2x=2y→6\frac{\partial z}{\partial x}=2xy\to12,\frac{\partial z}{\partial y}=x^2\to4,\frac{\partial^2 z}{\partial^2 x}=2y\to6∂x∂z​=2xy→12,∂y∂z​=x2→4,∂2x∂2z​=2y→6

求一阶导可以用backward()

x = torch.tensor(2., requires_grad=True)
y = torch.tensor(3., requires_grad=True)z = x * x * yz.backward()
print(x.grad, y.grad)
>>>tensor(12.) tensor(4.)

我们也可以使用autograd.grad()

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x)
print(grad_x[0])
>>>tensor(12.)

在这里如果这样写代码,同时输出y的导数:

grad_x=torch.autograd.grad(outputs=z,inputs=x)
grad_y=torch.autograd.grad(outputs=z,inputs=y)

此时会这样进行报错:

RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they
have already been freed). Saved intermediate values of the graph are freed when you call .backward() or
autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time
or if you need to access saved tensors after calling backward.

这是因为无论是backward还是autograd.grad在计算一次梯度后图就被释放了,如果想要保留,需要添加retain_graph=True

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x, retain_graph=True)
grad_y = torch.autograd.grad(outputs=z, inputs=y)print(grad_x[0], grad_y[0])
>>>tensor(12.) tensor(4.)

接下来看如何求高阶导,理论上其实是上面的grad_x再对x求梯度:

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x, retain_graph=True)
grad_xx = torch.autograd.grad(outputs=grad_x, inputs=x)print(grad_xx[0])
>>>RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

这里出现了报错,虽然retain_graph=True保留了计算图和中间变量梯度,但没有保存grad_x的运算方式,需要使用create_graph=True在保留原图的基础上再建立额外的求导计算图,也就是会把∂z∂x=2xy\frac{\partial z}{\partial x}=2xy∂x∂z​=2xy这样的运算存下来:

# autograd.grad() + autograd.grad()
x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x, create_graph=True)
grad_xx = torch.autograd.grad(outputs=grad_x, inputs=x)print(grad_xx[0])
>>>tensor(6.)

grad_xx这里也可以直接用backward(),相当于直接从∂z∂x=2xy\frac{\partial z}{\partial x}=2xy∂x∂z​=2xy开始回传

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * ygrad = torch.autograd.grad(outputs=z, inputs=x, create_graph=True)
grad[0].backward()print(x.grad)
>>>tensor(6.)

也可以先用backward()然后对x.grad这个一阶导继续求导

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True)
grad_xx = torch.autograd.grad(outputs=x.grad, inputs=x)print(grad_xx[0])
>>>tensor(6.)

那是不是也可以直接用两次backward()呢?第二次直接x.grad从开始回传,我们试一下

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True) # x.grad = 12
x.grad.backward()print(x.grad)
>>>tensor(18., grad_fn=<CopyBackwards>)

发现了问题,结果不是6,而是18,发现第一次回传时输出x梯度是12。这是因为PyTorch使用backward()时默认会累加梯度,需要手动把前一次的梯度清零

x = torch.tensor(2.).requires_grad_()
y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True)
x.grad.data.zero_()
x.grad.backward()print(x.grad)
>>>tensor(6., grad_fn=<CopyBackwards>)

前面都是标量对标量求导,如果不是标量时:

x = torch.tensor([1., 2.]).requires_grad_()
y = x + 1y.backward()
print(x.grad)
>>>RuntimeError: grad can be implicitly created only for scalar outputs

可以看到此时报错了,因为只能标量对标量,标量对向量求梯度,x可以是标量或者向量,但y只能是标量,所以只要先将y转变为标量,对分别求导没影响的就是求和:

以下面为例:

假设x=[x1,x2],y=[x12,x22],y′=y.sum()=x12+x22,∂y′∂x1=2x1→2,∂y′∂x2=2x2→4x=[x_1,x_2],y=[x_1^2,x_2^2],y'=y.sum()=x_1^2+x_2^2,\frac{\partial y'}{\partial x_1}=2x_1\to2,\frac{\partial y'}{\partial x_2}=2x_2\to4x=[x1​,x2​],y=[x12​,x22​],y′=y.sum()=x12​+x22​,∂x1​∂y′​=2x1​→2,∂x2​∂y′​=2x2​→4

x = torch.tensor([1., 2.]).requires_grad_()
y = x * xy.sum().backward()
print(x.grad)
>>>tensor([2., 4.])

具体一点来解释,我们写出求导计算的雅可比矩阵,y=[y1,y2]y=[y_1,y_2]y=[y1​,y2​]是一个向量,J=[∂y∂x1,∂y∂x2]=[∂y1∂x1∂y1∂x2∂y2∂x1∂y2∂x2]J=[\frac{\partial y}{\partial x_1},\frac{\partial y}{\partial x_2}]=\begin{bmatrix}\frac{\partial y_1}{\partial x_1}&\frac{\partial y_1}{\partial x_2}\\\frac{\partial y_2}{\partial x_1}&\frac{\partial y_2}{\partial x_2}\end{bmatrix}J=[∂x1​∂y​,∂x2​∂y​]=[∂x1​∂y1​​∂x1​∂y2​​​∂x2​∂y1​​∂x2​∂y2​​​],但是我们希望最终求导的结果是[∂y1∂x1,∂y2∂x2][\frac{\partial y_1}{\partial x_1},\frac{\partial y_2}{\partial x_2}][∂x1​∂y1​​,∂x2​∂y2​​],我们怎么得到这个值呢?注意∂y1∂x2\frac{\partial y_1}{\partial x_2}∂x2​∂y1​​和\frac{\partial y_2}{\partial x_1}是0,那么:

[∂y1∂x1,∂y2∂x2]T=[∂y1∂x1∂y1∂x2∂y2∂x1∂y2∂x2][11][\frac{\partial y_1}{\partial x_1},\frac{\partial y_2}{\partial x_2}]^T=\begin{bmatrix}\frac{\partial y_1}{\partial x_1}&\frac{\partial y_1}{\partial x_2}\\\frac{\partial y_2}{\partial x_1}&\frac{\partial y_2}{\partial x_2}\end{bmatrix}\begin{bmatrix}1\\1\end{bmatrix}[∂x1​∂y1​​,∂x2​∂y2​​]T=[∂x1​∂y1​​∂x1​∂y2​​​∂x2​∂y1​​∂x2​∂y2​​​][11​]

所以不用y.sum()的另一种方式是:

x = torch.tensor([1., 2.]).requires_grad_()
y = x * xy.backward(torch.ones_like(x))
print(x.grad)
>>>tensor([2., 4.])

注意这里的torch.ones_like(x)指的就是雅可比矩阵右乘的那个向量,我们也可以使用autograd

x = torch.tensor([1., 2.]).requires_grad_()
y = x * xgrad_x = torch.autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(x))
print(grad_x[0])
>>>tensor([2., 4.])

或:

x = torch.tensor([1., 2.]).requires_grad_()
y = x * xgrad_x = torch.autograd.grad(outputs=y.sum(), inputs=x)
print(grad_x[0])
>>>tensor([2., 4.])

着重强调以及引申几点:

梯度清零:

Pytorch的自动求导梯度不会自动清零,而是会累积,所以一次反向传播后需要手动清零:

x.grad.zero_()

而在神经网络中,我们只需要执行:

optimizer.zero_grad()

使用detach()进行截断:

此时不会再往后计算梯度,假设有模型A和模型B,我们需要将A的输出作为B的输入,但训练时我们只训练模型B,那么可以这样做:

input_B = output_A.detach()

还是以前面的计算为例,将a切断,此时将只有b一条通路,且a变为叶子节点:

x = torch.tensor([2.], requires_grad=True)a = torch.add(x, 1).detach()
b = torch.add(x, 2)
y = torch.mul(a, b)y.backward()print("requires_grad: ", x.requires_grad, a.requires_grad, b.requires_grad, y.requires_grad)
print("is_leaf: ", x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
print("grad: ", x.grad, a.grad, b.grad, y.grad)>>>requires_grad:  True False True True
>>>is_leaf:  True True False False
>>>grad:  tensor([3.]) None None None

原位操作 in-place

这个操作改变值,但是不改变对象地址

注意:叶子节点不可执行in-place操作,因为反向传播时会访问原来的对象地址。

pytorch autograd.grad相关推荐

  1. Pytorch autograd.grad与autograd.backward详解

    Pytorch autograd.grad与autograd.backward详解 引言 平时在写 Pytorch 训练脚本时,都是下面这种无脑按步骤走: outputs = model(inputs ...

  2. [转]一文解释PyTorch求导相关 (backward, autograd.grad)

    PyTorch是动态图,即计算图的搭建和运算是同时的,随时可以输出结果:而TensorFlow是静态图. 在pytorch的计算图里只有两种元素:数据(tensor)和 运算(operation) 运 ...

  3. 实操教程|PyTorch AutoGrad C++层实现

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨xxy-zhihu@知乎 来源丨https://zhuanla ...

  4. c++ vector向量直接赋值_vector-Jacobian product 解释 ---- pytorch autograd

    这篇文章将要解释pytorch autograd文档中的vector-Jacobian product. 文章由pytorch 官方文档中的这段话引出. 首先,雅各比矩阵J计算的是向量Y对于向量X的导 ...

  5. pytorch - autograd与逻辑回归

    1.torch.autograd--自动求导系统 深度学习模型的训练就是不断更新权值,权值的更新需要求解梯度,梯度在模型训练中是至关重要的.然而求解梯度十分繁琐,pytorch提供自动求导系统.我们不 ...

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

    刚开始接触pytorch框架时,最让我觉得神奇的就是它居然可以–自 动 求 导 ! 于是我开始尝试理解内部的运行机制,但很快放弃了,直接当成黑盒使用-- 最近又遇到一个奇怪的bug,让我不得不去学一下 ...

  7. torch.autograd.grad求二阶导数

    1 用法介绍  pytorch中torch.autograd.grad函数主要用于计算并返回输出相对于输入的梯度总和,具体的参数作用如下所示: torch.tril(input, diagonal=0 ...

  8. Pytorch的grad、backward()、zero_grad()

    grad 梯度 什么样的tensor有grad? pytorch中只有torch.float和复杂类型才能有grad. x = torch.tensor([1, 2, 3, 4], requires_ ...

  9. pytorch autograd计算标量函数二阶导数

    计算标量函数:y=x3+sin(x)y=x^3+sin(x)y=x3+sin(x) 在x=1,π,5x=1,\pi,5x=1,π,5时的一阶导dydx\frac{dy}{dx}dxdy​和二阶导d2y ...

  10. Pytorch Autograd (自动求导机制)

    Introduce Pytorch Autograd库 (自动求导机制) 是训练神经网络时,反向误差传播(BP)算法的核心. 本文通过logistic回归模型来介绍Pytorch的自动求导机制.首先, ...

最新文章

  1. usaco Feed Ratios
  2. QIIME 2教程. 26为QIIME 2开发新插件DevelopingPlugin(2020.11)
  3. 手把手教你36小时搭建无人超市系统 !(附代码)
  4. java Poi导入exel表格的数据,入库
  5. img设置宽高不生效_便宜 好用 不掉盘 保姆级粒子云刷机攻略
  6. 曲奇饼案例运营管理_昆仑海岸 农村生活污水处理设施智慧运营管理项目荣获典型案例奖...
  7. Linux 命令之 dmidecode -- 显示机器的DMI信息
  8. 从零开始系统化学Android,值得收藏!
  9. 【java】java Integer 缓存 一定是 -128~127 吗
  10. Unity实现爆炸的伤害值计算
  11. 腾讯 WeGame 被迫下架《怪物猎人:世界》究竟该怪谁?| 畅言
  12. AJAX用法、HTTP头部信息
  13. jquery uploadify 相关文档 按钮样式显示不全 Google浏览器显示问题
  14. 逻辑回归:详细建模流程与例子代码
  15. python二级考试程序题批改_python开发一个自动批改本地Word作业的程序
  16. 会议日程安排问题python
  17. 01、Cadence使用记录之新建工程与基础操作(原理图绘制:OrCAD Capture CIS)
  18. linux 内存 参数,linux free命令参数及用法详解(linux查看内存命令)
  19. 【实战篇】40 # 如何实现3D地球可视化?
  20. TINA导入Ti官网器件

热门文章

  1. Thinkphp使用消息队列Queue
  2. 查找算法---二分查找(递归方式)
  3. 关于 printf(%*.*s/n,m,n,ch) 的问题
  4. Java并发编程实战之基于生产者消费者模式的日志服务读书笔记
  5. 深度解读 Flink 1.11:流批一体 Hive 数仓
  6. Kotlin实战【三】表示与选择
  7. js 栈 html标签修复,js 打印错误堆栈
  8. dotween unity 延时_使用DoTween在Unity中制作队列(Sequence)动画
  9. 耳机使用说明书 jbl ua_怎么挑选一款适合自己的蓝牙耳机?看看这篇文章!
  10. html的经典语法大全,HTML语法大全