pytorch 深度学习之自动微分
自动微分
深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。 实际中,根据我们设计的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
例如,相对 \(y = 2{x^T}x\) 关于向量 \(x\) 求导,首先创建一个变量 \(x\) 并为其分配一个初始值:
import torch
x = torch.arange(4.0)
x
tensor([0., 1., 2., 3.])
需要一个地方来存储梯度。重要的是,我们不会在每次对一个参数求导时都分配新的内存。 因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。 注意,一个标量函数关于向量 \(x\) 的梯度是向量,并且与 \(x\) 具有相同的形状。
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 默认值是None
y = 2 * torch.dot(x,x)
y
tensor(28., grad_fn=<MulBackward0>)
\(x\) 是一个长度为 4 的向量,计算 \(x\) 和 \(x\) 的点积,得到了我们赋值给y的标量输出。 接下来,我们通过调用反向传播函数来自动计算 \(y\) 关于 \(x\) 每个分量的梯度,并打印这些梯度:
y.backward()
x.grad
tensor([ 0., 4., 8., 12.])
函数 \(y = 2{x^T}x\) 关于 \(x\) 的梯度应该是 \(4x\),验证:
x.grad == 4 * x
tensor([True, True, True, True])
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
tensor([1., 1., 1., 1.])
非标量变量的反向传播
当 \(y\) 不是标量时,向量 \(y\) 关于向量 \(x\) 的导数的最自然解释是一个矩阵。对于高阶和高维的 \(y\) 和 \(x\),求导的结果可以是一个高阶张量。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 在我们的例子中,我们只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
tensor([0., 2., 4., 6.])
分离计算
假设 \(y\) 是作为 \(x\) 的函数计算的,而 \(z\) 则是作为 \(y\) 和 \(x\) 的函数计算的。 想象一下,我们想计算 \(z\) 关于 \(x\) 的梯度,但由于某种原因,我们希望将 \(y\) 视为一个常数, 并且只考虑到 \(x\) 在 \(y\) 被计算后发挥的作用。
在这里,我们可以分离 \(y\) 来返回一个新变量 \(u\),该变量与 \(y\) 具有相同的值,但丢弃计算图中如何计算 \(y\) 的任何信息。 换句话说,梯度不会向后流经 \(u\) 到 \(x\)。 因此,下面的反向传播函数计算 \(z=u*x\) 关于 \(x\) 的偏导数,同时将 \(u\) 作为常数处理, 而不是 \(z=x*x*x\) 关于 \(x\) 的偏导数。
x.grad.zero_()
y = x * x
u = y.detach();
z = u * x
z.sum().backward()
x.grad == u
tensor([True, True, True, True])
由于记录了 \(y\) 的计算结果,我们可以随后在 \(y\) 上调用反向传播, 得到 \(y=x*x\) 关于的 \(x\) 的导数,即 \(2*x\)。
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
tensor([True, True, True, True])
Python 控制流的梯度计算
在下面的代码中,while 循环的迭代次数和 if 语句的结果都取决于输入 a 的值:
def f(a):
b = a * 2
while b.norm() < 1000:
b *= 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(),requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a
tensor(True)