从后向传播中排除子图
每一个tensor
都有一个requires_grad
标志,利用该标志可以精确地从计算图中排除子图,并且可以提高效率。
requires_grad
如果一个计算的输入需要计算梯度,则它的输出也需要计算梯度。相反,只有当所有的输入都不需要计算梯度时,输出才不需要计算梯度。如果子图中所有的tensor
都不需要计算梯度,则不会在此子图上进行反向传播计算。
当你想要冻结模型的一部分,或者提前知道不需要计算某些参数的梯度时,上述这点非常有用。例如,对一个预训练过的CNN
进行微调时,只需在冻结的基础上改变requires_grad,并且不会保存中间的缓冲区,直到计算到达最后一层,其中的仿射变换利用的权重需要计算梯度,神经网络的输出也需要计算梯度。
autograd如何编码过程
autograd
是自动微分的反向系统。从概念上讲,autograd
记录了一个图,这个图记录了生成结果的所有的运算,由此形成一个有向无环图,图的叶结点为输入的tensor
,根结点为输出的tensor
。沿着从根结点到叶结点的路径,利用链式法则就可以自动计算所有的梯度。
就内部而言,autograd
将此图表示为关于Function
对象(实际上是表达式)的图,Function
可用于计算评估图的结果。当执行前向传播时,autograd
同时运行所要求的计算,并且生成一个图,该图表示计算梯度的function
(每个tensor
的.grad_fn
属性是该图的入口点)。当前向传播完成时,我们在后向传播中评估此图以计算梯度。
需要注意的一点是,在每次迭代时都会重新生成此图,这正是允许使用任意的Python控制流语句的原因,使用这些语句可以改变计算图的形状和大小。在进行训练前,不需要编码所有可能的路径,所运行的就是所要微分的。
autograd下的原地操作
在autograd
中支持原地操作是一件困难的事,多数情况下,我们不鼓励使用。autograd
激进的缓冲释放和重用使其非常高效,原地操作大幅降低内存使用的情况非常少见。除非在非常大的内存压力下运行,否则永远不需要使用它们。
限制原地操作适用性的原因主要有两个:
-
原地操作可能会覆盖需要计算梯度的值
-
实际上,每个原地操作要求实现重写计算图。非原地操作版本简单地分配新的对象并保留对旧的计算图的引用,然而原地操作需要将所有输入的
creator
更改为表示此操作的Function
。这可能很困难,特别是当有许多的tensor
引用相同的存储(例如通过索引或转置生成),实际上,如果被修改输入的存储被其他tensor
引用时,原地操作会引发错误。
原地错误检查
每个tensor
保留一个版本计数器,在任何操作中,如果被标记为脏(dirty),则需要递增。当Function
保存tensor
以便后向传播时,也会保存每个tensor
的版本号。每当访问self.saved_tensors
时,便会检查,若它大于保存的值,则会引发错误。这可以确保,如果使用原地操作并且没有发生任何错误,那么计算的梯度是正确的。