torch.autograd
是pytorch的自动微分引擎,用以推动神经网络训练。在本节,你将会对autograd如何帮助神经网络训练的概念有所理解。
背景
神经网络(nns)是在输入数据上执行的嵌套函数的集合。这些函数由参数(权重、偏置)定义,并在pytorch中保存于tensors中。
训练nn需要两个步骤:
前向传播:在前向传播中(forward prop),神经网络作出关于正确输出的最佳预测。它使输入数据经过每一个函数来作出预测。
反向传播:在反向传播中(backprop),神经网络根据其预测中的误差来调整其参数,它通过从输出向后遍历,收集关于函数参数的误差的导数(梯度),并使用梯度下降优化参数。有关更多关于反向传播的细节,参见video from 3blue1brownvideo from 3blue1brown。
在pytorch中的使用
让我们来看一下单个训练步骤。对于这个例子,我们从 torchvision
加载了一个预训练的resnet18模型。我们创建了一个随机数据tensor,用以表示一个3通道图片,其高和宽均为64,而其对应的 label
初始化为某一随机值。
import torch, torchvision
model = torchvision.models.resnet18(pretrained=true)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
接下来,我们将数据输入模型,经过模型的每一层最后作出预测。这是前向过程。
prediction = model(data) # forward pass
我们使用模型的预测及其对应的标签计算误差(loss
)。下一步是通过网络反向传播误差。当在误差tensor上调用.backward()
时,反向传播开始。然后,autograd计算针对每一个模型参数的梯度,并将其保存在参数的 .grad
属性中。
loss = (prediction - labels).sum()
loss.backward() # backward pass
接下来,我们加载一个优化器,在此案例中是sgd,学习率是0.01,动量参数(momentum)是0.9。我们在优化器中注册所有的模型参数。
optim = torch.optim.sgd(model.parameters(), lr=1e-2, momentum=0.9)
最后,我们调用 .step()
启动梯度下降。优化器会通过保存在 .grad
的参数梯度调整所有参数。
optim.step() # gradient descent
此时,你已拥有训练神经网络所需的一切。以下部分详细介绍了autograd的工作原理 - 可随意跳过。
autograd中的微分
让我们来看一下 autograd
是如何收集梯度的。创建两个tensor a
和 b
,并且 requires_grad=true
。这向 autograd
发出信号,跟踪在它们上执行的每一个操作。
import torch
a = torch.tensor([2., 3.], requires_grad=true)
b = torch.tensor([6., 4.], requires_grad=true)
由 a
和 b
创建tensor q
。
\[q = 3a^2 - b^2
\]
q = 3*a**2 - b**2
假设 a
和 b
是一个神经网络的参数,q
是误差。在nn训练中,求解关于参数的梯度,即:
\[\frac{\partial q}{\partial a} = 9a^2
\]
\[\frac{\partial q}{\partial b} = -2b
\]
当我们在 q
上调用 .backward()
,autograd计算以上梯度并保存在对应tensor的 .grad
属性中。
q.backward()
是一个向量,因此我们需要在 q.backward()
中显示地传递一个 gradient
参数。gradient
是一个和 q
相同形状的tensor,它表示q关于其本身的梯度,即:
\[\frac{\partial q}{\partial q} = 1
\]
等效地,我们还可以将q聚合为一个标量,并隐式的向后调用,如 q.sum().backward()
external_grad = torch.tensor([1., 1.])
q.backward(gradient=external_grad)
梯度现在杯保存在 a.grad
、b.grad
中
## 检查收集的梯度是否正确
print(9*a**2 == a.grad)
print(-2*b == b.grad)
输出:
tensor([true, true])
tensor([ture, true])
选读 - 使用 autograd
进行矢量微分
计算图
从概念上来说,autograd在一个由function对象组成的有向无环图(dag)中记录了数据(tensors)和所有执行的操作(连同由此产生的新tensors)。在dag中,叶节点是输入tensors,根节点是输出tensors。通过从根节点到叶节点跟踪此图,你可以使用链式法则自动计算梯度。
在前向过程中,autograd同时进行两件事:
执行请求的操作计算结果tensor,
在dag中保留操作的 gradient function。
在dag根节点处调用 .backward()
时启动反向过程。然后autograd
:
由每个 .grad_fn
计算梯度,
将梯度累积在其对应tensor的 .grad
属性中,
使用链式法则,将梯度一直传播到叶节点。
下图是以上例子中dag的可视化表示。在该图中,箭头表示前向过程的方向。节点表示在前向过程中每一个操作的backward functions。蓝色叶节点表示我们的tensor a
和 b
。
注意:dags在pytorch中是动态的。需要重点注意的是:dag是从头开始重新创建的,在每次 .backward
调用时,autograd开始填充一个新图。这正是在模型中允许你使用控制流语句的原因。如果需要,你可以在每次迭代中更改形状、大小和操作。
从dag中排除
torch.autograd
跟踪所有 requires_grad=true
的tensor上的操作。对于不要求计算梯度的tensor,requires_grad=false
,并将其从梯度计算dag中排除。
当一个操作就算只有一个输入tensor有 requires_grad=true
,其输出的tensor仍然要计算梯度。
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=true)
a = x y
print(f"does 'a' require gradients? : {a.requires_grad}")
b = x z
print(f"does 'b' require gradients? : {b.requires_grad}")
输出:
does `a` require gradients? : false
does `b` require gradients?: true
在神经网络中,不计算梯度的参数通常成为冻结参数。如果你事先知道不需要这些参数的梯度,那冻结模型的一部分很有用(这通过减少autograd计算量提供了一些性能优势)。
从dag中排除的另一个重要的常见用法是finetuning a pretrained network
在finetune中,我们冻结模型的大部分参数,并且通常只修改分类层以对新的标签作出预测。让我们通过一个小例子来演示这一点。像之前一样,我们加载一个预训练resnet18模型,并且冻结所有参数。
from torch import nn, optim
model = torchvision.models.resnet18(pretrained=true)
# 冻结网络中的所有参数
for param in model.parameters():
param.requires_grad = false
假设我们要在一个10标签数据集上微调模型。在resnet中,分类层是最后的线性层 model.fc
。我们可以简单地用一个新的线性层(默认情况下未冻结)替换它作为我们的分类器。
model.fc = nn.linear(512, 10)
模型中除了 model.fc
的所有参数均被冻结。需要计算梯度的参数仅仅是 model.fc
的权重和偏置
# 仅优化分类层
optimizer = optim.sgd(model.parameters(), lr=1e-2, momentum=0.9)
注意,尽管我们在优化器中注册了所有参数,但是计算梯度(在梯度下降中更新)的参数仅是分类层的权重和偏置。
the same exclusionary functionality is available as a context manager in torch.no_grad().