
本文是《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)
张量、函数和计算图
上述代码定义了下面的计算图:

在这个单层神经网络中,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允许您计算所谓的雅可比乘积,而不是实际的梯度。
对于向量函数:
其中:
此时,向量y的关于向量x的梯度可由雅可比矩阵给出:
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)) 。
进一步阅读
芸芸小站首发,阅读原文:http://xiaoyunyun.net/index.php/archives/302.html