torch.autograd 的简要介绍

torch.autograd是 PyTorch 的自动差分引擎,可为神经网络训练提供支持。

神经网络(NN)是在某些输入数据上执行的嵌套函数的集合。 这些函数由参数(由权重和偏差组成)定义,这些参数在 PyTorch 中存储在张量中。

训练 NN 分为两个步骤:

  1. 正向传播:在正向传播中,NN 对正确的输出进行最佳猜测。 它通过其每个函数运行输入数据以进行猜测。
  2. 反向传播:在反向传播中,NN 根据其猜测中的误差调整其参数。 它通过从输出向后遍历,收集有关函数参数(梯度)的误差导数并使用梯度下降来优化参数来实现。

用法

首先,需要加载数据,利用torchvision加载准备训练的resnet18模型。

其次,创建一个随机数据张量,具有 3 个通道的单个图像,高度&宽度为 64,其对应的label初始化为一些随机值。

1
2
3
4
import torch, torchvision	#引入训练库
model = torchvision.models.resnet18(pretrained=True) #引入模型
data = torch.rand(1, 3, 64, 64) #创建一个随机张量
labels = torch.rand(1, 1000) #设置随机labels标签

接着利用正向传播进行预测。

1
prediction = model(data) # forward pass

接着我们利用预测的模型和对应标签计算误差loss。进行反向传播此误差。.backward()进行反向传播时,Autograd 会为每个模型参数计算梯度并将其存储在参数的.grad属性中。

1
2
loss = (prediction - labels).sum()
loss.backward() # backward pass

加载一个优化器,使用 梯度下降法(SGD) ,学习率为 0.01,步长为 0.9。 然后在优化器中注册模型的所有参数。

1
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

然后再调用 .step()启动 梯度下降 模型,优化器利用.grad中存储的梯度来进行调整参数。

1
optim.step() #gradient descent

于是便具备训练神经网络的所需数据。

Autograd 的微分

观察autograd如何收集梯度。利用requires_grad=True创建两个张量ab

1
2
3
4
import torch

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

利用a和b创建张量Q。
$$
Q=3a^3-b^2
$$

1
Q = 3*a**3 - b**2

ab是神经网络的参数,Q是误差。在NN训练中,我们要Q分别对a、b的梯度
$$
\begin{cases}
\frac{\partial Q}{\partial a} = 9a^2
\
\ \frac{\partial Q}{\partial b} = -2b

\end{cases}
$$
Q上调用.backward()时,Autograd 将计算这些梯度并将其存储在各个张量的.grad属性中。

Q.backward()中显式传递gradient梯度参数,因为它是向量,也就是说梯度反向传播时,会将公式变成矩阵形式进行运算。 gradient梯度是与Q形状相同的张量,它表示Q相对于本身的梯度,即
$$
\frac{\partial Q}{\partial Q} = 1
$$
我们也可以将Q聚合为一个标量,然后隐式地向后调用,例如Q.sum().backward()

1
2
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)

梯度现在沉积在a.gradb.grad

1
2
3
# check if collected gradients are correct
print(9*a**2 == a.grad)
print(-2*b == b.grad)

output:

1
2
tensor([True, True])
tensor([True, True])

使用 autograd 的向量微积分

$$
\begin{cases}
\vec{y}=f(\vec{x})……(\mathcal{1})\
l=g(\vec{y})……(\mathcal{2})

\end{cases}
$$

由公式1,我们可以知道 向量y相对于向量x的雅可比矩阵有J:
$$
J=
\begin{pmatrix}
\frac{\partial y}{\partial x_{1}} & \dots &\frac{\partial y}{\partial x_{n}}
\end{pmatrix}
=
\begin{pmatrix}
\frac{\partial y_{1}}{\partial x_{1}} & \dots & \frac{\partial y_{1}}{\partial x_{n}} \
\vdots & \ddots & \vdots \
\frac{\partial y_{m}}{\partial x_{1}} & \dots & \frac{\partial y_{m}}{\partial x_{n}}
\end{pmatrix}
$$
如果v恰好是标量函数的梯度:
$$
\vec{v}
=
\begin{pmatrix}
\frac{\partial l}{\partial y_{1}} & \dots &\frac{\partial l}{\partial y_{m}}
\end{pmatrix} ^T
$$

torch.autograd 是用于计算向量雅可比积的引擎。 也就是说,给定任何向量v,计算乘积J^T · v

也就是 l 相对于 向量x 的 梯度
$$
\frac{\partial l}{\partial \vec{x} }

=J^T \cdot \vec{v}

\begin{pmatrix}
\frac{\partial y_{1}}{\partial x_{1}} & \dots & \frac{\partial y_{1}}{\partial x_{n}} \
\vdots & \ddots & \vdots \
\frac{\partial y_{m}}{\partial x_{1}} & \dots & \frac{\partial y_{m}}{\partial x_{n}}
\end{pmatrix}

\begin{pmatrix}
\frac{\partial l}{\partial y_{1}} \
\vdots \
\frac{\partial l}{\partial y_{m}}
\end{pmatrix}
=
\begin{pmatrix}
\frac{\partial l}{\partial x_{1}} \
\vdots \
\frac{\partial l}{\partial x_{n}}
\end{pmatrix}
$$
external_grad表示v

计算图

Autograd 在由函数对象组成的有向无环图(DAG)中记录数据(张量)和所有已执行的操作(以及由此产生的新张量)。

在正向传播中,Autograd 同时执行两项操作:

  • 运行请求的操作以计算结果张量,并且
  • 在 DAG 中维护操作的梯度函数

当在 DAG 根目录上调用.backward()时,后退通道开始。 autograd然后:

  • 从每个.grad_fn计算梯度,
  • 将它们累积在各自的张量的.grad属性中,然后
  • 使用链式规则,一直传播到叶子张量。

../../_img/dag_autograd.png

箭头指向前进的方向。

节点代表正向传播中每个操作的反向函数。

蓝色的叶节点代表我们的叶张量ab

可以修改属性从而达到将齐排除在DAG中:

torch.autograd跟踪所有将其requires_grad标志设置为True的张量的操作。 对于不需要梯度的张量,将此属性设置为False会将其从梯度计算 DAG 中排除。

所以可以修改 requires_grad的属性为 “False” ,从而达到不进行梯度运算。

1
2
3
4
5
6
7
8
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}")

output:

1
2
Does `a` require gradients? : False
Does `b` require gradients?: True

在 NN 中,不计算梯度的参数通常称为冻结参数

举例

将部分参数进行冻结的话,会达到不需要的函数不会进行梯度运算,从而达到减少自动梯度计算,这会带来性能优势。

在微调中,我们冻结了大部分模型,通常仅修改分类器层以对新标签进行预测。

和上面一样,先加载模型并将所有参数进行冻结。

1
2
3
4
5
6
7
from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
param.requires_grad = False

假设我们要在具有 10 个标签的新数据集中微调模型。 在 resnet 中,分类器是最后一个线性层model.fc。 我们可以简单地将其替换为充当我们的分类器的新线性层(默认情况下未冻结)。

1
model.fc = nn.Linear(512, 10)

除了model.fc的参数外,模型中的所有参数都将冻结。 计算梯度的唯一参数是model.fc的权重和偏差。

1
2
# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

因此分类器的权重和偏差会在梯度下降运算中进行调整。