序言:在训练一个神经网络时,梯度的计算是一个关键的步骤,它为神经网络的优化提供了关键数据。但是在面临复杂神经网络的时候导数的计算就成为一个难题,要求人们解出复杂、高维的方程是不现实的。这就是自动微分出现的原因,当前最流行的深度学习框架如PyTorch、Tensorflow等都提供了自动微分的支持,让人们只需要很少的工作就能神奇般地自动计算出复杂函数的梯度。

PyTorch的autograd简介

Tensor是PyTorch实现多维数组计算和自动微分的关键数据结构。一方面,它类似于numpy的ndarray,用户可以对Tensor进行各种数学运算;另一方面,当设置.requires_grad = True之后,在其上进行的各种操作就会被记录下来,用于后续的梯度计算,其内部实现机制被成为动态计算图(dynamic computation graph)

Variable变量:在PyTorch早期版本中,Tensor只负责多维数组的运算,自动微分的职责是Variable完成的,因此经常可以看到因而产生的包装代码。而在0.4.0版本之后,二者的功能进行了合并,使得自动微分的使用更加简单了。

autograd机制能够记录作用于Tensor上的所有操作,生成一个动态计算图。图的叶子节点是输入的数据,根节点是输出的结果。当在根节点调用.backward()的时候就会从根到叶应用链式法则计算梯度。默认情况下,只有.requires_gradis_leaf两个属性都为True的节点才会被计算导数,并存储到grad中。

动态计算图本质上是一个有向无环图,因此“叶”和“根”的称呼是不太准确的,但是这种简称可以帮助理解,PyTorch的文档中仍然采用这种说法。

requires_grad属性

requires_grad属性默认为False,也就是Tensor变量默认是不需要求导的。如果一个节点的requires_grad是True,那么所有依赖它的节点requires_grad也会是True。换言之,如果一个节点依赖的所有节点都不需要求导,那么它的requires_grad也会是False。在反向传播的过程中,该节点所在的子图会被排除在外。

>>> x = torch.randn(5, 5)  # requires_grad=False by default
>>> y = torch.randn(5, 5) # requires_grad=False by default
>>> z = torch.randn((5, 5), requires_grad=True)
>>> a = x + y
>>> a.requires_grad
False
>>> b = a + z
>>> b.requires_grad
True

Function

我们已经知道PyTorch使用动态计算图(DAG)记录计算的全过程,那么DAG是怎样建立的呢?一些博客认为DAG的节点是Tensor(或说Variable),这其实是不准确的。DAG的节点是Function对象,边表示数据依赖,从输出指向输入。因此Function类在PyTorch自动微分中位居核心地位,但是用户通常不会直接去使用,导致人们对Function类了解并不多。

每当对Tensor施加一个运算的时候,就会产生一个Function对象,它产生运算的结果,记录运算的发生,并且记录运算的输入。Tensor使用.grad_fn属性记录这个计算图的入口。反向传播过程中,autograd引擎会按照逆序,通过Function的backward依次计算梯度。

backward函数

backward函数是反向传播的入口点,在需要被求导的节点上调用backward函数会计算梯度值到相应的节点上。backward需要一个重要的参数grad_tensor,但如果节点只含有一个标量值,这个参数就可以省略(例如最普遍的loss.backward()loss.backward(torch.tensor(1))等价),否则就会报如下的错误:

Backward should be called only on a scalar (i.e. 1-element tensor) or with gradient w.r.t. the variable

要理解这个参数的内涵首先要从数学角度认识梯度运算。如果有一个向量函数$\vec{y}=f(\vec{x})$,那么$\vec{y}$相对于$\vec{x}$的梯度是一个雅克比矩阵(Jacobian matrix):

$$\begin{split}J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\end{split}$$

本文讨论的主角torch.autograd本质上是一个向量-雅克比乘积(*vector-Jacobian product*)的计算引擎,即计算$v^{T}\cdot J$,而所谓的参数grad_tensor就是这里的$v$。由定义易知,参数grad_tensor需要与Tensor本身有相同的size。通过恰当地设置grad_tensor,容易计算任意的$\frac{\partial y_{m}}{\partial x_{n}}$求导组合。

反向传播过程中一般用来传递上游传来的梯度,从而实现链式法则,简单的推导如下所示:

$$\begin{split}J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)\end{split}$$

(注:这里的计算结果被转置为列向量以方便查看)


注意:梯度是累加的

backward函数本身没有返回值,它计算出来的梯度存放在叶子节点的grad属性中。PyTorch文档中提到,如果grad属性不为空,新计算出来的梯度值会直接加到旧值上面。

为什么不直接覆盖旧的结果呢?这是因为有些Tensor可能有多个输出,那么就需要调用多个backward。叠加的处理方式使得backward不需要考虑之前有没有被计算过导数,只需要加上去就行了,这使得设计变得更简单。因此我们用户在反向传播之前,常常需要用zero_grad函数对导数手动清零,确保计算出来的是正确的结果。

PyTorch自动微分基本原理的更多相关文章

  1. PyTorch 自动微分示例

    PyTorch 自动微分示例 autograd 包是 PyTorch 中所有神经网络的核心.首先简要地介绍,然后训练第一个神经网络.autograd 软件包为 Tensors 上的所有算子提供自动微分 ...

  2. PyTorch 自动微分

    PyTorch 自动微分 autograd 包是 PyTorch 中所有神经网络的核心.首先简要地介绍,然后将会去训练的第一个神经网络.该 autograd 软件包为 Tensors 上的所有操作提供 ...

  3. pytorch学习-AUTOGRAD: AUTOMATIC DIFFERENTIATION自动微分

    参考:https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autog ...

  4. <转>如何用C++实现自动微分

    作者:李瞬生转摘链接:https://www.zhihu.com/question/48356514/answer/123290631来源:知乎著作权归作者所有. 实现 AD 有两种方式,函数重载与代 ...

  5. MindSpore:自动微分

    MindSpore:自动微分 作为一款「全场景 AI 框架」,MindSpore 是人工智能解决方案的重要组成部分,与 TensorFlow.PyTorch.PaddlePaddle 等流行深度学习框 ...

  6. 附录D——自动微分(Autodiff)

    本文介绍了五种微分方式,最后两种才是自动微分. 前两种方法求出了原函数对应的导函数,后三种方法只是求出了某一点的导数. 假设原函数是$f(x,y) = x^2y + y +2$,需要求其偏导数$\fr ...

  7. 自动微分(AD)学习笔记

    1.自动微分(AD) 作者:李济深链接:https://www.zhihu.com/question/48356514/answer/125175491来源:知乎著作权归作者所有.商业转载请联系作者获 ...

  8. (转)自动微分(Automatic Differentiation)简介——tensorflow核心原理

    现代深度学习系统中(比如MXNet, TensorFlow等)都用到了一种技术——自动微分.在此之前,机器学习社区中很少发挥这个利器,一般都是用Backpropagation进行梯度求解,然后进行SG ...

  9. 【tensorflow2.0】自动微分机制

    神经网络通常依赖反向传播求梯度来更新网络参数,求梯度过程通常是一件非常复杂而容易出错的事情. 而深度学习框架可以帮助我们自动地完成这种求梯度运算. Tensorflow一般使用梯度磁带tf.Gradi ...

随机推荐

  1. Python入门之元组

    一.什么是元祖 元祖是不可变类型(列表是可变类型) 为什么要设计元祖这样不可变类型?因为一旦创建了不可变类型的对象,对象内部的所有数据就不能被修改了,这样避免了 由于修改数据导致的错误.此外,对于不可 ...

  2. 40个超有趣的Linux命令行彩蛋和游戏

    40个有趣的Linux命令行彩蛋和游戏,让你假装成日理万机的黑客高手.附一键安装脚本,在树莓派和ubuntu云主机上亲测成功,有些还可以在Windows的DOS命令行中运行. 本文配套B站视频:40个 ...

  3. 三星 S10 运行 Ubuntu 系统

    导读 DeX 是一种模仿桌面操作系统的用户 UI 界面,把支持 DeX 的三星手机用数据线连上外置显示器,用户就可以获得一种类似桌面系统的使用体验. 三星 S8.Note 8.S9.Note 9.S1 ...

  4. c++存储区域

    来自:https://www.cnblogs.com/simonote/articles/3146038.html 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储 ...

  5. 如何通过DICOM的tag来判断3D图像的方向

    在DICOM标准里,有三个TAG与成像的方向相关. 参考来源:Kitware关于DICOM方向的说明 http://public.kitware.com/IGSTKWIKI/index.php/DIC ...

  6. 2017北京网络赛 J Pangu and Stones 区间DP(石子归并)

    #1636 : Pangu and Stones 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 In Chinese mythology, Pangu is the fi ...

  7. python-python基础3

    本章内容: 函数 递归 高阶函数 一.函数 一个函数一般完成一项特定的功能 函数使用     函数需要先定义     使用函数,调用

  8. 获取QQ群中的所有群友QQ

    package com.jm.mail.tools; import java.io.BufferedReader; import java.io.IOException; import java.io ...

  9. 12541:TNS无监听状态

    上次在项目上遇见数据库报这个问题,然后网上几乎都是让重新进行配置数据库.配置多次之后还是无效,最后找到了问题的根源. 使用的是Oracle数据库,用PLSQL登录报的这个错误. 在计算机全局搜索:li ...

  10. P1061 判断题

    P1061 判断题 转跳点: