Pytorch学习笔记--1 AUTOGRAD

Pytorch学习笔记--1 AUTOGRAD

  • AUTOGRAD:全自动微分
    • Tensor
    • 梯度

AUTOGRAD:全自动微分

Autograd–automatic gradient,顾名思义,是能够帮我们自动求解梯度的包。所有Pytorch神经网络的核心都是autograd包,因此在正式开始训练我们的第一个网络之前,先让我们看看autograd具体能做些什么。
不论是深度学习还是更为基础的神经网络,它们“学习”的能力都是围绕Back Propagation(BP)(即反向传播)建立起来的,而BP的数学基础就是微分。想对BP做进一步了解的朋友可以移步这里,几个高赞回答都解答的很完善了。对于一个庞大的网络,为其中的每一个运算求微分是极其繁琐的,而autograd包将我们从数学的深渊中解放了出来。
对于所有的张量运算,autograd包都可以为我们提供其所对应的微分。auotgrad是一种由运行流程定义的(define-by-run)框架,也就意味着整个网络后向传播的过程将取决与代码的执行流程。在某些情况下或许用户需要在迭代中对输入的张量做不同的操作,那么autograd会自动根据操作的改变进行调整。
下面让我们结合一些简单例子来进一步了解autograd。

Tensor

在上一篇文章中我们已经了解过torch.Tensor这个包了。如果将一个tensor的属性.requires_grad设为True,那么框架就会自动追踪所有基于该tensor的运算。在运算结束后执行.backward(),所有运算相关的梯度都将会被自动求解出来,并且该张量对应的梯度会被累加到属性.grad上。
如果想要将某个tensor移出追踪的列表,可以使用.detach()函数完成解绑,解绑完成后在该张量上的后续计算都不会被autograd追踪。
如果只是想要暂时停止追踪(有助于节省内存消耗),可以通过with torch.no_grad():将指定代码块包裹住。这个方法在评估模型时尤其有用,在模型评估的代码块中不涉及梯度计算以及参数更新,暂时停止梯度计算有助于提升程序的运行效率。
autograd的实现中还有一个非常重要的类:Function
Tensor之间通过Function进行相互连接并建立起一张无环图,这张无环图包含了完整的计算流程。每个tensor都有.grad_fn属性来描述创造该张量所使用的Function。需要注意这里有一个特例,用户创建的tensor的grad_fnNone
在计算导数的时候可以直接调用Tensor的方法:.backward()。如果该tensor是一个标量(即张量内部只有一个元素),那么用户不需要为backward()指定任何参数。反之则需为gradient参数传入一个大小匹配的张量,稍后我们会做讨论,现在先来看看标量的后向传播。

动手创建一个张量,并将requires_grad设为True:

import torch

x = torch.ones(2, 2, requires_grad=True)
print(x)

# Out:
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True)

执行一次张量运算:

y = x + 2
print(y)
print(y.grad_fn)

# Out:
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=)
# 	生成该张量相关的运算

再试试其他的运算:

z = y * y * 3
out = z.mean()
print(z)
print(out)

# Out:
# tensor([[27., 27.],
#         [27., 27.]], grad_fn=) 
# tensor(27., grad_fn=)

.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 0x7fac3a8d0c18>

梯度

现在我们可以在刚才创建的名为out的张量上做后向传播(BP)了,由于out中仅有一个元素,out.backward()等价于out.backward(torch.tensor(1.))

out.backward()
print(x.grad)

# Out:
# tensor([[4.5000, 4.5000],
#         [4.5000, 4.5000]])

程序最终输出了大小为2x2的矩阵,其中每个值均为4.5。
这个结果是如何得到的呢?方便起见,我们简写out o o o,根据上述操作可以写出 o o o的实际表达式为: o = 1 4 ∑ i z i o=\frac{1}{4}\sum_{i}z_i o=41izi,且 z i = 3 ( x i + 2 ) 2 z_i=3(x_i+2)^2 zi=3(xi+2)2。很容易求得 ∂ o ∂ x i = 3 2 ( x i + 2 ) \frac{\partial o}{\partial x_i}=\frac{3}{2}(x_i+2) xio=23(xi+2),在 x i = 1 x_i=1 xi=1时有 ∂ o ∂ x i ∣ x i = 1 = 9 2 \frac{\partial o}{\partial x_i}|_{x_i=1}=\frac{9}{2} xioxi=1=29。具体到4.5这个值的意义,其实就是 o o o所对应的函数表达式在 x i = 1 x_i=1 xi=1处的梯度为4.5。
针对这个例子,如果我们要求函数的极小值点,则只需要不断延梯度的反方向行走。假设步长lr为0.2,那么 x i _ n e w = x i − l r ∗ 4.5 , x i _ n e w = 0.1 x_{i\_new}=x_i-lr*4.5,x_{i\_new}=0.1 xi_new=xilr4.5,xi_new=0.1,通过不断迭代可以在 x i = − 2 x_i=-2 xi=2处找到极小值点,此时再经由反向传播获得的x.grad值为0.0,也符合数学上对于极小值点的定义。

上面提到了out中元素数量可能大于一的情况,例如我们通过函数获得了一个向量 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 n ∂ x 1 ⋯ ∂ y n ∂ x n ) J=\begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_n}{\partial x_1} & \cdots & \frac{\partial y_n}{\partial x_n} \end{pmatrix} J=x1y1x1ynxny1xnyn

torch.autograd是用于计算雅可比向量积的工具,即:当给定一个向量 v = ( v 1 v 2 ⋯ v n ) T v=(v_1 v_2 \cdots v_n)^T v=(v1v2vn)T,计算 v T ⋅ J v^T \cdot J vTJ。如果给定的函数输出结果为标量 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=(y1lyml)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} \cdot \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} JTv=x1y1xny1x1ymxnymy1lyml=x1lxnl

(注: v T ⋅ J v^T \cdot J vTJ给出的结果与 J T ⋅ v J^T \cdot v JTv在内容上是相同的,两者分别为行向量与列向量)
现在让我们来看一个雅可比向量积应用的实例:

x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
	y = y * 2
print(y)

# Out:
# tensor([   -2.6975,   472.1523, -1040.3346], grad_fn=)

很明显,这时的函数输出y已经不再是一个标量了。torch.autograd无法直接求解出雅可比矩阵,但可以通过向backward传入一个向量来获取对应的雅可比向量积:

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)

# Out:
# tensor([4.0960e+02, 4.0960e+03, 4.0960e-01])

当我们希望autograd停止对于张量梯度追踪的时候,可以使用with torch.no_grad():将其包入其中:

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)

# Out:
# True
# True
# False

该方法在训练模型时的验证(validation)步骤中将起到很大作用,在推理时暂停了对于梯度的追踪,可以极大的节省验证时间与消耗的内存。
另一种方法是使用.detach()方法创建一个新的张量,其内容与原始张量全部相同,唯一区别就是requires_grad为False:

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())

# Out:
# True
# False
# tensor(True)

你可能感兴趣的:(Deep,Learning)