自动求导(Autograd)
在进行深度学习的过程中,我们时常需要进行求导操作。求导的操作可以自动进行完成,下面我们首先给出一个简单的例子来看一看,求导这一过程是怎么完成的。 我们给出如下的一个数学式子: \[ f = \frac{a * b + c}{d} \] 对f进行求导的公式就如下: \[ \frac{df}{da} = \frac{b}{d}\\ \frac{df}{db} = \frac{a}{d}\\ \frac{df}{dc} = \frac{1}{d}\\ \frac{df}{dd} = -\frac{a * b + c}{d^2} \]
1 | import torch |
1 | a = torch.FloatTensor([2]) |
False False False False
True True False True True
1 | f.backward() |
tensor([0.5000]) tensor([0.2500]) None tensor([-0.2188])
1. 设置Tensor
每一个张量都有几个非常重要的属性:
requires_grad
grad_fn
grad
requires_grad
是一个布尔值,当它被设置为True时,它的所有操作都会被跟踪。当完成计算后,你可以在最终的计算结果Tensor上调用.backward()
函数,会将所有requires_grad
为True的Tensor的导数计算出来,计算结果可以通过.grad
来访问到。
grad_fn
属性会指明创建该Tensor的是一个什么样的操作函数,如果是用户自定义的Tensor,改属性就是None。
设置requires_grad
可以通过直接设置,还可以通过requires_grad_(bool)
函数来进行。不过在诸如ones
、zeors
、rand
等工厂方法中,有一个requires_grad
参数可以直接用于指定:
1 | x = torch.ones(2, 2, requires_grad=True) |
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
如果y是通过x创建的,而x的requires_grad
为True,那么y的requires_grad
也为True。
1 | y = x + 2 |
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
True
<AddBackward0 object at 0x00000213D63ABAC8>
2. 求导
现在比如我们有 Tensor a ,它的requires_grad
属性也为True。经过一系列运算得到了 f : \(f = f(g(...h(a)...))\),那么只要我们调用f.backward()
,就可以把导数计算出来了。
2.1 f 是标量时
在一开始我们给出了一个例子,是若干个标量运算得到标量后求导。现在我们再来看看标量运算结果对矩阵的求导情况:
1 | z = y * y * 3 |
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward1>)
现在来计算\(\frac{d\ out}{d\ x}\)。我们知道 \(o = \frac{1}{4}\sum_i z_i\), \(z_i = 3(x_i+2)^2\) and \(z_i\bigr\rvert_{x_i=1} = 27\)。 所以, \(\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2)\), 于是 \(\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5\)。
关于标量对矩阵的求导,大体而言就是标量对矩阵中每个元素分别求导。更多内容可以自行参考Matrix calculus。
1 | out.backward() |
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
2.2 f 不是标量时
实际上在Pytorch中,不允许 Tensor 对 Tensor 求导,只允许标量 Scalar 对张量 Tensor 求导,求导结果是和自变量同型的 Tensor。 实际上我们需要用到求导一般都是最后算出了Loss,然后调用loss.backward()
,再更新参数,所以事实上根本不允许求矩阵之间的导数。
然而实际操作中还是允许调用backward
的 Tensor 是一个多维张量的,但是需要传入额外参数:
tensor.backward(gradient=None, retain_graph=None, create_graph=False)
传入的这个gradient
是一个和tensor
同型的张量,**假设 x 经过一番计算得到 y,那么 y.backward(w) 求的不是 y 对 x 的导数,而是 l = torch.sum(y*w) 对 x 的导数 **。
1 | W = torch.Tensor([[1, 1, 1], [2, 2, 2]]) |
tensor([[1., 1., 1.],
[2., 2., 2.]], requires_grad=True)
tensor([[1., 3., 5.],
[2., 4., 6.]])
tensor([[ 1., 3., 5.],
[ 4., 8., 12.]], grad_fn=<MulBackward0>)
1 | z.backward(torch.Tensor([[1, 1, 1], [2, 3, 4]])) |
tensor([[ 1., 3., 5.],
[ 4., 12., 24.]])