Pytorch autograd,backward详解

日常都是无脑使用backward,每次看到别人的代码里使用诸如autograd.grad这种方法的时候就有点抵触,今天花了点时间了解了一下原理,写下笔记以供之后参考。如下笔记基于Pytorch1.0html

Tensor

Pytorch中全部的计算其实均可以回归到Tensor上,因此有必要从新认识一下Tensor。若是咱们须要计算某个Tensor的导数,那么咱们须要设置其.requires_grad属性为True。为方便说明,在本文中对于这种咱们本身定义的变量,咱们称之为叶子节点(leaf nodes),而基于叶子节点获得的中间或最终变量则可称之为结果节点。例以下面例子中的x则是叶子节点,y则是结果节点。node

x = torch.rand(3, requires_grad=True)
y = x**2
z = x + x

另一个Tensor中一般会记录以下图中所示的属性:python

  • data: 即存储的数据信息
  • requires_grad: 设置为True则表示该Tensor须要求导
  • grad: 该Tensor的梯度值,每次在计算backward时都须要将前一时刻的梯度归零,不然梯度值会一直累加,这个会在后面讲到。
  • grad_fn: 叶子节点一般为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪一种类型。例如上面示例代码中的y.grad_fn=<PowBackward0 at 0x213550af048>, z.grad_fn=<AddBackward0 at 0x2135df11be0>
  • is_leaf: 用来指示该Tensor是不是叶子节点。

图片出处:PyTorch Autograd

torch.autograd.backward

有以下代码:微信

x = torch.tensor(1.0, requires_grad=True)
y = torch.tensor(2.0, requires_grad=True)
z = x**2+y
z.backward()
print(z, x.grad, y.grad)

>>> tensor(3., grad_fn=<AddBackward0>) tensor(2.) tensor(1.)

能够z是一个标量,当调用它的backward方法后会根据链式法则自动计算出叶子节点的梯度值。机器学习

可是若是遇到z是一个向量或者是一个矩阵的状况,这个时候又该怎么计算梯度呢?这种状况咱们须要定义grad_tensor来计算矩阵的梯度。在介绍为何使用以前咱们先看一下源代码中backward的接口是如何定义的:函数

torch.autograd.backward(
		tensors, 
		grad_tensors=None, 
		retain_graph=None, 
		create_graph=False, 
		grad_variables=None)
  • tensor: 用于计算梯度的tensor。也就是说这两种方式是等价的:torch.autograd.backward(z) == z.backward()
  • grad_tensors: 在计算矩阵的梯度时会用到。他其实也是一个tensor,shape通常须要和前面的tensor保持一致。
  • retain_graph: 一般在调用一次backward后,pytorch会自动把计算图销毁,因此要想对某个变量重复调用backward,则须要将该参数设置为True
  • create_graph: 当设置为True的时候能够用来计算更高阶的梯度
  • grad_variables: 这个官方说法是grad_variables' is deprecated. Use 'grad_tensors' instead.也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就行了。

好了,参数大体做用都介绍了,下面咱们看看pytorch为何设计了grad_tensors这么一个参数,以及它有什么用呢?学习

仍是用代码作个示例ui

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward()

>>> ...
RuntimeError: grad can be implicitly created only for scalar outputs

当咱们运行上面的代码的话会报错,报错信息为RuntimeError: grad can be implicitly created only for scalar outputsspa

上面的报错信息意思是只有对标量输出它才会计算梯度,而求一个矩阵对另外一矩阵的导数一筹莫展。scala

$$ X = \left[\begin{array}{cc} x_0 & x_1 \ \end{array}\right] ,,,,,,,,,
Z=X+2=\left[\begin{array}{cc} x_0+2 & x_1+2 \ \end{array}\right] \Rightarrow \frac{\partial{Z}}{\partial{X}}=? $$

那么咱们只要想办法把矩阵转变成一个标量不就行了?好比咱们能够对z求和,而后用求和获得的标量在对x求导,这样不会对结果有影响,例如:

$$ \begin{align} &Z_{sum}=\sum{z_i}=x_0+x_1+8 \notag \ &\text{then} ,,,,, \frac{\partial{Z_{sum}}}{\partial{x_0}}=\frac{\partial{Z_{sum}}}{\partial{x_1}}=1 \notag \end{align} $$

咱们能够看到对z求和后再计算梯度没有报错,结果也与预期同样:

x = torch.ones(2,requires_grad=True)
z = x + 2
z.sum().backward()
print(x.grad)

>>> tensor([1., 1.])

咱们再仔细想一想,对z求和不就是等价于z点乘一个同样维度的全为1的矩阵吗?即$sum(Z)=dot(Z,I)$,而这个I也就是咱们须要传入的grad_tensors参数。(点乘只是相对于一维向量而言的,对于矩阵或更高为的张量,能够看作是对每个维度作点乘)

代码以下:

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward(torch.ones_like(z)) # grad_tensors须要与输入tensor大小一致
print(x.grad)

>>> tensor([1., 1.])

弄个再复杂一点的:

x = torch.tensor([2., 1.], requires_grad=True).view(1, 2)
y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)

z = torch.mm(x, y)
print(f"z:{z}")
z.backward(torch.Tensor([[1., 0]]), retain_graph=True)
print(f"x.grad: {x.grad}")
print(f"y.grad: {y.grad}")

>>> z:tensor([[5., 8.]], grad_fn=<MmBackward>)
x.grad: tensor([[1., 3.]])
y.grad: tensor([[2., 0.],
        [1., 0.]])

结果解释以下:

总结:

说了这么多,grad_tensors的做用其实能够简单地理解成在求梯度时的权重,由于可能不一样值的梯度对结果影响程度不一样,因此pytorch弄了个这种接口,而没有固定为全是1。引用自知乎上的一个评论:若是从最后一个节点(总loss)来backward,这种实现(torch.sum(y*w))的意义就具体化为 multiple loss term with difference weights 这种需求了吧。

torch.autograd.grad

torch.autograd.grad(
		outputs, 
		inputs, 
		grad_outputs=None, 
		retain_graph=None, 
		create_graph=False, 
		only_inputs=True, 
		allow_unused=False)

看了前面的内容后在看这个函数就很好理解了,各参数做用以下:

  • outputs: 结果节点,即被求导数
  • inputs: 叶子节点
  • grad_outputs: 相似于backward方法中的grad_tensors
  • retain_graph: 同上
  • create_graph: 同上
  • only_inputs: 默认为True, 若是为True, 则只会返回指定input的梯度值。 若为False,则会计算全部叶子节点的梯度,而且将计算获得的梯度累加到各自的.grad属性上去。
  • allow_unused: 默认为False, 即必需要指定input,若是没有指定的话则报错。

参考

<footer style="color:white;;background-color:rgb(24,24,24);padding:10px;border-radius:10px;"><br> <h3 style="text-align:center;color:tomato;font-size:16px;" id="autoid-2-0-0"><br> <footer style="color:white;;background-color:rgb(24,24,24);padding:10px;border-radius:10px;"><br> <h3 style="text-align:center;color:tomato;font-size:16px;" id="autoid-2-0-0"><br> <center> <span>微信公众号:AutoML机器学习</span><br> <img src="https://ask.qcloudimg.com/draft/1215004/21ra82axnz.jpg" style="width:200px;height:200px"> </center> <b>MARSGGBO</b><b style="color:white;"><span style="font-size:25px;">♥</span>原创</b><br> <span>若有意合做或学术讨论欢迎私戳联系~<br>邮箱:marsggbo@foxmail.com</span> <br><br> <b style="color:white;"><br> 2020-01-23 17:45:35 <p></p> </b><p><b style="color:white;"></b><br> </p></h3><br> </footer> <br><br> <br><br> <b style="color:white;"><br> 2019-9-18<p></p> </b><p><b style="color:white;"></b><br> </p></h3><br> </footer>

相关文章
相关标签/搜索