AUTOGRAD 包是pytorch中和神经网络关系最为密切的一部分。它可以为基于tensor的的所有操作提供自动微分的功能。它是一个在运行时定义(define-by-run)的框架,这意味着其中的反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
torch.Tensor
是该包中最主要的一个class。如果我们设置其属性.requires_grad = True
,则torch会追踪到对该tensor上的所有操作(operation)。当计算结束后,可以通过.backward()
来自动计算所有的梯度。对这个tensor的梯度会累加在.grad
中。
如果想避免一个张量受历史数据的影响,可以通过.detach()
来将其从计算图历史中分开,以避免之后的计算被追踪。
为了防止追踪历史(且占用内存),我们可以将代码包装在with torch.no_grad():
中,这样很有效。因为在评估模型时,模型中可能会包含requires_grad = True
的可训练参数,但我们可能并不需要其梯度。
对autograd来说,还有另外一个非常重要的class,Fucntion
Tensor
和Function
之间互相连接,构成了一个无环图(acyclic graph)。图中包括了计算的完整历史。每个张量都有.grad_fn
属性,该属性会引用创建该tensor的Function。(除了是由用户自己创建的tensor,它们的grad_fn
为None
)。
如果想要计算导数,我们可以在 Tensor 上调用 .backward()
。如果 Tensor 是一个标量(如它只包含一个元素的数据),则不需要为backward()
指定任何参数,但是如果它有更多的元素,则需要指定一个 gradient 参数,该参数是一个与该张量形状匹配的张量。
创建一个tensor,并设置其 requires_grad=True 来追踪其计算图
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
# output:
# tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
然后对该tensor做操作
y = x + 2
print(y)
# output:
# tensor([[3., 3.],
# [3., 3.]], grad_fn=)
此处因为y是计算后的结果,所以它具有grad_fn
属性
print(y.grad_fn)
# output:
#
再看个其它的操作
z = y * y * 3
out = z.mean()
print(z, out)
# output:
# tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
可见每一个tensor都对应找到得出该tensor的grad_fn
。
默认情况下,requires_grad
是False
。我们可以用.requires_grad_( ... )
来原地修改已有的tensor的requires_grad
状态。如下面代码所示:
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)
# output:
# False
# True
#
现来来看一下上面例子中的反向传播是怎么做的。
因为 out
是一个标量(可见它最后输出了z的平均值27)。所以out.backward()
就相当于out.backward(torch.tensor(1.))
。这个相当于是怎么的个意思,不懂
anyway,对其计算导数,并输出对x的导数 d ( o u t ) / d x d(out)/dx d(out)/dx
out.backward()
print(x.grad)
# output:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
那好,为什么是4.5呢?
首先,我们把out
看做张量" o o o",则我们有
∵ o = 1 4 ∑ i z i z i = 3 ( x i + 2 ) 2 且 z i ∣ x i = 1 = 27 ∴ ∂ o ∂ x i = 3 2 ( x i + 2 ) ∂ o ∂ x i ∣ x i = 1 = 9 2 = 4.54 \begin{aligned} \because \mathcal{o} &= \frac{1}{4}\sum_i z_i\\ z_i&=3(x_i + 2)^2 \ \ 且 \ \ z_i\bigg|_{x_i = 1} = 27 \\ \therefore \frac{\partial o}{\partial x_i}&= \frac{3}{2}(x_i + 2)\\ \frac{\partial o}{\partial x_i} \bigg|_{x_i = 1} &= \frac{9}{2} = 4.5 {4} \end{aligned} ∵ozi∴∂xi∂o∂xi∂o∣∣∣∣xi=1=41i∑zi=3(xi+2)2 且 zi∣∣∣∣xi=1=27=23(xi+2)=29=4.54
数学上,如果有向量值函数 y ⃗ = f ( x ⃗ ) \vec{y} = f(\vec{x}) y=f(x),那么 y ⃗ \vec{y} y相对于 x ⃗ \vec{x} x的梯度是一个雅可比矩阵:
J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y m ∂ x 1 ⋮ ⋱ ⋮ ∂ y 1 ∂ x n ⋯ ∂ y m ∂ x n ) J = \begin{pmatrix} \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{pmatrix} J=⎝⎜⎛∂x1∂y1⋮∂xn∂y1⋯⋱⋯∂x1∂ym⋮∂xn∂ym⎠⎟⎞
通常来说,torch.autograd
是计算雅克比向量积的“引擎”。即,给定任意向量 v = ( v 1 , v 2 , ⋯ , v m ) T v=(v_1, v_2, \cdots,v_m)^T v=(v1,v2,⋯,vm)T,计算乘积 v T ⋅ J v^T \cdot J vT⋅J,如果 v v v恰好是标量函数 l = g ( y ⃗ ) l = g(\vec{y}) l=g(y)的导数,即 v = ( ∂ l ∂ y 1 , ⋯ , ∂ l ∂ y m ) T v = (\frac{\partial l}{\partial y_1} , \cdots, \frac{\partial l}{\partial y_m} )^T v=(∂y1∂l,⋯,∂ym∂l)T。那么根据链式法则,雅可比向量积应该是 l l l对 x ⃗ \vec{x} x的导数。
J T ⋅ v = ( ∂ y 1 ∂ x 1 ⋯ ∂ y m ∂ x 1 ⋮ ⋱ ⋮ ∂ y 1 ∂ x n ⋯ ∂ y m ∂ x n ) ( ∂ l ∂ y 1 ⋮ ∂ l ∂ y m ) = ( ∂ l ∂ x 1 ⋮ ∂ l ∂ x n ) J^T \cdot v = \begin{pmatrix} \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{pmatrix} \begin{pmatrix} \frac{\partial l}{\partial y_1}\\ \vdots\\ \frac{\partial l}{\partial y_m} \end{pmatrix} = \begin{pmatrix} \frac{\partial l}{\partial x_1}\\ \vdots\\ \frac{\partial l}{\partial x_n} \end{pmatrix} JT⋅v=⎝⎜⎛∂x1∂y1⋮∂xn∂y1⋯⋱⋯∂x1∂ym⋮∂xn∂ym⎠⎟⎞⎝⎜⎛∂y1∂l⋮∂ym∂l⎠⎟⎞=⎝⎜⎛∂x1∂l⋮∂xn∂l⎠⎟⎞
注意:行向量的 v T ⋅ J v^T⋅J vT⋅J 也可以被视作列向量的 J T ⋅ v J^T⋅v JT⋅v
雅可比向量积的这一特性使得将外部梯度输入到具有非标量输出的模型中变得非常方便。
举个栗子:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
# output:
# tensor([-278.6740, 935.4016, 439.6572], grad_fn=)
Tips: 其中y.data.norm()是在求y的向量2范数,相当于:
torch.sqrt(torch.sum(torch.pow(y, 2)))
在这种情况下,y 不再是标量。torch.autograd 不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
# output:
# tensor([4.0960e+02, 4.0960e+03, 4.0960e-01])
也可以通过将代码块包装在 with torch.no_grad():
中,来阻止autograd跟踪设置了 .requires_grad=True 的张量的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
# output:
# True
# True
# False