Autograd模块
PyTorch的Autograd模块是应用所有神经网络的核心内容,在张量(Tensor)上的所有操作,Autograd都能为他们自动提供微分,也就是自动求导的方法,从而简化了手动计算导数的复杂过程。
在0.4之前的版本中,PyTorch通过使用Variable类来自动计算所有的梯度,该类主要包含三个属性:
- data:保存Variable所包含的Tensor。
- grad:保存data对应的梯度,类型为Variable,形状与data一致。
- grad_fn:指向一个Function对象,我们可以通过它使用反向传播计算输入的梯度。
而从0.4版本开始,为了简化使用,PyTorch就将Variable类合并到Tensor中去了,因此我们现在可以直接通过Tensor来使用Autograd模块提供的功能。要使用Autograd,我们只需在创建Tensor时设置属性requries_grad为True即可。一旦我们将requries_grad属性设置为True,就表明我们需要对该Tensor进行自动求导,PyTorch会记录该Tensor的每一步操作历史并自动计算。
当我们将requries_grad属性设置为True时,PyTorch会自动追踪和记录对张量Tensor的所有操作,当计算完成后调用backward( )方法自动计算梯度并且将计算结果保存到grad属性中。Tensor进行相关操作后,grad_fn已经被赋予了一个新的函数,这个函数引用了Tensor类的Function对象。Tensor和Function互相连接生成了一个非循环图,它记录并且编码了完整的计算历史。每个张量都有一个grad_fn属性,如果这个张量是用户手动创建的,那么这个张量的grad_fn是None。
自动求导
自动求导使用backward( )函数进行反向传播来计算指定Tensor的梯度,在调用backward( )函数前,Tensor的grad属性为None。
- 简单的自动求导
如果Tensor表示的是一个标量(只有一个元素的Tensor),则不需要为backward( )指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,它是形状匹配的张量。
上述代码中的张量z只有一个元素,因此使用backward( )函数不需要传任何参数。
以上的z.backward( )相当于是z.backward(torch.tensor(1.))的简写。这种参数常出现在图像分类中的单标签分类,输出一个标量代表图像的标签。
复杂的自动求导
当返回值不是一个标量时,我们在调用backward( )函数时需要传入一个与待自动求导的Tensor大小相同的Tensor作为backward( )函数的参数。
我们可以使用with torch.no_grad()上下文管理器临时禁止对已设置requires_grad=True的张量进行自动求导。这个方法在测试集计算准确率的时候会经常用到,例如:
在使用with torch.no_grad()进行嵌套后,PyTorch就不会再跟踪历史记录了,从而释放内存的使用量,同时会加快少许的运算速度。
自动求导原理
要明白自动求导的原理,首先需要知道计算图的概念。
- 计算图
计算图是现代深度学习框架的核心,为高效自动求导算法——反向传播提供了理论支持。计算图具有如下两个优势:
- 使用一个简单函数就可以组合成一个极其复杂的模型。
- 可以实现自动微分(自动求导)。
例如对于某个表达式y=wx+b,其中,w、x、b是变量,*、+、=是算子。PyTorch通过记录算子与变量之间的关系可以生成如下图所示的计算图。
其中,我们称w、x、b是叶子节点,这些通常是手动创建的、而非运算得到的变量,因此也叫做创建变量。y为根节点,是计算图的最终目标,也就是通过计算后得到的结果,因此也称为结果变量。
判断一个变量是创建变量还是结果变量可以通过Tensor的is_leaf属性获取。例如对于上述代码中的三个张量Tensor:x、y、z,通过访问各自的is_leaf属性可以获取它们的变量属性。
x、y为创建变量,z为结果变量。
- backward( )实质
通过上述代码,我们知道执行z.backward( )方法会更新x.grad和y.grad的值,这一点我们可以从grad_fn属性中进行探索,grad_fn记录并编码了整个计算历史。对于z=x**2+y**3来说,计算结果z的grad_fn为AddBackward0类型的变量,AddBackward0里面有一个next_functions,这个next_functions就是整个grad_fn的精华。
打印next_functions可以看到,它是包含两个元素的元组tuple。其中,第一个元素表示x相关的操作记录,第二个元素表示y相关的操作记录。AddBackward0表示的是相加,而这个tuple中的PowBackward0则分别表示x**2与y**3的操作记录。以x为例,我们继续使用next_functions属性最终得到一个AccumulateGrad。在PyTorch的反向图计算中,AccumulateGrad类型代表的就是叶子节点类型。AccumulateGrad类中有一个variable属性指向叶子节点,这个variable属性就是我们最初的创建变量x。
总结整个backward( )函数的执行过程(以上述代码中的z.backward( )为例)如下:
- 调用z中的grad_fn属性。
- 遍历grad_fn的next_functions属性,分别取出其中的Function(AccumulateGrad),执行求导操作。这个操作是一个递归的过程直到最后类型为叶子节点为止。
- 将计算结果保存到对应的variable所引用的对象(x和y)的grad属性里。
- 更新对应变量的grad属性。