pytorch 梯度计算
pytorch
本文字数:759 字 | 阅读时长 ≈ 3 min

pytorch 梯度计算

pytorch
本文字数:759 字 | 阅读时长 ≈ 3 min

在训练神经网络时我们有很多的需求,比如我们在训练时需要冻结某一部分网络,再比如我们需要通过一个网络两次等等,这都涉及对计算图的操作,首先通过简单的 demo 来看一下 pytorch 是怎么计算梯度的,然后我们再通过一些实例对网络进行操作

1. 一个简单的梯度示例

创建三个二维变量 x,y,z,令

$$
\begin{aligned}
a = x+2y \
b = a+0.5z
\end{aligned}
$$

我们画出上述计算的简单图示

假设$x=[2, 1]$,$y=[1, 3]$,$z=[5, 2]$,计算梯度$\frac{\partial{b}}{\partial{a}}=1$,$\frac{\partial{b}}{\partial{z}}=0.5$,$\frac{\partial{a}}{\partial{x}}=1$,$\frac{\partial{a}}{\partial{y}}=2$,所以

$$
\begin{aligned}
\frac{\partial{b}}{\partial{x}}=\frac{\partial{b}}{\partial{a}}\frac{\partial{a}}{\partial{x}}=11=1 \
~~ \
\frac{\partial{b}}{\partial{y}}=\frac{\partial{b}}{\partial{a}}\frac{\partial{a}}{\partial{y}}=12=2 \
~~ \
\frac{\partial{b}}{\partial{z}}=0.5
\end{aligned}
$$

在 torch 中计算时,我们需要知道一些 tensor 的属性:

下面我们通过代码进行验证

注意其中有一句 a.retain_grad(),这句代码是说最后也要得到 a 的梯度,因为 torch 在 backward()之后只有叶子节点有梯度值,中间变量是没有的,如果想直接计算出来需要加上上述语句

import torch

''' initial xyz (requires_grad=True) ''' 
x = torch.Tensor([2, 1]).requires_grad_()
y = torch.Tensor([1, 3]).requires_grad_()
z = torch.Tensor([5, 2]).requires_grad_()

a = x + y*2
a.retain_grad()
# a = a.detach()
b = a + z/2
b.backward(torch.ones_like(x))
# b.backward(b.data)
print(x.data, x.grad)
print(y.data, y.grad)
print(z.data, z.grad)
print(a.data, a.grad)
'''
tensor([2., 1.]) tensor([1., 1.])
tensor([1., 3.]) tensor([2., 2.])
tensor([5., 2.]) tensor([0.5000, 0.5000])
tensor([4., 7.]) tensor([1., 1.])
'''

最终输出结果与我们计算的是相同的,上述代码中 b.backward(torch.ones_like(x)) 括号中的参数维度和 b 的维度相同,如果没有该参数会报下述错误

RuntimeError: grad can be implicitly created only for scalar outputs

这是因为默认的 backward()希望是一个标量,但是我们的 b 是一个二维向量,所以我们将其中传入和 b 维度相同的 1 即可(应该是默认输出的每一维度对自己的梯度为 1?因为如果传入的是 torch.ones_like(x)*2 的话最后的梯度会变为原来的 2 倍)如果最后输出的是 scalar,backward()不需要传入参数,默认传入的应该是 torch.ones_like(torch.tensor(1))

经过验证,如果输出的 b 是向量,b.backward(gradient=torch.ones_like(x))其实等价于下面两句,实际上还是将 b 变成了标量在进行的 backward

b = torch.sum(b*torch.ones_like(x))
b.backward()
9月 09, 2024