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

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

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

数据可分为:叶子节点(leaf node)和非叶子节点;叶子节点是用户创建的节点,不依赖其它节点;它们表现出来的区别在于反向传播结束之后,非叶子节点的梯度会被释放掉,只保留叶子节点的梯度,这样就节省了内存。如果想要保留非叶子节点的梯度,可以使用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() and torch.autograd.grad() ,他们的区别在于前者是给叶子节点填充.grad字段,而后者是直接返回梯度给你,我会在后面举例说明。还需要知道y.backward()其实等同于torch.autograd.backward(y)

一个简单的求导例子是:y=(x+1)∗(x+2)y=(x+1)*(x+2)y=(x+1)∗(x+2),计算 ∂y/∂x\partial y /\partial x∂y/∂x ,假设给定 x=2x=2x=2, 先画出计算图:

手算的话,
∂y∂x=∂y∂a∂a∂x+∂y∂b∂b∂x=x+2+x+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+x+1=7 ∂x∂y​=∂a∂y​∂x∂a​+∂b∂y​∂x∂b​=x+2+x+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[0])
>>>tensor(7.)

因为指定了输出y,输入x,所以返回值就是∂y/∂x\partial y/\partial x∂y/∂x这一梯度,完整的返回值其实是一个元组,保留第一个元素就行,后面元素是?

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

手算的话:
∂z∂x=2xy→12,∂z∂y=x2→4,∂2z∂x2=2y→6\frac{\partial z}{\partial x}=2xy \to12,\frac{\partial z}{\partial y}=x^2 \to 4,\frac{\partial^2z}{\partial x^2}=2y \to 6 ∂x∂z​=2xy→12,∂y∂z​=x2→4,∂x2∂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])
# grad_y = torch.autograd.grad(outputs=z, inputs=y) 无法对y进行求导了
>>>tensor(12.)

为什么不在这里面同时也求对y的导数呢?因为无论是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的运算方式,需要使用creat_graph=True在保留原图的基础上再建立额外的求导计算图,也就是会把∂z/∂x=2xy\partial z/\partial x=2xy∂z/∂x=2xy这样的运算存下来。

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

# autograd.grad() + backward()
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这个一阶导继续求导:

# backward() + autograd.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从开始回传,我们试一下:

# backward() + backward()
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()时默认会累加梯度,也就是12+6=18,需要手动把前一次的梯度清零:

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+1
y.backward()
print(x.grad)
>>>RuntimeError: grad can be implicitly created only for scalar outputs

报错了,因为只能标量对标量,标量对向量求梯度,xxx可以是标量或者向量,但yyy只能是标量;所以只需要先将$$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\prime=y.sum()=x_1^2+x_2^2, \\ \frac{\partial y\prime}{\partial x_1}=2x_1 \to 2,\frac{\partial y\prime}{\partial x_2}=2x_2 \to 4 x=[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]\boldsymbol y=[y_1,y_2]y=[y1​,y2​]是一个向量,
J=[∂y∂x1,∂y∂x2]=[∂y1∂x1∂y1∂x2∂y2∂x1∂y2∂x2]\boldsymbol J=[\frac{\partial \boldsymbol y}{\partial x_1},\frac{\partial \boldsymbol 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​​和∂y2∂x1\frac{\partial y_2}{\partial x_1}∂x1​∂y2​​都是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}]^\mathsf{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.])

也可以使用autograd。上面和这里的torch.ones_like(x) 位置指的就是雅可比矩阵右乘的那个向量。

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
    

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

  1. pytorch求导总结(torch.autograd)

    1.Autograd 求导机制 我们在用神经网络求解PDE时, 经常要用到输出值对输入变量(不是Weights和Biases)求导: 例如在训练WGAN-GP 时, 也会用到网络对输入变量的求导,py ...

  2. 常见激活函数及其求导相关知识

    文章目录 Sigmoid函数 Sigmoid函数介绍 Sigmoid函数求导 tanh 函数 tanh 函数介绍 tanh 函数求导 Relu函数 Relu函数介绍 Relu函数求导 Softmax函 ...

  3. 高数——(反)三角函数及求导相关公式应用

    为什么会有这篇文章呢?源于一道题... ,这是一道求不定积分的课后习题.当晚在求这道题时,我和我的室友先完成了以下步骤: 设, 做到这一步之后,我们突然就不知道该这么变式了,因为需要将t变回x,而无法 ...

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

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

  5. 【2】激活函数与Loss函数求导

    文章目录 1.常见的激活函数与梯度 Sign函数 Sigmoid函数 Tanh函数 ReLU函数 Softmax函数 Leaky ReLU函数 SELU函数 Softplus函数 2.常见的Loss与 ...

  6. autograd-自动求导系统

    torch.autograd 权值的更新需要求解梯度,pytorch提供了自动求导系统,我们只需要搭建前向传播计算图,由autograd的方法就可以得到所有张量的梯度. 其中最常用的方法是backwa ...

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

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

  8. PyTorch基础(二)-----自动求导Autograd

    一.前言 上一篇文章中提到PyTorch提供了两个重要的高级功能,分别是: 具有强大的GPU加速的张量计算(如NumPy) 包含自动求导系统的的深度神经网络 第一个特性我们会在之后的学习中用到,这里暂 ...

  9. PyTorch定义新的自动求导(Autograd) 函数

    PyTorch定义新的自动求导(Autograd) 函数 pytorch官网提供了定义新的求导函数的方法(链接放在文章末尾了),官网举的例子,可能我比较笨,愣是反应了好一会儿才理解.这篇博客主要讲 P ...

最新文章

  1. DL之DNN优化技术:利用Batch Normalization(简介、入门、使用)优化方法提高DNN模型的性能
  2. 可滚动Widget SingleChildScrollView
  3. 自定义JSP中的Taglib标签之四自定义标签中的Function函数
  4. 小红书再度出拳整治医美 首批处置违规笔记27.9万篇
  5. 实现putty基于密钥的安全登录
  6. Android常见概念
  7. 文件的长度,跟FileReader读到的长度,不一样
  8. sqlserver2010教程百度云盘_SQLServer数据库基础教程(72集),全套视频教程学习资料通过百度云网盘下载...
  9. 好用的局域网共享工具有哪些?win10系统如何设置?
  10. 计算机专业学校课程改革,中专学校计算机专业课程改革初探
  11. VS编译器 :LNK链接错误汇总:LNK2001 / LNK2005 / LNK2019 / LNK1120
  12. 软件体系结构复习指南
  13. 谷歌退出中国市场传言推动百度股价大涨4.8%【转载】
  14. 2019百度网盘破解不限速
  15. OSChina 周一乱弹 —— 大学老教授说你媳妇在幼儿园
  16. 微信报餐小程序,食堂报餐统计,微信订餐小程序点餐公司订餐微信小程序源码开发
  17. 页面跳转的两种方式(转发和重定向)区别详解:
  18. 《老路用得上的商学课》21-30学习笔记
  19. SpaceX龙飞船顺利返航,溅落太平洋!后续太空任务已安排上
  20. MyEclipse个性设置 .

热门文章

  1. oracle 加载数据戽_走进大数据丨 ETL - Load(数据加载)
  2. Java中,一切皆是对象——java中的对象类型与基本数据类型的区别
  3. Redis操作命令总结
  4. 解决Nginx出现403 forbidden (13: Permission denied)报错的四种方法
  5. CI框架中pdo的使用方法
  6. 反思转变:多努力以积累  多动脑以应变
  7. 序数是什么意思_序数与基数
  8. 戴尔服务器改win7系统,戴尔dell预装win10怎么改win7系统
  9. tomcat java 参数乱码_javaweb乱码(tomcat服务器)
  10. python实现分布式_Python如何快速实现分布式任务