注意
here下载完整的示例代码
Autograd: 自动求导¶
PyTorch 中所有神经网络的中心是 autograd
包。
让我们先简要地介绍一下,然后再训练第一个神经网络。
autograd
包为张量上的所有操作提供自动求导。 它是一个在运行时定义的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
让我们通过一些示例以更简单的方式看下这一点。
张量¶
torch.Tensor
是这个程序包的核心类。 如果将它的属性 .requires_grad
设置为 True
,它将开始跟踪对其的所有操作。 完成计算后,你可以调用.backward()
并自动计算所有梯度。 该张量的梯度将累积到.grad
属性中。
要停止张量跟踪历史,可以调用.detach()
将其从计算历史记录中分离出来,并防止将来的计算被跟踪。
为了停止跟踪历史(和使用内存),你还可以将代码块封装在 with torch.no_grad():
中。 这在模型评估时特别有用,因为模型可能有可训练且 requires_grad=True
的参数,但我们不需要梯度。
还有另外一个类对实现自动求导非常重要——Function
。
Tensor
和Function
相互连接并建立了一个非循环图,该图编码了完整的计算历史。 每个张量都有一个 .grad_fn
属性引用创建 Tensor
自身的 Function
(除非这个张量是用户手动创建的,即这个张量的 grad_fn 是 None
)。
如果要计算导数,可以在Tensor
上调用.backward()
。 如果Tensor
是一个标量(即它的数据只包含一个元素),则无需指定任何参数给backward()
,但如果它具有更多元素,则需要指定一个gradient
参数,该参数是形状匹配的张量。
import torch
创建一个张量并设置requires_grad=True
来跟踪它上面的计算
x = torch.ones(2, 2, requires_grad=True)
print(x)
输出:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
执行一个张量操作:
y = x + 2
print(y)
输出:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
是操作的结果,因此它有一个grad_fn
。
print(y.grad_fn)
输出:
<AddBackward0 object at 0x7f7fa5d39550>
对y
执行更多操作
z = y * y * 3
out = z.mean()
print(z, out)
输出:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... )
就地改变当前 Tensor 的requires_grad
标志。 如果输入标志未给出,默认为False
。
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
输出:
False
True
<SumBackward0 object at 0x7f7fa5d399b0>
梯度¶
现在开始反向传播。
因为out
包含单个标量,out.backward()
等效于out.backward(torch.tensor(1.))
out.backward()
打印梯度 d(out)/dx
print(x.grad)
输出:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
你应该得到一个取值全部为4.5
的矩阵。 让我们称out
张量为“\(o\)”。
我们有 \(o = \frac{1}{4}\sum_i z_i\),
\(z_i = 3(x_i+2)^2\) 和 \(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\)。
数学上,如果有一个向量值函数\(\vec{y}=f(\vec{x})\),那么 \(\vec{y}\) 相对于 \(\vec{x}\) 的梯度是一个雅可比矩阵:
一般来说,torch.autograd
是计算雅可比向量积的引擎。 即给定任意向量\(v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}\),计算乘积 \(v^{T}\cdot J\)。 如果 \(v\) 正好是标量函数\(l=g\left(\vec{y}\right)\)的梯度,即\(v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\),那么根据链式法则,雅可比向量积应该是\(l\)对\(\vec{x}\)的导数:
(注意\(v^{T}\cdot J\)给出的是一个行向量,它可以通过\(J^{T}\cdot v\)将其视为列向量。)
雅可比向量积的这一特性使得将外部梯度输入具有非标量输出的模型变得非常方便。
现在我们来看一个雅可比向量积的例子:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
输出:
tensor([-347.7287, -694.1026, 1276.7065], grad_fn=<MulBackward0>)
现在在这种情况下y
不再是标量。 torch.autograd
不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给backward
:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
输出:
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
要停止跟踪.requires_grad=True
的张量上的历史记录和自动求导,还可以通过将代码块封装在
with torch.no_grad():
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
输出:
True
True
False
或者使用.detach()
获取具有相同内容但不需要导数的新张量:
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
输出:
True
False
tensor(True)
稍后阅读:
https://pytorch.org/docs/stable/autograd.html#function 上关于autograd.Function
的文档
脚本总运行时间: (0 分 0.396 秒)