欢迎光临散文网 会员登陆 & 注册

07 自动求导【动手学深度学习v2】

2021-11-15 11:16 作者:如果我是泡橘子  | 我要投稿

向量链式法则




例1:


  • 线性回归的例子:利用链式法则对线性回归求导
  • <x,w>:x 和 w 做内积
  • 上图中求 z 对 w 的导数时,第二项 b 对 a 的导数在代入时应该应该写作 d (a - y) ,图中少了一对括号



例2:


  • 上图中求 z 对 w 的导数时,第二项 b 对 a 的导数在代入时应该应该写作 d (a - y) ,图中少了一对括号
  • 图中 ||b|| 表示求 b 的范数



自动求导


计算一个函数在指定值上的导数



自动求导与符号求导、数值求导的区别

  • 符号求导:指定一个函数,然后求函数的导数(这里可以理解为对某一个函数进行求导,求出其导函数,而不是一个具体的值),它是一个显式的计算
  • 数值求导:给定一个未知函数(不知道其具体表达式),然后利用数值去拟合它的导数(这里有点像利用极限去求导的过程)



计算图


虽然使用pytorch不用去理解计算图,但是有必要知道其内部的工作原理,便于使用其它框架(如tensorflow等)时去理解计算图

  • 加入中间变量 a 、 b ,将他们做成一个基本的计算子
  • 每个圆圈表示一个操作,也可以表示成为一个输入


显式构造

(Tensorflow / Theano / MXNet)


from mxnet import sym

a = sym.var()

b = sym.var()

c = 2 * a + b

# bind data into a and b later

  • 此处会报错“No module named 'mxnet'”,直接在conda环境下使用 pip install mxnet 或者沐神之前用的在程序第一行使用 !pip install mxnet 这行代码进行安装也可

输出:

TypeError: var() missing 1 required positional argument: 'name'

  • 这里会提示缺少一个位置参数
  • 这里应该不是一段完整的可运行的代码,只是为了介绍显式构造


隐式构造

(PyTorch / MXNet)


from mxnet import autograd , nd

with autograd.record():

    a = nd.ones((2,1))

    b = nd.ones((2,1))

    c = 2 * a + b

输出:

  • 没有输出,但也不会报错





自动求导的两种模式



反向积累




反向累积总结



复杂度


  • 内存复杂度:深度神经网络耗费GPU资源的根源,做梯度的时候需要存储前面的运行结果
  • 正向累积:不管神经网络的深度有多深都不需要存储任何的结果,但是因为需要对每一层计算梯度,所以每计算一个梯度都需要重新扫描一遍,计算复杂度太高





自动求导的实现



import torch

x = torch.arange(4.0)

x

输出:

tensor([0., 1., 2., 3.])


在计算 y 关于 向量x 的梯度之前,需要一个地方来存储梯度


x.requires_grad_(True)

# 等价于 x = torch.arange(4.0,requires_grad=True)

x.grad # 默认值是None


然后计算y


y = 2 * torch.dot(x,x)

y

  • y 等于 x 与 x 的内积的两倍

输出:

tensor(28., grad_fn=<DotBackward0>)

  • grad_fn:这里是隐式构造图,所以有一个求梯度的函数,告知 y 是从 x 计算过来的


通过调用反向传播函数来自动计算 y 关于 x 每个分量的梯度


y.backward()

x.grad

  • y.backward():求导
  • 利用 x.grad 来访问导数

输出:

tensor([ 0., 4., 8., 12.])


x.grad == 4 * x

  • 验证一下结果

输出:

tensor([True, True, True, True])


计算 x 的另一个函数


# 在默认情况下,PyTorch会累积梯度,因此在进行另外一个函数之前需要清除之前的值

x.grad.zero_()

y = x.sum()

y.backward()

x.grad

  • PyTorch中下划线表示重写内容,zero_表示把所有的梯度清零

输出:

tensor([1., 1., 1., 1.])


深度学习中,目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和

  • 深度学习中很少对一个向量进行求导,而只是对一个标量进行求导
  • 绝大部分情况下会先进行求和,再进行求导,这样子就是一个标量


# 对非标量调用 backward 需要传入一个 gradient 参数

x.grad.zero_()

y = x * x

# 等价于 y.backward(torch.ones(len(x)))

y.sum().backward()

x.grad

  • y.detach():把 y 当作一个常数而不是一个关于 x 的函数

输出;

tensor([0., 2., 4., 6.])

  • 以后将会用于在一些场景下将网络中的参数固定


即使构建函数的计算图需要通过Python控制流(例如:条件、循环或任意函数调用),仍然可以计算得到变量的梯度


def f(a):

    b = a * 2

    while b.norm() < 1000:

        b = b * 2

    if b.sum() > 0:

        c = b

    else:

        c = 100 * b

    return c


a = torch.randn(size = (),requires_grad=True)

d = f(a)

d.backward()

  • size=():表示是一个标量
  • require_grad=True:表示需要梯度
  • 在计算的时候,torch会把整个计算图存下来,然后倒着做一遍
  • 隐式计算的好处在于对于控制流做的更好一些,但是速度比较慢





Q&A


1、ppt上隐式构造和显式构造看起来为什么差不多?


  • 看上去是差不多,但是显式构造是先把整个计算写出来,然后再给值
  • 用python来实现一个函数和用数学来实现一个函数是不一样的
  • 很多情况下,在使用的时候显式构造非常不方便





2、需要正向和反向都要算一遍吗?


  • 在神经网络中求梯度的时候,需要正着算一遍,然后再反着算一遍
  • 自动求导的时候只需要反着算一遍,不需要正着再算一遍





3、为什么Pytorch会默认累积梯度?


  • 反向传播的时候需要把内存结果存起来,因此耗费内存比较多
  • PyTorch对内存的管理不太好,如果对于一个很大的批量,如果无法一次计算全计算出来,则可以将其划分成多次计算,然后进行累加,从而得到正确的结果
  • 当weight在不同的模型之间进行共享时也是有好处的饿





4、为什么是0246?是这么理解吗:x^2对x求导


2 * x 等于0246





5、为什么深度学习中一般对标量求导而不是对矩阵或者向量,如果我的loss是包含向量或者矩阵,那求导之前是不是要把它们变成标量?


  • 因为loss通常是一个标量
  • 如果loss是一个向量的话,随着神经网络深度的增加,它会变成一个很大的张量,无法进行计算





6、mxnet不用gluo的时候,除了用sym构建一个网络层,有什么自动求导的好的方法吗?比如我构建了一个loss公式后


跳过了。。。





7、今天讲的求导,会在pytorch里如何实现代码呢?


  • 讲了实现,但是没有讲具体公式的实现,有兴趣可以自己试一下


自动求导 QA P3 - 03:39






8、多个loss分别反向的时候是不是需要累积梯度?


  • 是的,神经网络中有多个loss的话,是需要进行累积的,这也是为什么pytorch默认是累加梯度的





9、为什么获取.grad前需要backward?


  • 不去backward的话就不会去计算梯度,这件事情占用很多的内存,所以需要手动backward计算梯度





10、求导过程是不是都是有向图,也就是可以用树状结构来表示,有没有其它环状的图结构?


  • 有,例如循环神经网络就能够变成一个环状的图结构





11、pytorch或mxnet框架审计上可以实现矢量的求导吗?



自动求导 QA P3 - 05:44






----end----

07 自动求导【动手学深度学习v2】的评论 (共 条)

分享到微博请遵守国家法律