.backward()
.backward() 是 PyTorch 中用于自动求导的函数,它的主要作用是计算损失函数对模型参数的梯度,从而实现反向传播算法。
在深度学习中,我们通常使用梯度下降 算法来更新模型参数,使得模型能够逐步逼近最优解。
在梯度下降算法中,我们需要计算损失函数关于模型参数的梯度 ,以便确定参数更新的方向和大小。
这个计算过程就是反向传播算法,而 loss.backward() 就是反向传播算法的实现。
官方:官方文档
Tensor.backward(gradient=None, retain_graph=None, create_graph=False, inputs=None)[source]
.
当前Variable(理解成函数Y)对leaf variable(理解成变量X=[x1,x2,x3])求偏导。
Computes the gradient of current tensor wrt graph leaves.
计算当前张量相对于图中叶子节点的梯度。
The graph is differentiated using the chain rule. If the tensor is non-scalar (i.e. its data has more than one element) and requires gradient, the function additionally requires specifying gradient. It should be a tensor of matching type and location, that contains the gradient of the differentiated function w.r.t. self.
"使用链式法则对图进行微分。如果张量是非标量(即其数据具有多个元素)并且需要梯度,则该函数还需要指定梯度。它应该是相同类型和位置的张量,其中包含相对于自身的微分函数的梯度。"
This function accumulates gradients in the leaves - you might need to zero .grad attributes or set them to None before calling it. See Default gradient layouts for details on the memory layout of accumulated gradients.
这个函数在叶子节点中累积梯度,可能需要在调用它之前将 .grad 属性清零或设置为 None。有关累积梯度的内存布局详情,请参阅默认梯度布局。
Parameters
gradient (Tensor or None)
-- 计算图可以通过链式法则求导。如果Tensor是 非标量(non-scalar)的(即是说Y中有不止一个y,即Y=[y1,y2,...]),且requires_grad=True。那么此函数需要指定gradient,它的形状应该和Variable的长度匹配(这个就很好理解了,gradient的长度体与Y的长度一直才能保存每一个yi的梯度值啊),里面保存了Variable的梯度。
原文链接:https://blog.csdn.net/weixin_43763731/article/details/88982979
retain_graph (bool, optional)
-- 这个参数默认是False。计算梯度所必要的buffer在经历过一次backward过程后不会被释放。如果你想多次计算某个子图的梯度的时候,设置为True。
create_graph (bool, optional)
-- If True, graph of the derivative will be constructed, allowing to compute higher order derivative products. Defaults to False.
inputs (sequence of Tensor)
-- 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.
看到一篇比较清楚地讲解,我把结论写在这里,具体的参看原文:
参考:https://blog.csdn.net/witnessai1/article/details/79763596
-
Tensor必须是一个一维标量
如:
a = v(t.FloatTensor([2, 3]), requires_grad=True)
m = v(t.FloatTensor([[2, 3]]), requires_grad=True)
则不行会有如下报错:
报错信息:backward只能被应用在一个标量上,也就是一个一维tensor,或者传入跟变量相关的梯度。
-
特别注意Tensor里面默认的参数
requires_grad=False
,requires_grad == True
, 则表示它可以参与求导,也可以从它向后求导。
requires_grad == True
具有传递性:如果:x.requires_grad == True
,y.requires_grad == False
,z=f(x,y)
则z.requires_grad == True
-
Tensor.backward(parameters)
接受的 参数parameters
必须要和Tensor
的大小一模一样,然后作为Tensor
的系数传回去; -
如果
Tensor
不是一个一维标量,想要获取对应梯度,需要计算jacobian矩阵。
loss.backward()和torch.autograd.grad的区别
参考:loss.backward()和torch.autograd.grad的区别
- loss.backward()会将求导结果累加在grad上。这也是为什么在训练每个batch的最开始,需要对梯度清零的原因。
- torch.autograd.grad不会将求导结果累加在grad上。
- loss.backward()后,非叶子节点的导数计算完成之后就会被清空。不过,可以在非叶子节点之后,加上
"非叶子节点.retain_grad()" 来解决这个问题。(作用同:requires_grad == True
) - torch.autograd.grad可以获取非叶子节点的梯度。
- PS:Pytorch中的张量有一个is_leaf的属性。若一个张量为叶子节点,则其is_leaf属性就为True,若这个张量为非叶子节点,则其is_leaf属性就为False。一般地,由用户自己创建的张量为叶子节点。另外,神经网络中各层的权值weight和偏差bias对应的张量也为叶子节点。由叶子节点得到的中间张量为非叶子节点。在反向传播中,叶子节点可以理解为不依赖其它张量的张量。
Pytorch中的自动求导机制会根据输入和前向传播过程自动构建计算图(节点就是参与运算的变量,图中的边就是变量之间的运算关系),然后根据计算图进行反向传播,计算每个节点的梯度值。
Pytorch提供了两种求梯度的方法,分别是backward()和torch.autograd.grad()。
- backward()方法可以计算根节点对应的所有叶子节点的梯度。
- 如果不需要求出当前张量对所有产生该张量的叶子节点的梯度,则可以使用torch.autograd.grad()。
不过需要注意的是,这两种梯度方法都会在反向传播求导的时候释放计算图,如果需要再次做自动求导,因为计算图已经不再了,就会报错。如果要在反向传播的时候保留计算图,可以设置retain_graph=True。
pytorch的计算图
参考:https://zhuanlan.zhihu.com/p/33378444
pytorch是动态图机制,所以在训练模型时候,每迭代一次都会构建一个新的计算图。而计算图其实就是代表程序中变量之间的关系。举个列子: y = ( a + b ) ( b + c ) y = (a+b)(b+c) y=(a+b)(b+c) 在这个运算过程就会建立一个如下的计算图:
注意图中的 leaf_node,叶子结点就是由用户自己创建的Variable变量,在这个图中仅有a,b,c 是 leaf_node。为什么要关注leaf_node?因为在网络backward时候,需要用链式求导法则求出网络最后输出的梯度,然后再对网络进行优化,如下就是网络的求导过程。
python
x = Variable(torch.FloatTensor([[1, 2]]), requires_grad=True) # 定义一个输入变量
y = Variable(torch.FloatTensor([[3, 4],
[5, 6]]))
loss = torch.mm(x, y) # 变量之间的运算
loss.backward(torch.FloatTensor([[1, 0]]), retain_graph=True) # 求梯度,保留图
print(x.grad.data) # 求出 x_1 的梯度
x.grad.data.zero_() # 最后的梯度会累加到叶节点,所以叶节点清零
loss.backward(torch.FloatTensor([[0, 1]])) # 求出 x_2的梯度
print(x.grad.data) # 求出 x_2的梯度
这里有一点不太理解:为什么loss.backward(torch.FloatTensor([[1, 0]]), retain_graph=True)
是对 x 1 x_1 x1求导,而loss.backward(torch.FloatTensor([[0, 1]]))
是对 x 2 x_2 x2求导。
好像有一点明白了,torch.FloatTensor([[1, 0]])
中 x 2 x_2 x2为0???