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=<AddBackward0>)
# <AddBackward0 object at 0x7fac3a8d0080>	生成该张量相关的运算

再试试其他的运算:

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

# 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 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。
这个结果是如何得到的呢?方便起见,我们简写outpytorch 只推理不计算梯度 pytorch计算auc_代码块,根据上述操作可以写出pytorch 只推理不计算梯度 pytorch计算auc_代码块的实际表达式为:pytorch 只推理不计算梯度 pytorch计算auc_迭代_03,且pytorch 只推理不计算梯度 pytorch计算auc_后向传播_04。很容易求得pytorch 只推理不计算梯度 pytorch计算auc_后向传播_05,在pytorch 只推理不计算梯度 pytorch计算auc_代码块_06时有pytorch 只推理不计算梯度 pytorch计算auc_代码块_07。具体到4.5这个值的意义,其实就是pytorch 只推理不计算梯度 pytorch计算auc_代码块所对应的函数表达式在pytorch 只推理不计算梯度 pytorch计算auc_代码块_06处的梯度为4.5。
针对这个例子,如果我们要求函数的极小值点,则只需要不断延梯度的反方向行走。假设步长lr为0.2,那么pytorch 只推理不计算梯度 pytorch计算auc_迭代_10,通过不断迭代可以在pytorch 只推理不计算梯度 pytorch计算auc_后向传播_11处找到极小值点,此时再经由反向传播获得的x.grad值为0.0,也符合数学上对于极小值点的定义。

上面提到了out中元素数量可能大于一的情况,例如我们通过函数获得了一个向量pytorch 只推理不计算梯度 pytorch计算auc_代码块_12,那么pytorch 只推理不计算梯度 pytorch计算auc_代码块_13关于pytorch 只推理不计算梯度 pytorch计算auc_代码块_14的梯度将可以表示成一个雅可比矩阵(Jacobian matrix):
pytorch 只推理不计算梯度 pytorch计算auc_迭代_15

torch.autograd是用于计算雅可比向量积的工具,即:当给定一个向量pytorch 只推理不计算梯度 pytorch计算auc_代码块_16,计算pytorch 只推理不计算梯度 pytorch计算auc_后向传播_17。如果给定的函数输出结果为标量pytorch 只推理不计算梯度 pytorch计算auc_pytorch 只推理不计算梯度_18,那么其梯度可以写作pytorch 只推理不计算梯度 pytorch计算auc_pytorch 只推理不计算梯度_19,进一步根据链式法则结合上面得到的雅可比矩阵,可以推导出雅可比向量积即为标量pytorch 只推理不计算梯度 pytorch计算auc_迭代_20相对于输入pytorch 只推理不计算梯度 pytorch计算auc_代码块_14的梯度:
pytorch 只推理不计算梯度 pytorch计算auc_代码块_22

(注:pytorch 只推理不计算梯度 pytorch计算auc_后向传播_17给出的结果与pytorch 只推理不计算梯度 pytorch计算auc_迭代_24在内容上是相同的,两者分别为行向量与列向量)
现在让我们来看一个雅可比向量积应用的实例:

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=<MulBackward0>)

很明显,这时的函数输出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)