作者:Ja1r0

用PyTorch构建的神经网络,其梯度计算是通过torch.autograd来完成的。当我们进行了一系列计算,并想获取一些变量间的梯度信息,需要进行以下步骤:

  1. 构建一个计算图,用Variable将Tensor包装起来,形成计算图中的节点。然后Variable之间进行各种运算就像Tensor之间的运算一样,Variable支持几乎所有的Tensor运算。
  2. 当你进行完一系列运算之后,可以执行.backward()来自动计算出所有需要的梯度。
  3. 来针对某个变量x执行x.grad获得想要的梯度值。

Variable

autograd.Variable是torch.autograd中很重要的类。它用来包装Tensor,将Tensor转换为Variable之后,可以装载梯度信息。



pytorch计算FLOP_pytorch元素相乘

pytorch的一个重要特点就是动态计算图(Dynamic Computational Graphs)。计算图中每一个节点代表一个变量,变量间建立运算关系并且可以修改,而不像Tensorflow中的计算图是固定不可变的。

Variable用来构建一个计算图中的节点。将Tensor转换为Variabla类型之后,该Tensor就成了计算图中的一个节点。对于该节点,有两个重要的特性:

  • .data——获得该节点的值,即Tensor类型的值
  • .grad——获得该节点处的梯度信息

关于Variable的参数之一“requires_grad”和特性之一“grad_fn”有要注意的地方,都和该变量是否是人自己创建的有关:

  1. requires_grad有两个值:True和False,True代表此变量处需要计算梯度,False代表不需要。变量的“requires_grad”值是Variable的一个参数,在建立Variable的时候就已经设定好,默认是False。
  2. grad_fn的值可以得知该变量是否是一个计算结果,也就是说该变量是不是一个函数的输出值。若是,则grad_fn返回一个与该函数相关的对象,否则是None。
import torch
from torch.autograd import Variable
x=Variable(torch.randn(2,2))
y=Variable(torch.randn(2,2))
z=Variable(torch.randn(2,2),requires_grad=True)
a=x+y
b=a+z

以上形成了这样一个计算图:



pytorch计算FLOP_pytorch元素相乘_02

  • 用户创建变量:x,y,z
  • 运算结果变量:a,b

(官方文档中所说的“graph leaves”,“leaf variables”,都是指像x,y,z这样的事先创建的、而非运算得到的变量,本文我们把这样的变量称为创建变量,像a,b那样的称为结果变量)

运算结果变量的“requires_grad”值是取决于输入的变量的,例如变量b:其是由a和z计算得到的,如果a或者z需要计算关于自己的梯度(requires_grad=True),因为梯度要反向传播,那么b的“requires_grad”就是True;如果a和z的“requires_grad”都是False那么,b的也是False。而且运算结果变量的“requires_grad”是不可以更改的,且不会改变。用户创建变量的“requires_grad”是可以更改的。



pytorch计算FLOP_用户创建_03

pytorch计算FLOP_用户创建_04

pytorch计算FLOP_Tensorflow_05

Gradients

我们再来建立一个计算稍微复杂一点能体现出梯度计算过程的计算图:

import torch
from torch.autograd import Variable
x=torch.Tensor([[1.,2.,3.],[4.,5.,6.]])
x=Variable(x,requires_grad=True)
y=x+2
z=y*y*3
out=z.mean()



pytorch计算FLOP_用户创建_06

这是这样一个图。先来看一下这四个变量都是什么:

print(x)
print(y)
print(z)
print(out)



pytorch计算FLOP_pytorch元素相乘_07

再来看一下上面讨论过的grad_fn的值:

print(x.grad_fn)
print(y.grad_fn)
print(z.grad_fn)
print(out.grad_fn)



pytorch计算FLOP_Tensorflow_08

可见作为leaf Variable的x,是一个用户创建的变量,它的grad_fn是None。而其余三个变量都是一个运算的结果,其grad_fn是一个与运算对应的对象。

计算图已经构建好了,下面来计算梯度。所计算的梯度都是结果变量关于创建变量的梯度。在这个计算图中,能计算的梯度有三个,分别是out,z和y关于x的梯度,以out关于x的梯度为例:

要在变量out处执行.backward(),这时开始计算梯度,由梯度计算的链式法则算到所有的结果变量(graph leaves),这个图中只有一个x。然后在创建变量处执行.grad,获得结果变量out关于创建变量x的梯度。我们先来手动计算一下:

用 

 表示变量out,

这时我们就计算出了 

 的表达式。在定义x的时候已经给 

 赋值,然后代入表达式中计算,就可以得到一个具体的梯度值。要注意:

  • 若是关于graph leaves求导的结果变量是一个标量,那么gradient默认为None,或者指定为“torch.Tensor([1.0])”
  • 若是关于graph leaves求导的结果变量是一个向量,那么gradient是不能缺省的,要是和该向量同纬度的tensor

pytorch计算FLOP_Tensorflow_09

如果是z关于x求导就必须指定gradient参数:


pytorch计算FLOP_用户创建_10

完。

PS:本人也是刚开始学,如果有理解的不对的还望不吝赐教。


17年12月28日更新

接上文。由 

 ,代入每一个 

 ,就得到了向量 

 关于向量 

 的导数,这个导数是和 

 同维度的。这种情况需要给出gradient参数,这个参数需要和 

 同纬度,而且当gradient向量所有元素为1.0时,可以得到正确的关于leaf Variable的导数。若不为1.0时,算出的导数就扩大对应倍数,我之前只是知道这个现象,但是不知道为什么要这样设置。那这个.backward()括号中的gradient参数到底是干嘛的呢。今天看到 

@七月

 写的这篇文章,明白了是怎么回事。原来在执行

z.backward(gradient)

的时候,若z不是一个标量,那么就先构造一个标量的值:L = torch.sum(z*gradient),再关于L对各个leaf Variable计算梯度


pytorch计算FLOP_pytorch元素相乘_11

也就是说,若z不是标量,那么就在计算图中添加一个由z运算得到的标量L,

L=torch.sum(z*Variable(gradients))

因为要把z和gradients对应元素相乘,于是向量gradients需要和z的维度相同。L.backward(),或者说 z.backward(gradient=gradients) 会计算  ,当gradients向量所有元素都为1时,计算出的 x.grad 正是  ,因为  。这也是为什么.backward(gradient=...)括号里这个参数名为“gradient”,因为它是构造出的变量L关于原变量z的梯度。