目录:
一、autograd:自动微分
PyTorch
中所有神经网络的核心是 autograd
包,autograd
包对 tensor
上的所有运算提供了自动微分。它是一个 difine-by-run 框架,意味着代码如何运行定义了反向传播,并且每一次迭代操作都可以不同。
二、Tensor
torch.Tensor
是包的核心类。若设置属性 requires_grad=True
,则会开始跟踪其上的所有操作。当计算完成后,调用 backward()
自动计算所有的梯度。tensor
上的梯度会累积到 .grad
属性中。
要停止 tensor
跟踪历史记录,可调用 .detach()
将其从计算历史中分离,并阻止将来的计算被跟踪。
为了阻止跟踪历史记录(和使用内存),可以 torch.no_grad():
包装代码块。在评估模型时,这可能特别有用。因为模型可能包含属性 requires_grad=True
的参数,但是我们并不需要其梯度。
对于autograd的实现,另一个类非常重要——Function
tensor
和 Function
相互连接构成一个非循环图,编码完整的计算历史。每个 tensor
有一个 .grad_fn
属性,指向创建该 tensor
的 Function
(除了用户创建的 tensor
,它们的grad_fn
为 None
)。
若想要计算导数,可以对该 tensor
调用 backward()
。若该 tensor
是一个标量(即只包含一个元素),不需要为 backward()
指定任何参数。然而,若该 tensor
包含多个元素,需要指定一个梯度参数,该参数是一个 tensor
并且形状匹配。
示例
生成一个tensor
,并设置 requires_grad=True
以跟踪其上的运算
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
输出:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
进行运算
y = x + 2
print(y)
输出:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y 作为运算结果被创建,所有它有grad_fn
print(y.grad_fn)
输出:
<AddBackward0 object at 0x7f77ae1835c0>
继续进行运算
z = y * y * 3
out = z.mean()
print(z, out)
输出:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_(...)
原地改变 tensor
的 requires_grad
属性,若不指定,默认值为 False
。
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)
输出:
False
True
<SumBackward0 object at 0x7f77ae183e10>
三、梯度
因为 out
只包含一个标量,因此 out.backward()
等价于out.backward(torch.tensor(1.))
。
out.backward()
打印梯度:d(out)/dx
print(x.grad)
输出:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
得到一个元素为4.5的2x2矩阵。记 out
为“
o
o
o”,则
o
=
1
4
∑
i
z
i
o = \frac{1}{4}\sum_i z_i
o=41∑izi,
z
i
=
3
(
x
i
+
2
)
2
z_i = 3(x_i+2)^2
zi=3(xi+2)2 ,
z
i
∣
x
i
=
1
=
27
z_i\bigr\rvert_{x_i=1} = 27
zi∣∣xi=1=27。因为,
∂
o
∂
x
i
=
3
2
(
x
i
+
2
)
\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2)
∂xi∂o=23(xi+2),所以
∂
o
∂
x
i
∣
x
i
=
1
=
9
2
=
4.5
\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5
∂xi∂o∣∣xi=1=29=4.5。
从数学上讲,对于函数 y ⃗ = f ( x ⃗ ) \vec{y}=f(\vec{x}) y=f(x), y ⃗ \vec{y} y 关于 x ⃗ \vec{x} x的梯度是一个Jacobian 矩阵:
J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ) \begin{aligned}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{aligned} J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞
通常来说,torch.autograd
是一个计算向量 和 Jacobian 乘积的引擎。即,给定任意的向量
v
=
(
v
1
v
2
⋯
v
m
)
T
v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}
v=(v1v2⋯vm)T,计算
v
T
⋅
J
v^{T}\cdot J
vT⋅J。如果
v
v
v恰好是一个标量函数
l
=
g
(
y
⃗
)
l=g\left(\vec{y}\right)
l=g(y)的梯度,即,
v
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
T
v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}
v=(∂y1∂l⋯∂ym∂l)T,根据链式法则,向量和Jacobian的乘积将会是
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
)
\begin{aligned}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{aligned}
JT⋅v=⎝⎜⎛∂x1∂y1⋮∂xn∂y1⋯⋱⋯∂x1∂ym⋮∂xn∂ym⎠⎟⎞⎝⎜⎛∂y1∂l⋮∂ym∂l⎠⎟⎞=⎝⎜⎛∂x1∂l⋮∂xn∂l⎠⎟⎞
(注意:
v
T
⋅
J
v^{T}\cdot J
vT⋅J 给出一个行向量,可以通过取
J
T
⋅
v
J^{T}\cdot v
JT⋅v 将其视为列向量)
向量和Jacobian乘积的这种特性使得将外部梯度馈送到具有非标量输出的模型中非常方便。
向量和Jacobian乘积示例
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
输出:
tensor([-806.5612, 620.5370, 1196.6376], grad_fn=<MulBackward0>)
上述例子中,y 不是一个标量,torch.autograd
不能直接计算完整的Jacobian,但是如果我们只想要求向量和Jacobian的乘积,只需将向量作为参数向后传递:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
输出:
tensor([2.0480e+02, 2.0480e+03, 2.0480e-01])
也可以使用 torch.no_grad()
包装代码块,停止 autograd
跟踪属性为 requires_grad=True
的 tensor
的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
输出:
True
True
False