一、概念

如果将Tensor的属性.requires_grad属性设置为True,它将开始追踪在其上的所有操作(主要目的是利用链式法则进行梯度传播)。完成计算后,可以调用.backward()方法来完成所有梯度计算。此Tensor的梯度将累计到.grad属性中。

注意在y.backward()时,如果y是标量,则不需要为backward()传入任何参数;否则,需要传入一个与y同形的Tensor。这为了避免向量(甚至更高维张量)对张量求导,而转换成标量对张量求导。举个例子,假设形状为pytorch梯度下降 pytorch梯度上升_标量 的矩阵 pytorch梯度下降 pytorch梯度上升_pytorch_02 经过运算得到了 pytorch梯度下降 pytorch梯度上升_标量_03 的矩阵 pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_04pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_04 又经过运算得到了 pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_06 的矩阵pytorch梯度下降 pytorch梯度上升_机器学习_07。那么 pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_08 应该是 pytorch梯度下降 pytorch梯度上升_标量_09 四维张量,pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_10 是一个 pytorch梯度下降 pytorch梯度上升_机器学习_11 的四维张量。反向传播时对四维张量相乘是十分困难的,因此为了避免这种问题,我们不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形式的张量。所以必要时我们要把张量通过将所有张量的元素加权求和的方式转换为标量,举个例子,假设pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_12由自变量pytorch梯度下降 pytorch梯度上升_深度学习_13计算而来,pytorch梯度下降 pytorch梯度上升_pytorch_14是和pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_12同形的张量,则y.backward(w)的含义是:先计算l=torch.sum(y*w),则 pytorch梯度下降 pytorch梯度上升_标量_16 是个标量,然后求 pytorch梯度下降 pytorch梯度上升_标量_16 对自变量 pytorch梯度下降 pytorch梯度上升_深度学习_13

用一个例子说明这一点:

x=torch.tensor([1.0,2.0,3.0,4.0],requires_grad=True)
y=2*x
z=y.view(2,2)
print(z)

输出:

pytorch梯度下降 pytorch梯度上升_pytorch_19


可以看到现在y并不是一个标量,在调用backward()时需要传入一个和y同形的权重向量进行加权求和得到一个标量:

v=torch.tensor([[1.0,0.1],[0.01,0.001]],dtype=torch.float)
z.backward(v)
print(x.grad)

输出:

pytorch梯度下降 pytorch梯度上升_机器学习_20


注意,x.grad是和x同形的张量。

如果不想要被继续追踪,可以调用.detach()将张量从追踪记录中分离出来,这样就可以防止将来的计算被追踪(梯度传不过去了)。此外,还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数(requires_grad=True)的梯度。

Function是另外一个很重要的类。Tensor和Function互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor都有一个.grad_fn属性,该属性即创建该Tensor的Function,就是说该Tensor是不是通过某些运算得到的,若是,则grad_fn返回一个与这些运算相关的对象,否则是None。

二、Tensor及梯度

2.1 Tensor

创建一个Tensor并设置requires_grad=True

x=torch.ones(2,2,requires_grad=True)
print(x)
print(x.grad_fn)

输出:

pytorch梯度下降 pytorch梯度上升_pytorch_21


接下来做下加法操作:

y=x+2
print(y)
print(y.grad_fn)

输出如下:

pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_22


x是直接创建的,所以它没有grad_fn;y是通过一个加法操作创建的,所以它有一个AddBackward0的grad_fn。像x这种直接创建的称为叶子节点,叶子节点对应的grad_fn是None。

print(x.is_leaf,y.is_leaf)

输出如下:

pytorch梯度下降 pytorch梯度上升_pytorch_23


我们可以通过.requires_grad_()来用in-place的方式改变requires_grad属性(注意未指定情况下requires_grad默认为False):

a=torch.randn(2,2)
a=((a*3)/(a-1))
print(a.requires_grad)
#False
a.requires_grad_(True)
print(a.requires_grad)
#True
2.2 梯度

计算得到out的过程如下:

import torch
x=torch.ones(2,2,requires_grad=True)
y=x+2
z=y*y*3
out=z.mean()
out.backward()
print(x.grad)

因为out是一个标量,所以调用backward()时不需要指定求导变量:

out.backward()
#等价于out.backward(torch.tensor(1.))

我们可以通过如下的命令查看out关于x的梯度pytorch梯度下降 pytorch梯度上升_pytorch_24

print(x.grad)

输出结果如下:

pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_25


量都为向量的函数 pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_26 ,那么 pytorch梯度下降 pytorch梯度上升_pytorch_27 关于 pytorch梯度下降 pytorch梯度上升_pytorch_28

pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_29

而torch.autograd这个包就是用来计算一些雅可比矩阵的乘积的。例如,如果pytorch梯度下降 pytorch梯度上升_标量_30是一个标量函数的pytorch梯度下降 pytorch梯度上升_机器学习_31的梯度:

pytorch梯度下降 pytorch梯度上升_深度学习_32

那么根据链式法则我们有pytorch梯度下降 pytorch梯度上升_标量_16关于pytorch梯度下降 pytorch梯度上升_pytorch_28的雅可比矩阵就为:

pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_35

(这里有一个问题,不是m倍的吗?)

注意,grad在反向传播的过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零

#注意这里grad的累加过程
out2=x.sum()
out2.backward()
print(x.grad)

out3=x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)

pytorch梯度下降 pytorch梯度上升_pytorch_36


接下来看下中断梯度追踪的例子:

x=torch.tensor(1.0,requires_grad=True)
y1=x**2
with torch.no_grad():
	y2=x**3
y3=y1+y2

print(x.requires_grad)
print(y1,y1.requires_grad)
print(y2,y2.requires_grad)
print(y3,y3.requires_grad)

输出:

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<ThAddBackward>) True

可以看到,上面的y2没有grad_fn并且y2.requires_grad=False,而y3是有grad_fn的,此时我们将y3对x求梯度:

y3.backward()
print(x.grad)

输出:

tensor(2.)

如果正常求导,求导结果是pytorch梯度下降 pytorch梯度上升_机器学习_37,但由于y2定义时被torch.no_grad():包裹,所以与y2有关的梯度不会回传,只有与y1有关的梯度才会回传,即pytorch梯度下降 pytorch梯度上升_pytorch梯度下降_38对x的梯度。

上⾯提到, y2.requires_grad=False ,所以不能调⽤ y2.backward() ,会报错:

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

此外,如果我们想要修改 tensor 的数值,但是⼜不希望被 autograd 记录(即不会影响反向传播),
那么我么可以对 tensor.data 进⾏操作。

x = torch.ones(1,requires_grad=True)
print(x.data) # 还是⼀个tensor
print(x.data.requires_grad) # 但是已经是独⽴于计算图之外
y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播
y.backward()
print(x) # 更改data的值也会影响tensor的值
print(x.grad)

输出:

tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])

三、涉及到的具体的API介绍

backward

这个函数的原型是:

Tensor.backward(gradient=None, retain_graph=None, create_graph=False, inputs=None)

整个计算图的梯度通过链式法则进行累计。当调用backward的函数非零时,需要提供gradient参数(具体有关gradient参数的要求在上方一、中进行了介绍)。还需要注意的一点是,在调用这个函数时,我们首先需要将整个模型的梯度清零,因为这个函数会在原来梯度的基础上累加梯度。

参数介绍:

  • gradient
  • retain_graph:如果这个值为False,那么用于计算梯度的整个计算图会被释放。注意在几乎所有情况下都没有必要设置这个值为True并且值为False时整个计算图会以更高效的方式来运行。
  • create_graph:如果这个值为True,那么求导图会被构建,这允许我们计算更高等级的求导结果。
  • Inputs w.r.t. which the gradient will be accumulated into .grad. All other Tensors will be ignored. If not provided, the gradient is accumulated into all the leaf Tensors that were used to compute the attr::tensors. All the provided inputs must be leaf Tensors.