pytorch autograd.grad
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相关推荐
- Pytorch autograd.grad与autograd.backward详解
Pytorch autograd.grad与autograd.backward详解 引言 平时在写 Pytorch 训练脚本时,都是下面这种无脑按步骤走: outputs = model(inputs ...
- [转]一文解释PyTorch求导相关 (backward, autograd.grad)
PyTorch是动态图,即计算图的搭建和运算是同时的,随时可以输出结果:而TensorFlow是静态图. 在pytorch的计算图里只有两种元素:数据(tensor)和 运算(operation) 运 ...
- 实操教程|PyTorch AutoGrad C++层实现
点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨xxy-zhihu@知乎 来源丨https://zhuanla ...
- c++ vector向量直接赋值_vector-Jacobian product 解释 ---- pytorch autograd
这篇文章将要解释pytorch autograd文档中的vector-Jacobian product. 文章由pytorch 官方文档中的这段话引出. 首先,雅各比矩阵J计算的是向量Y对于向量X的导 ...
- pytorch - autograd与逻辑回归
1.torch.autograd--自动求导系统 深度学习模型的训练就是不断更新权值,权值的更新需要求解梯度,梯度在模型训练中是至关重要的.然而求解梯度十分繁琐,pytorch提供自动求导系统.我们不 ...
- 【pytorch】 grad、grad_fn、requires_grad()、with torch.no_grad() 、net.train()、net.eval():记录一次奇怪的debug经历
刚开始接触pytorch框架时,最让我觉得神奇的就是它居然可以–自 动 求 导 ! 于是我开始尝试理解内部的运行机制,但很快放弃了,直接当成黑盒使用-- 最近又遇到一个奇怪的bug,让我不得不去学一下 ...
- torch.autograd.grad求二阶导数
1 用法介绍 pytorch中torch.autograd.grad函数主要用于计算并返回输出相对于输入的梯度总和,具体的参数作用如下所示: torch.tril(input, diagonal=0 ...
- Pytorch的grad、backward()、zero_grad()
grad 梯度 什么样的tensor有grad? pytorch中只有torch.float和复杂类型才能有grad. x = torch.tensor([1, 2, 3, 4], requires_ ...
- 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 ...
- Pytorch Autograd (自动求导机制)
Introduce Pytorch Autograd库 (自动求导机制) 是训练神经网络时,反向误差传播(BP)算法的核心. 本文通过logistic回归模型来介绍Pytorch的自动求导机制.首先, ...
最新文章
- usaco Feed Ratios
- QIIME 2教程. 26为QIIME 2开发新插件DevelopingPlugin(2020.11)
- 手把手教你36小时搭建无人超市系统 !(附代码)
- java Poi导入exel表格的数据,入库
- img设置宽高不生效_便宜 好用 不掉盘 保姆级粒子云刷机攻略
- 曲奇饼案例运营管理_昆仑海岸 农村生活污水处理设施智慧运营管理项目荣获典型案例奖...
- Linux 命令之 dmidecode -- 显示机器的DMI信息
- 从零开始系统化学Android,值得收藏!
- 【java】java Integer 缓存 一定是 -128~127 吗
- Unity实现爆炸的伤害值计算
- 腾讯 WeGame 被迫下架《怪物猎人:世界》究竟该怪谁?| 畅言
- AJAX用法、HTTP头部信息
- jquery uploadify 相关文档 按钮样式显示不全 Google浏览器显示问题
- 逻辑回归:详细建模流程与例子代码
- python二级考试程序题批改_python开发一个自动批改本地Word作业的程序
- 会议日程安排问题python
- 01、Cadence使用记录之新建工程与基础操作(原理图绘制:OrCAD Capture CIS)
- linux 内存 参数,linux free命令参数及用法详解(linux查看内存命令)
- 【实战篇】40 # 如何实现3D地球可视化?
- TINA导入Ti官网器件
热门文章
- Thinkphp使用消息队列Queue
- 查找算法---二分查找(递归方式)
- 关于 printf(%*.*s/n,m,n,ch) 的问题
- Java并发编程实战之基于生产者消费者模式的日志服务读书笔记
- 深度解读 Flink 1.11:流批一体 Hive 数仓
- Kotlin实战【三】表示与选择
- js 栈 html标签修复,js 打印错误堆栈
- dotween unity 延时_使用DoTween在Unity中制作队列(Sequence)动画
- 耳机使用说明书 jbl ua_怎么挑选一款适合自己的蓝牙耳机?看看这篇文章!
- html的经典语法大全,HTML语法大全