[翻译]PyTorch官方教程中文版:自动微分

matrix.jpg

本文是《PyTorch官方教程中文版》系列文章之一,目录链接:[翻译]PyTorch官方教程中文版:目录

本文翻译自PyTorch官方网站,链接地址:Autograd

训练神经网络时,最常用的是反向传播算法(back propagation),该算法使用损失函数计算出的梯度(gradient)来调整模型的权重。

为了计算这些梯度,PyTorch有一个名为 torch.autograd 的内置微分引擎,它支持任何计算图的梯度自动计算。

考虑最简单的单层神经网络,具有输入 x、参数 w 、 b,以及损失函数,它可以在 PyTorch 中按以下方式定义:

import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

张量、函数和计算图

上述代码定义了下面的计算图:

comp-graph.png

在这个单层神经网络中,w 和 b 是需要优化的参数。为了优化这些参数,我们需要使用损失函数计算这些变量的梯度。为了使用损失函数自动计算梯度,我们设置了张量 w 和 b 的 requires_grad 属性。

提示:您可以在创建张量时设置 requires_grad 的值,也可以稍后使用 x.requires_grad_(True) 方法设置。

计算图上我们用来对张量做计算的函数实际上是对象 Function,这个对象知道如何进行前向计算,并且知道在反向传播时如何计算其导数。对反向传播函数的引用保存在张量的 grad_fn 属性中。更多信息请参考此文档

print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")

上述代码输出:

Gradient function for z = <AddBackward0 object at 0x7f9851144a60>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f985114a7d0>

计算图

为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数。为了计算这些导数,我们调用 loss.backward(),然后从 w.grad 和 b.grad 中检索值。(译者:此段省略两公式,因为“一篇文章里每多一个公式,就会有一半读者被劝退”,而且这两个公式也不重要,可有可无。)

loss.backward()
print(w.grad)
print(b.grad)

上述代码输出:

tensor([[0.3313, 0.0626, 0.2530],
        [0.3313, 0.0626, 0.2530],
        [0.3313, 0.0626, 0.2530],
        [0.3313, 0.0626, 0.2530],
        [0.3313, 0.0626, 0.2530]])
tensor([0.3313, 0.0626, 0.2530])
  • 提示1:我们只能获取计算图中 requires_grad 属性值为 True叶子节点的 grad 属性,其他节点的梯度将不可用。
  • 提示2:出于性能考虑,我们只能在计算图上调用一次 backward 函数,如果你需要多次调用 backward,那么需要给 backward 函数的参数“retain_graph”传递True,即写成 “backward(retain_graph=True)” 。

禁用梯度跟踪

所有属性“requires_grad”值为 True 的张量默认情况下都开启了跟踪计算历史功能,同时支持梯度计算。但是在某些情况下,我们不想要这样做,例如:当我们已经训练好了模型,并只想将其用来做预测(推导)时,即我们只想通过网络进行前向计算时(只需要调用 forward ,不需要 backward)。针对这种情况,我们可以通过将代码嵌入到 torch.no_grad() 块中来停止跟踪历史。

z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)

上述代码输出:

True
False

达到同样目的的另一种方法是使用 detach() 方法:

z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)

上述代码输出:

False

下列情况应该禁用梯度跟踪:

  • 需要把神经网络中的一些参数标记为已冻结参数(frozen parameters)
  • 当只需要前向操作时,因为不跟踪梯度时张量的计算更有效率,禁用梯度跟踪可以提高计算速度。

计算图的更多信息

从概念上讲,autograd 在由 Function 对象组成的有向无环图 (DAG) 中记录数据(张量)、所有执行的操作、生成的新张量。在此 DAG 中,叶子是输入张量,根是输出张量,只需要通过从根到叶跟踪此图,就可以使用链式规则自动计算梯度。

在正向传播时,autograd 同时做下面两件事:

  • 执行请求的计算并得到计算结果(结果是张量)。
  • 在DAG中维护每个计算的 gradient 函数。

当 .backward() 被调用时,autograd 这样做:

  • 用每个 .grad_fn 计算梯度。
  • 将这些梯度累计在相应张量的 .grad 属性中。
  • 使用链式规则,一路计算到叶张量。

提示: 在PyTorch中,DAG 是动态的,这一点非常重要。每次 .backward() 调用结束后,autograd 开始填充一个新计算图,这正是允许您在模型中使用控制流语句的原因。必要时,您可以在每次迭代时更改形状、大小和操作。

不推荐阅读:张量梯度和雅可比积

(译者:这一节看不懂就算了,因为我也看不懂,并不影响使用PyTorch)

在某些情况下,我们有一个标量损失函数,需要计算相对于某些参数的梯度,但是输出函数是任意张量。在这种情况下,PyTorch允许您计算所谓的雅可比乘积,而不是实际的梯度。
对于向量函数:
tempsnip.png
其中:
x.PNG
y.PNG
此时,向量y的关于向量x的梯度可由雅可比矩阵给出:
m.PNG
PyTorch 不是计算雅可比矩阵本身,而是允许您计算雅可比乘积。调用 backward函数时v作为参数,v的尺寸必须和原始张量一致。计算乘积的方法:

inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")

上述代码输出:

First call
tensor([[4., 2., 2., 2., 2.],
        [2., 4., 2., 2., 2.],
        [2., 2., 4., 2., 2.],
        [2., 2., 2., 4., 2.]])

Second call
tensor([[8., 4., 4., 4., 4.],
        [4., 8., 4., 4., 4.],
        [4., 4., 8., 4., 4.],
        [4., 4., 4., 8., 4.]])

Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],
        [2., 4., 2., 2., 2.],
        [2., 2., 4., 2., 2.],
        [2., 2., 2., 4., 2.]])

请注意,当我们使用相同的参数第二次调用 backward 时,得到的梯度值是不同的。这是因为在 backward 执行时,PyTorch回累积梯度,即计算得到的梯度值被加到所有叶子节点的 grad 属性上。如果要正确的计算梯度,则需要在每次计算之前将 grad 属性清零,在真正训练时,优化器可以帮助我们做这个工作。

注意:我们不带参数调用 backward() 函数,基本上等同于 backward(torch.tensor(1.0)) 。

进一步阅读


芸芸小站首发,阅读原文:


最后编辑:2023年08月11日 ©版权所有,转载须保留原文链接

发表评论

正在加载 Emoji