最近由于实际需要在学习pytorch,作为深度学习中最为重要的反向传播计算,pytorch用非常简单的backward( )函数就实现了,但是在实现过程中对于其参数存在一些疑问,下面就从pytorch中反向传播求导的计算方式,backward( )函数参数来进行说明。
这里首先还是放出backward( )函数的pytorch文档,因为整个说明主要还是围绕这个函数来进行的。
问题描述
从上面的文档可以看到backward函数有一个奇怪的参数:grad_tensors,在实现pytorch的官方教程中可以发现:
import torch
import torch.nn as nn
x = torch.tensor([2, 3, 4], dtype=torch.float, requires_grad=True)
print(x)
y = x * 2
while y.norm() < 1000:
y = y * 2
print(y)
y.backward(torch.ones_like(y))
print(x.grad)
上面的程序的输出为:
tensor([2., 3., 4.], requires_grad=True)
tensor([ 512., 768., 1024.], grad_fn=<MulBackward0>)
tensor([256., 256., 256.])
这里我们分布来讲述上面的过程:
- 创建一个张量x,并设置其 requires_grad参数为True,程序将会追踪所有对于该张量的操作,当完成计算后通过调用
.backward()
,自动计算所有的梯度, 这个张量的所有梯度将会自动积累到.grad
属性。 - 创建一个关于x的函数y,由于x的requires_grad参数为True,所以y对应的用于求导的参数
grad_fn
为<MulBackward0>
。这是因为在自动梯度计算中还有另外一个重要的类Function
,Tensor
和Function
互相连接并生成一个非循环图,它表示和存储了完整的计算历史。 每个张量都有一个.grad_fn
属性,这个属性引用了一个创建了Tensor
的Function
(除非这个张量是用户手动创建的,即,这个张量的grad_fn
是None
,例如1中创建的x的grad_fn
是None
) - 我们进行反向传播并输出x的梯度值,而这里出现了一个参数
torch.ones_like(y)
即为grad_tensors参数
,这里便引入了我们的问题:
为什么在求导的过程中需要引入这个参数,如果我们不引入这个参数的话,则会报下面的错误:
RuntimeError: grad can be implicitly created only for scalar outputs
即为提示我们输出不是一个标量
下面就开始分析这个问题以及这个参数的作用。
pytorch实现反向传播中求导的方法
这里主要参考pytorch计算图文章中的讲解:
由上面的文章知道pytorch是动态图机制,在训练模型时候,每迭代一次都会构建一个新的计算图。而计算图其实就是代表程序中变量之间的关系。对于这个例子可以构建如下计算图:
上面的计算图中每一个叶子节点都是一个用户自己创建的变量,在网络backward时候,需要用链式求导法则求出网络最后输出的梯度,然后再对网络进行优化,如下就是网络的求导过程。
通过观察上面的计算图可以发现一个很重要的点:
pytorch在利用计算图求导的过程中根节点都是一个标量,即一个数。当根节点即函数的因变量为一个向量的时候,会构建多个计算图对该向量中的每一个元素分别进行求导,这也就引出了下一节的内容
这里顺带说一下:
pytoch构建的计算图是动态图,为了节约内存,所以每次一轮迭代完也即是进行了一次backward函数计算之后计算图就被在内存释放,因此如果你需要多次backward只需要在第一次反向传播时候添加一个retain_graph=True标识,让计算图不被立即释放。实际上文档中retain_graph和create_graph两个参数作用相同,因为前者是保持计算图不释放,而后者是创建计算图,因此如果我们不想要计算图释放掉,将任意一个参数设置为True都行。(这一段说明的内容与本文主要说明的内容无关,只是顺带说明一下)
backward函数
结合上面两节的分析,可以发现,pytorch在求导的过程中,分为下面两种情况:
- 如果是标量对向量求导(scalar对tensor求导),那么就可以保证上面的计算图的根节点只有一个,此时不用引入
grad_tensors参数
,直接调用backward函数即可 - 如果是(向量)矩阵对(向量)矩阵求导(tensor对tensor求导),实际上是先求出Jacobian矩阵中每一个元素的梯度值(每一个元素的梯度值的求解过程对应上面的计算图的求解方法),然后将这个Jacobian矩阵与
grad_tensors参数
对应的矩阵进行对应的点乘,得到最终的结果。
Jacobian矩阵参考:Jacobian矩阵和Hessian矩阵
为什么pytorch不支持tensor对tensor求导,而是引入grad_tensors参数
参考:PyTorch 的 backward 为什么有一个 grad_variables 参数?
来源:CSDN
作者:Camlin_Z
链接:https://blog.csdn.net/sinat_28731575/article/details/90342082