PyTorch教程:autograd - 自动微分

目录

本文翻译自Pytorch官方文档《DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ

PyTorch 中所有神经网络的核心是 autograd 软件包。 让我们先简要地介绍一下,然后再训练第一个神经网络。

autograd 软件包为 Tensor 上的所有操作提供自动微分。 它是一个按运行定义的框架,这意味着您的反向传播(backprop)是由代码的运行方式定义的,并且每次迭代都可以不同。

让我们通过一些示例以更简单的方式展示这一点。

Tensor

torch.Tensor 是程序包的中心类。 如果将其属性 .requires_grad 设置为 True,它将开始跟踪对其的所有操作。 完成计算后,可以调用 .backward() 并自动计算所有梯度。 该 tensor 的梯度将累积到 .grad 属性中。

要停止 tensor 跟踪历史记录,可以调用 .detach() 将其与计算历史记录分离,并防止跟踪将来的计算。

为了防止跟踪历史记录(和使用内存),您还可以使用 with torch.no_grad(): 包装代码块。 这在评估模型时特别有用,因为模型可能具有可训练且 require_grad=True 的参数,但我们不需要梯度。

还有另外一个类对 autograd 实现非常重要:Function

TensorFunction 相互连接并建立一个无环图,该图对完整的计算历史进行编码。 每个 tensor 都有一个 .grad_fn 属性,该属性引用创建了 TensorFunction(用户创建的 Tensors 除外 —— 它们的 grad_fn is None)。

如果要计算导数,可以在 Tensor 上调用 .backward()。 如果 Tensor 是标量(即,它保存一个元素),则无需为 backward() 指定任何参数,但是,如果 Tensor 具有更多元素,则需要指定一个匹配形状的 tensor 作为 gradient 参数 。

import torch

创建一个张量并设置 require_grad=True 来跟踪它的计算

x = torch.ones(
    2, 2,
    requires_grad=True,
)
x
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

执行一个 tensor 操作

y = x + 2
y
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

y 是由于操作而创建的,因此具有 grad_fn

y.grad_fn
<AddBackward0 at 0x221a0d43310>

y 上执行更多的操作

逐元素运算

z = y * y * 3
z
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>)

求均值

out = z.mean()
out
tensor(27., grad_fn=<MeanBackward0>)

.requires_grad_(...) 就地改变一个已经存在的 tensor 的 requires_grad 标志。 默认输入标志是 False

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
a.requires_grad
False
a.requires_grad_(True)
a.requires_grad
True
b = (a * a).sum()
b.grad_fn
<SumBackward0 at 0x221a46d2370>

梯度

现在开始反向传播。 因为 out 包含单个标量,所以 out.backward() 等同于 out.backward(torch.tensor(1.))

out.backward()

打印梯度 d(out)/dx

x.grad
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

应该返回值均为 4.5 的矩阵。 让我们将 out 称为 Tensor o。 我们有

\begin{align} o = \frac{1}{4}\sum_i z_i \end{align}

\begin{align} z_i = 3(x_i+2)^2 \end{align}

\begin{align} z_i\bigr\rvert_{x_i=1} = 27 \end{align}

因此

\begin{align} \frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2) \end{align}

因此

\begin{align} \frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5 \end{align}

数学上,如果有向量值函数

\begin{equation*} \vec{y}=f(\vec{x}) \end{equation*}

那么 $\vec{y}$ 相对于 $\vec{x}$ 的梯度是雅可比矩阵:

\begin{equation*} J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\
\vdots & \ddots & \vdots\
\frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) \end{equation*}

一般来说,torch.autograd 是用于计算 vector-Jacobian 内积的引擎。 也就是说,给定任何向量

\begin{equation*} v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T} \end{equation*}

计算内积

\begin{equation*} $v^{T}\cdot J$ \end{equation*}

如果 v 刚好是标量函数 $$l=g\left(\vec{y}\right)$$ 的梯度,那就是

\begin{equation*} v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T} \end{equation*}

那么,根据链式规则,相对于 $$\vec{x}$$ 的 vector-Jacobian 内积将是 l 的梯度:

\begin{equation} J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\
\vdots & \ddots & \vdots\
\frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\
\vdots\
\frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\
\vdots\
\frac{\partial l}{\partial x_{n}} \end{array}\right) \end{equation}

注意,$$v^{T}\cdot J$$ 给出行向量,通过取 $$J^{T}\cdot v$$ 可以将其视为列向量。

vector-Jocobian 内积的这一特征使得将外部梯度输入具有非标量输出的模型变得非常方便。

现在,让我们看一个 vector-Jacobian product 的示例:

x = torch.randn(
    3,
    requires_grad=True,
)
y = x * 2
while y.data.norm() < 1000:
    y = y * 2   
y
tensor([ 198.6640, -968.9248,  377.3608], grad_fn=<MulBackward0>)

现在,在这种情况下,y 不再是标量。 torch.autograd 无法直接计算完整的雅可比行列式,但是如果我们只想要向量-雅可比积,只需将向量作为参数传递给 backward 即可:

v = torch.tensor(
    [0.1, 1.0, 0.0001],
    dtype=torch.float
)
y.backward(v)
x.grad
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])

您也可以通过使用 torch.no_grad() 将代码块包装在 .requires_grad=True 中,从而停止 autograd 在 Tensor 上跟踪历史记录

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)
True
True
False

或者使用 .detach() 获得具有相同内容但不需要梯度的新 Tensor:

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
True
False
tensor(True)

扩展阅读

autograd.Function 的文档:

https://pytorch.org/docs/stable/autograd.html#function

参考

https://github.com/pytorch/tutorials

https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html