关于torch.autograd的介绍
torch.autograd在pytorch的神经网络训练时用来自动计算梯度
一、背景
神经网络是对输入数据进行操作的一堆函数的集合,其中这一堆函数包括的参数为权重和偏差,存储在tensor张量中。
不了解的可以从李宏毅老师的机器学习视频中学习神经网络的基础知识。
训练神经网络时包含两步:
- 前向传递
神经网络根据输入数据和这一堆函数,得出预测结果。 - 反向传递
根据预测的结果调整神经网络中的参数(过程中需要计算梯度,torch.autograd在这里发挥作用)
二、在Pytorch中的使用
- 生成官方的案例数据
import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
模型是torchvision里面一个预训练的模型,用rand随机产生了一个data代表一张图片,RGB三个通道,64x64大小,所以是torch.rand(1,3,64,64)
labels是随机产生的标签。
- 前向传递,data给模型进行预测。
prediction = model(data)
- 损失函数的计算,计算出预测值和标签值之间的差异
loss = (prediction - labels).sum()
loss.backward() # backward pass
- 加载优化器,用来优化模型中的参数
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
SGD是用来计算梯度的一种方法,lr是learning rate学习速率,momentum应该是动量值?表示梯度下降时的惯性?
- 优化模型中的参数,通过step()来启动梯度下降
optim.step()
三、Autograd中的微分运算
生成a,b两个变量,并给定requires_grad=True,可以记录梯度。
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
定义: Q = 3 a 3 − b 2 Q = 3a^3 - b^2 Q=3a3−b2
Q = 3*a**3 - b**2
则偏导数为:
∂
Q
∂
a
=
9
a
2
\frac{\partial Q}{\partial a} = 9a^2
∂a∂Q=9a2
∂
Q
∂
b
=
−
2
b
\frac{\partial Q}{\partial b} = -2b
∂b∂Q=−2b
因为Q是一个向量,所以pytorch认为Q的每个元素相当于一个中间变量,那么在求导时根据链式法则,Q和各个元素之间还应该对应有一层函数关系,所以在求梯度时需要手动给定Q和各个元素的偏导关系,由于这里定义误差函数为Q的两个元素求和,所以这个偏导关系应该为[1,1]。代码如下:
external_grad = torch.tensor([1,1])
Q.backward(gradient=external_grad)
# 求梯度的另一种表达方式
Q.sum().backward()
# 检查结果是否对应
print(9*a**2 == a.grad)
print(-2*b == b.grad)
关于backward为什么要给gradient这个参数的理解可以参考博客:gradient参数理解
或者看下面官方的解释:
对于一个向量函数
y
⃗
=
f
(
x
⃗
)
\vec{y}=f(\vec{x})
y=f(x),它的梯度应该是一个雅可比矩阵:
J
=
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
J = \begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix}
J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞
autograd其实计算的是雅可比矩阵
J
J
J和前面给的那个向量gradient(记为
v
⃗
\vec{v}
v)的点乘。即计算的是
J
T
⋅
v
⃗
J^T \cdot \vec{v}
JT⋅v
如果
v
⃗
\vec{v}
v恰好是另一个函数
l
=
g
(
y
⃗
)
l=g(\vec{y})
l=g(y)的偏导数:
v
⃗
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
T
\vec{v} = \begin{pmatrix} \frac{\partial l}{\partial y_1} & \cdots & \frac{\partial l}{\partial y_m} \end{pmatrix}^T
v=(∂y1∂l⋯∂ym∂l)T
那么
J
T
⋅
v
⃗
J^T\cdot\vec{v}
JT⋅v恰好是
l
l
l对
x
⃗
\vec{x}
x的梯度:
J
T
⋅
v
⃗
=
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
=
(
∂
l
∂
x
1
⋯
∂
l
∂
x
n
)
J^T\cdot\vec{v}= \begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix} \begin{pmatrix} \frac{\partial l}{\partial y_1}\\ \cdots\\ \frac{\partial l}{\partial y_m} \end{pmatrix}= \begin{pmatrix} \frac{\partial l}{\partial x_1}\\ \cdots\\ \frac{\partial l}{\partial x_n} \end{pmatrix}
JT⋅v=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞⎝⎛∂y1∂l⋯∂ym∂l⎠⎞=⎝⎛∂x1∂l⋯∂xn∂l⎠⎞
从这个角度来看,代码中的Q的各个元素相当于这里的
y
1
,
y
2...
y1,y2...
y1,y2..., 所以需要给定gradient参数(
v
⃗
\vec{v}
v)
附录
计算图
蓝色方框代表a,b,backward方框代表反向传递计算梯度时所需的反向传递函数。该图为有向无闭环图Directed Acyclic Graph(DAG)
固定神经网络中的某些参数
在神经网络中不需要计算梯度的参数成为冻结参数frozen parameters。在微调一个预训练模型时,冻结参数往往很有用。
例如,给定一个官方的预训练模型,并冻结其参数:
from torch import nn, optim
model = torchvision.models.resnet18(pretrained=True)
# 冻结神经网络中所有参数
for param in model.parameters():
param.requires_grad = False
假如我们想微调模型,让它的输出可以适合分类10种标签的任务,分类器一般在模型最后的线性层model.fc
。可以直接对其进行替换:
model.fc = nn.Linear(512, 10)
现在所有的模型参数,除了最后的线性层之外,其他参数均被冻结,所以只有最后线性层的参数可以被更新。使用SGD自动计算梯度并优化参数
# 优化
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)