PyTorch版《动手学深度学习》 2.3自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient).本节将介绍如何使用PyTorch提供的autograd模块来自动求梯度.

from torch import autograd,torch

2.3.1 简单例子

我们先来看一个简单例子:对函数 y = 2 x T x y=2x^Tx y=2xTx求关于列向量 x x x的梯度.我们先创建变量 x x x,并赋初值.

x = torch.arange(4).float().reshape(4,1)            # 缺失情况下默认 requires_grad = False
x
tensor([[0.],
        [1.],
        [2.],
        [3.]])

为了求有关变量 x x x的梯度,我们需要先调用requires_grad_函数将变量 x x x设置为可导.

x.requires_grad_()
tensor([[0.],
        [1.],
        [2.],
        [3.]], requires_grad=True)

下面定义有关变量 x x x的函数.

y = 2 * torch.mm(x.t(),x)
y
tensor([[28.]], grad_fn=<MulBackward0>)

y y y是通过 x x x计算生成的,由于依赖的 x x x需要求导,所以 y y y也需要求导.

x.requires_grad,y.requires_grad
(True, True)

每个Tensor都有一个grad_fn属性,该属性即创建该Tensor的Function,表明其是通过哪些运算得到的.若其由某些运算生成,则grad_fn返回一个与这些运算相关的对象,否则返回None.需要注意的是, x x x是直接创建的,称为叶节点,它没有grad_fn,而 y y y是通过乘法操作创建的,所以它有一个为MulBackward的grad_fn.

print(x.is_leaf,y.is_leaf) 
print(x.grad_fn,y.grad_fn)
True False
None <MulBackward0 object at 0x00000221430A38E0>

由于 x x x的形状是(4,1),y是一个标量.接下来我们我们可以通过backward函数自动求梯度.

y.backward()

回传的梯度将累积到 x x x的grad属性中.函数 y = 2 x T x y=2x^Tx y=2xTx关于 x x x的梯度应为 4 x 4x 4x,现在我们来验证一下求出来的梯度是正确的.

assert ((x.grad - 4 * x).norm().item()) == 0 
x.grad
tensor([[ 0.],
        [ 4.],
        [ 8.],
        [12.]])

需要注意的是,grad在反向传播过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零.

x.grad.data.zero_()
tensor([[0.],
        [0.],
        [0.],
        [0.]])

2.3.2 计算图

计算图是PyTorch对神经网络的具体实现方式.以 z = w x + b z = wx+b z=wx+b为例, x x x为输入, w w w b b b为网络需要学习的参数, z z z为输出.由于 x x x w w w b b b都是用户创建的,因此都为叶节点,最终输出 z z z作为根节点.

x = torch.randn(1)
w = torch.ones(1,requires_grad = True)
b = torch.ones(1,requires_grad = True)
print(x.is_leaf,w.is_leaf,b.is_leaf)
print(x.requires_grad,w.requires_grad,b.requires_grad)
True True True
False True True

在进行前向计算中,由计算生成的变量都不是叶结点.

y = w * x
z = y + b
print(y.is_leaf,z.is_leaf)
print(y.requires_grad,z.requires_grad)
print(y.grad_fn,z.grad_fn)
False False
True True
<MulBackward0 object at 0x00000221430BF4F0> <AddBackward0 object at 0x00000221430BF760>

当多个输出需要同时进行梯度回传时,需要将retain_graph设置True,从而保证在计算多个输出的梯度时互不影响.

z.backward(retain_graph = True)
w.grad,b.grad
(tensor([0.6390]), tensor([1.]))

backward函数还有一个需要传入的参数为grad_variables,其代表了根节点的导数,也可以看作根节点各部分的权重系数.因为PyTorch不允许Tensor对Tensor求导,求导数时都是标量对Tensor进行求导.因此,如果根节点是Tensor,需要配以对应大小的权重,并求和得到标量,再反向传播.如果根节点的值是标量,则该参数可省略,默认为1.

x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
y = 2 * x
z = y.view(2, 2)
z
tensor([[2., 4.],
        [6., 8.]], grad_fn=<ViewBackward0>)

现在 z z z不是一个标量,所以在调用backward时需要传入一个和 y y y同形的权向量进行加权求和得到一个标量.

v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
z.backward(v)
x.grad
tensor([2.0000, 0.2000, 0.0200, 0.0020])

需要注意的是,x.grad是和 x x x同形的Tensor.

2.3.3 取消自动计算梯度

如果不想要某些Tensor被继续追踪,有三种方式可以取消自动计算梯度.调用requires_grad_函数将变量设置为不可导.

a = torch.randn(1,4,requires_grad=True)
a.requires_grad_(False)
a.requires_grad
False

Tensor.detach()函数生成的数据默认requires_grad为False.

b = torch.randn(1,4,requires_grad=True)
b_copy = b.detach()
b_copy.requires_grad
False

还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数的梯度.

c = torch.randn(1,4,requires_grad=True)
with torch.no_grad():
    d = c + 2
e = c + 2
d.requires_grad,e.requires_grad
(False, True)

2.3.4 对Python控制流求梯度

即使函数的计算图包含了Python的控制流(如条件和循环控制),我们也有可能对变量求梯度.

def f(a):
    b = a * 2
    while b.norm().item() < 1000:
        b = b * 2
    if b.sum().item() > 0:
        c = b 
    else:
        c = 1000 * b
    return c

我们像之前一样调用backward函数求梯度.

a = torch.randn(1,requires_grad=True)
c = f(a)
c.backward()

我们来分析一下上面定义的 f f f函数.事实上,给定任意输入 a a a,其输出必定是 f ( a ) = x ∗ a f(a)=x*a f(a)=xa的形式,其中标量系数 x x x的值取决于输入 a a a.由于 c = f ( a ) c=f(a) c=f(a)有关 a a a的梯度为 x x x,且值为 c / a c/a c/a,我们可以像下面这样验证对本例中控制流求梯度的结果.

a.grad == c/a
tensor([True])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值