本系列介绍了入门PyTorch所需要了解的内容。本文主要参考文献:《Deep Learning with PyTorch: A 60 Minute Blitz》(PyTorch深度学习60分钟快速入门),更新于2019.06.12。
PyTorch中所有神经网络的核心是autograd
包,这个包提供所有在张量上进行自动求导的操作。这个过程是define-by-run的,也就是反向传播过程由代码运行方式定义,而且每次迭代都可以不同。
torch.Tensor
是这个包的重要类别。如果将其属性.requires_grad
设成True
,则会跟踪发生在这个张量上的所有操作。在计算结束后,可以通过调用.backward()
进行梯度的自动计算。这个张量的梯度被积累在属性.grad
中。
要停止一个张量参与反向传播,可以调用.detach()
函数使其脱离反向传播计算过程。
如果要停止反向传播和内存利用,可以将整个代码用代码块torch.no_grad()
,这个对于模型的评估格外有用,因为模型中可能存在可训练的参数(requires_grad=True
),但是却不希望计算它们的梯度。
对于反向传播很重要的另一个类别时Function
。
Tensor
和Function
之间是相互联系的,并且构建了一个非周期图(acyclic graph),记录了计算的完整历史。每个张量都有个.grad_fn
属性来引用创造这个Tensor
的Function
(除了由用户定义的张量,这些张量的grad_fn
是·None`)。
可以通过调用Tensor
上的.backward()
实现求导。但是如果这个张量是标量(只有一个元素数据),不需要对backward()
单独定义任何变量;但是元素很多的时候,需要制定gradient
变量来保证张量的维度保持不变。
import torch
创建一个张量并设定requires_grad=True
来跟踪实施在上面的计算:
x = torch.ones(2,2,requires_grad=True)
print(x)
进行一个张量操作:
y = x + 2
print(y)
y
是通过某个操作定义的变量,因此具有grad_fn
:
print(y.grad_fn)
进一步对y
进行操作:
z = y * y * 3
out = z.mean()
print(z.out)
requires_grad_( ... )
可以in-place地改变已有张量的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)
现在开始反向传播。由于out
包括的是一个标量,out.backward()
等同于out.backward(torch.tensor(1.))
。
out.backward()
显示导数d(out)/dx:
print(x.grad)
这里应该得到4.5组成的矩阵。称out
张量为o
,可以得到
o = 1 4 ∑ i z i , z i = 3 ( x i + 2 ) 2 o=\frac{1}{4}\sum_iz_i,\ z_i=3(x_i+2)^2 o=41∑izi, zi=3(xi+2)2且 z i ∣ x i = 1 = 27 z_i\vert_{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}\vert_{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 matrix)【在向量微积分中,雅可比矩阵是一阶偏导数以一定方式排列成的矩阵,其行列式称为雅可比行列式。】:
J = { ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n } J=\left\{\begin{matrix} \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{matrix}\right\} J=⎩⎪⎨⎪⎧∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎭⎪⎬⎪⎫
整体而言,torch.autograd
是一个用于计算向量-雅克比乘积的机器(an engine for computing vector-Jacobian product)。即给定任意向量 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 ) v=\left(\frac{\partial l}{\partial y_1}\cdots\frac{\partial l}{\partial y_m}\right) v=(∂y1∂l⋯∂ym∂l),那么根据chain rule,vector-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 ) J^T\cdot v=\left(\begin{matrix} \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{matrix}\right)\left(\begin{matrix} \frac{\partial l}{\partial y_1} \\ \vdots\\ \frac{\partial l}{\partial y_m}\\ \end{matrix}\right)=\left(\begin{matrix} \frac{\partial l}{\partial x_1}\\ \vdots\\ \frac{\partial l}{\partial x_n} \end{matrix}\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可以转换成列向量。
这个vector-Jacobian乘积使得将外部梯度送入一个没有标量输出的模型变得非常容易。
现在来具体看一个例子;
x = torch.randn(3,requires_grad=True)
y = x* 2
while y.data.norm() < 1000:
y = y * 2
print(y)
终端结果:
此时,y
已经不是一个标量了,torch.autograd
没有办法直接计算完整的Jacobian,但是我们其实只需要vector-Jacobian乘积,那么简单地将向量以变量的形式反向传播就可以:
v = torch.tensor(p0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
也可以通过将代码块融入with torch.no_grad():
中实现.requires_grad=True
下的反向梯度传播:
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
辅助阅读材料:【文档学习】Pytorch——torch.autorgrad包
更多内容,欢迎加入星球讨论。