动手学深度学习 | 自动求导 | 05

自动求导

链式法则,如果扩展到向量,最最重要的还是看形状。

<x,w>这是内积的写法。

自动求导涉及到一个计算图的概念,虽然Pytorch不用要求大家理解计算图,但是理解了对使用TensorFlow等都是有好处的。

计算图其实本质上就和刚刚求导链式法则的过程。

显示构造,就是先构造好这个公式,然后再带入数值计算,一般数学上都是属于显示构造。

正向累积就是正常计算,反向传播是从结果不停的反向进行求导。

反向从根节点向下扫,可以保证每个节点只扫一次;

正向从叶节点向上扫,会导致上层节点可能需要被重复扫多次(正向中子节点比父节点先计算,因此也无法像反向那样把本节点的计算结果传给每一个子节点)

自动求导实现

image-20210918112130183

操作总结

import torch

x = torch.arange(4.)
# 等价于 x = torch.arange(4.,requires_grad=True)
x.requires_grad_(True) # 需要存储中间结果
print(x.grad) # 查看x的梯度,默认为None

y = 2*torch.dot(x,x) # 点称
y # tensor(28., grad_fn=<MulBackward0>)

y.backward() # 对y进行反向传播
x.grad # 查看BP得到的梯度

# y=2x^2 y'=2x,可以判断BP得到的梯度是否是对的
4*x == x.grad

x.grad_zero_() # Pytorch的梯度会累积,这里是将0写入梯度中
y = x.sum() # sum() 相当于点乘一个全为1的向量 
y.backward()
x.grad

x.grad_zero_()
y = x*x
# x*x == sum(y·y)
y.sum().backward()
x.grad


# 将计算移动到计算图之外
x.grad_zero_()
y = x*x
u = y.detach()
z = u*x

z.sum().backward()
x.grad,x.gard==u

# 及时构建函数的计算图需要通过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

# size=() 标量
# a是一个随机变量,并且需要记录梯度
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()

# f()可以理解成是一个计算图的流程,最终得到d
# 因为是标量,所以可以用斜率来验证得到的梯度是否是正确的
a.grad == d / a

QA

  1. PPT上的显示构造和隐式构造为什么看起来差不多?

显示构造:把整个计算图都先设计完成,然后再给值

隐式构造:就直接写程序流程,然后框架会在后台进行计算图的构建

实际使用过程中,显示构造计算图会麻烦很多!

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

loss通常是一个标量,精度也好,很多机器学习的loss都是一个标量,如果loss是一个向量的loss的话,那就会变得很麻烦。向量向下走就是就是矩阵,矩阵往下走四维矩阵,网络程度一深,那么就会成为一个非常大的张量。

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

是的,如果后面你的神经网络有多个损失函数的话,你是需要做梯度累加的,这也是Pytorch默认累加梯度的一个理由。

posted @ 2021-09-18 12:08  RowryCho  阅读(542)  评论(0编辑  收藏  举报