004 自动求微分

Ann- / 2023-05-13 / 原文

1. 一个简单的例子

假设我们想对对列向量x进行求导。

 上面的代码中,requires_grad=True表明x这个东西需要用到梯度; y.backward()执行反向传播,x.grad是x的梯度。

再看一个y=x.sum()的例子:

 在计算这个新的函数的梯度之前,需要将x的梯度清零,使用x.grad.zero_()方法。默认情况下,Pytorch会累计梯度,所以计算新的函数的梯度之前需要将原来的x的梯度清零。 

注意:一个标量函数关于向量x的函数是一个向量,并且这个梯度向量与x有着相同的形状。

 

2. 非标量变量的反向传播

y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的yx,求导的结果可以是一个高阶张量。

然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中), 但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。

 

3. 分离计算

当y是x的函数(y=x*x),z又是x,y的函数(z=y*x)时,我们可以通过下面的例子计算z对x的梯度:

有时,我们希望将某些计算移动到记录的计算图之外。 例如,我们想计算z关于x的梯度,但由于某种原因,希望将y视为一个常数, 并且只考虑到xy被计算后发挥的作用。

这里可以分离y来返回一个新变量u,该变量与y具有相同的值, 但丢弃计算图中如何计算y的任何信息。 换句话说,梯度不会向后流经ux。 因此,下面的反向传播函数计算z=u*x关于x的偏导数,同时将u作为常数处理。

 y.detach()函数返回一个新的张量,这个张量是从当前计算图上分离下来的,但仍指向原变量的内存位置,且这个tensor的requires_grad为False。得到的这个tensor永远也不需要计算其梯度。注意:返回的tensor和原始的tensor共享同一内存。

 

由于记录了y = x * x,我们仍然可以对y进行反向传播,计算y = x * x对x的梯度,即2 * x:

 

4. Python控制流的梯度计算

使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。 在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。

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()

我们现在可以分析上面定义的f函数。 请注意,它在其输入a中是分段线性的。 换言之,对于任何a,存在某个常量标量k,使得f(a)=k*a,其中k的值取决于输入a,因此可以用d/a验证梯度是否正确。

a.grad == d / a

输出结果: