文章目录
PyTorch:1.5.0
autograd
库是使用 PyTorch 构建神经网络的核心。首先让我们简要地浏览一下,之后我们将会训练第一个神经网络。
autograd
库提供了 Tensors 上自动微分的所有操作,它是一个按照定义运行的框架,这意味着通过你的运行代码来定义反向传播,但是每一次迭代却可以是不相同的。
让我们通过示例来了解一些简单的术语。
1. 张量(Tensor)
torch.Tensor
是 PyTorch 中的核心类,如果你设置 Tensor 的 .require_grad
为 True
,那么 Tensor 上的所有操作将会被追踪。当完成计算时,你可以调用 .backward()
方法,PyTorch 将会帮你自动计算出所有的梯度。关于该 Tensor 的梯度将会被累加到 .grad
属性中。
为了停止 Tensor 进行历史中追踪,你可以调用 .detach()
方法来将它从计算历史记录中分离出来,并防止将来的计算被跟踪。
要停止 Tensor 停止跟踪历史记录(和使用内存),你可以使用 with torch.no_grad()
方法来将代码块包裹起来。这在模型评估时是非常有用的,因为在模型训练阶段可以使用具备 requires_grad=True
的参数进行调参,但是在模型评估阶段我们并不需要梯度。
另外,还有一个类(Function
)对于 autograd
的实现非常重要。
Tensor
和 Function
相互连接并构建一个非循环图,它保存了完整计算过程的历史信息。对于每个 tensor
都有 .grad_fn
属性保存着创建 Tensor 的 Function
引用(如果用户自己创建的张量,则 grad_fn
是 None
)。
你可以在 Tensor 上调用 .backward()
方法来计算导数。如果 Tensor 是一个标量(即包含一个数据元素的 Tensor),则不需要指定 backward()
的 任何参数。但是如果包含更多元素的 Tensor,则需要指定一个 gradient
参数来指定 Tensor 的维度。
import torch
创建一个 Tensor,并设置 requires_grad=True
来跟踪它的相关计算。
x = torch.ones(2, 2, requires_grad=True)
print(x)
输出结果:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
针对 Tensor 做一个操作:
y = x + 2
print(y)
输出结果:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
是由 Tensor 操作结果创建,所以它有一个 grad_fn
属性。
print(y.grad_fn)
输出结果:
<AddBackward0 object at 0x7f1d35f45ef0>
让我们在 y
上做更多的操作:
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
属性。默认情况下,如果不做指定,一个 Tensor 的 requires_grad
属性为 False
。
import torch
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 0x7f1d35f5bb38>
2. 梯度
让我们看一下后向传播,因为 out
包含了一个单个标量,out.backward()
等同于 out.backward(torch.tensor(1.))
。
out.backward()
打印出 out
对于 x
的梯度:
print(x.grad)
输出结果:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
你将会得到一个元素均为 4.5 的矩阵,这里我们将 out
Tensor 称为 o
。则有:
o
=
1
4
∑
i
z
i
z
i
=
3
(
x
i
+
2
)
2
⇒
z
i
∣
x
i
=
1
=
27
o = \frac{1}{4}\sum_i z_i \\ z_i = 3(x_i+2)^2 \\ \Rightarrow z_i\bigr\rvert_{x_i=1} = 27
o=41i∑zizi=3(xi+2)2⇒zi∣∣xi=1=27
因此:
∂
o
∂
x
i
=
3
2
(
x
i
+
2
)
⇒
∂
o
∂
x
i
∣
x
i
=
1
=
9
2
=
4.5
\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2) \\ \Rightarrow \frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5
∂xi∂o=23(xi+2)⇒∂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 是一个雅克比矩阵:
J
=
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
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)
J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞
总而言之,torch.autograd
是一个计算雅克比向量内积的引擎。也就是给定任意向量
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,然后根据链式法则,雅克比向量内积将作为
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=\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)
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 来作为一个列向量。
现在,让我们来看一个雅克比向量内积的示例:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
输出结果:
tensor([1001.7316, 475.3566, -226.5395], grad_fn=<MulBackward0>)
在这个案例中,y
不再是一个标量。torch.autograd
不能够完全直接计算出雅克比矩阵,但是我们仅仅是想要得到雅克比向量积,只需要简单地将向量作为参数传递给 backward
方法:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
输出结果:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
你可以使用设置 Tensors 的 .requires_grad=True
,或者使用 with torch.no_grad()
来包裹代码块,来停止 autograd
对历史记录进行跟踪。
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