跳转至

Autograd:自动微分

Autograd: 自动微分(Automatic differentiation)

autograd 包 是 PyTorch 中所有神经网络的中心。 我们首先简要查看,然后训练第一个神经网络。

autograd 包为 Tensor 上的所有运算提供自动微分(automatic differentiation)。 它是一个“运行中定义”(define-by-run)的框架,这意味着反向传播(backpropagation)算法由代码在运行时定义,并且每个迭代都可以不同。

我们通过更简单的术语的例子来看看这些。

张量(Tensor)

torch.Tensor 是这个包的中心类。 将它的 .requires_grad 属性设置为 True,即开始跟踪其上的所有运算。 完成计算后调用 .backward() 即可自动完成梯度计算。 该 tensor 梯度的累计和记录在 .grad 属性。

要停止 tensor 的历史跟踪,可调用 .detach(),将它从计算历史中分离出来,防止它在将来的计算中被跟踪。

要防止历史跟踪(和内存的使用),还可以将代码块放置在 with torch.no_grad(): 中。 这在评估模型时尤其有用,因为模型可能具有可训练的参数 requires_grad=True ,但进行评估时不需要梯度。

还有一个类对于 autograd 实现非常重要——Function

TensorFunction 互相连接并构建一个非循环图,它将完整的计算历史进行了编码记录。 每个 tensor 都有一个 .grad_fn 属性,该属性指向创建这个 TensorFunction (除了用户创建的 Tensor——他们的 grand_fnNone

如需计算导数(derivatives),可以调用 Tensor.backward() 方法。 如果 Tensor 是标量(即它包只含单元素数据),则 backward() 不需要指定参数,但是如果它有多个元素,则需要指定一个形状匹配的 tensor 作为 gradient 参数。

1
import torch

创建一个 tensor,设置 requires_grad=True 跟踪它的计算

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

进行一次 tensor 运算:

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

y 是这个运算的结果张量,所以它也有 grad_fn

1
print(y.grad_fn)
1
<AddBackward0 object at 0x7f10a069dd68>

y 做更多运算

1
2
3
4
z = y * y * 3
out = z.mean()

print(z, out)
1
2
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward1>)

.requires_grad_( ... ) 原位改变了已有 Tensor 的 requires_grad 标志。该输入标志的默认值是 False

1
2
3
4
5
6
7
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
1
2
3
False
True
<SumBackward0 object at 0x7f0ff5fc2240>

梯度(Gradients)

现在进行反向传播(backprop)。 由于 out 包含单个标量,out.backward()相当于 out.backward(torch.tensor(1.))

1
out.backward()

输出梯度 d(out)/dx

1
print(x.grad)
1
2
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

得到一个 4.5 矩阵。

out Tensor 设为 “o”。 我们有

o = \frac{1}{4}\sum_i z_i

z_i = 3(x_i+2)^2

以及

z_i\bigr\rvert_{x_i=1} = 27

所以,

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

那么

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

在数学上,若有向量值函数 \vec{y}=f(\vec{x}), 则 \vec{y} 的梯度是关于 \vec{x} 的雅可比矩阵:

\begin{align}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{align}

一般来说,torch.autograd 就是一个计算向量雅可比积的引擎。 也就是说,对给定的任意向量 v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}, 计算 v^{T}\cdot J 的积。 如果 v 恰好是标量函数 l=g\left(\vec{y}\right) 的梯度,换而言之, v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}, 那么,由链规则(chain rule)可知,向量雅可比积是 l 关于 \vec{x} 的梯度:

\begin{align}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{align}

(注意 v^{T}\cdot J 给出了一个行向量,它可被视作 J^{T}\cdot v 列向量。)

向量雅可比积的这些性质使得将外部梯度送到非标量输出模型中变得非常方便。

现在,我们看一个向量雅可比积的例子:

1
2
3
4
5
6
7
x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)
1
tensor([-977.1336,  264.0834, -116.1632], grad_fn=<MulBackward0>)

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

1
2
3
4
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)
1
tensor([2.0480e+02, 2.0480e+03, 2.0480e-01])

另外,用 .requires_grad=True: 包含代码块可以让 autograd 停止跟踪 Tensor 的历史

1
2
3
4
5
print(x.requires_grad)
print((x ** 2).requires_grad)

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

延后阅读:

autogradFunction 的文档在 https://pytorch.org/docs/autograd