文章目录
一、PyTorch 简介
1.1、PyTorch 模块
Component | Description |
---|---|
torch | A Tensor library like NumPy, with strong GPU support |
torch.utils | DataLoader and other utility functions for convenience |
torch.nn | A neural networks library deeply integrated with autograd designed for maximum flexibility |
torch.autograd | A tape-based automatic differentiation library that supports all differentiable Tensor operations in torch |
torch.multiprocessing | Python multiprocessing , but with magical memory sharing of torch Tensors across processes. Useful for data loading and Hogwild training |
torch.jit | A compilation stack (TorchScript ) to create serializable and optimizable models from PyTorch code |
1.2、PyTorch 计算图
1.2.1、静态计算图
老版的TensorFlow 使用静态图(提前创建好) 来表示计算任务,由节点和边组成,节点表示 Tensor(
输入、输出、W、B
) 或者OP(运算符)
,边(数据流动边、梯度流动边)表示 Tensor 和 OP 之间的依赖关系;Tensor
在图中通过运算符(OP, eg:数学运算 ADD、激活函数、神经网络层)
进行传递和变换
, 一个op
可以获得 0 个(输入叶子节点)或多个张量(Tensor), 通过创建会话(Session)对象来执行计算,产生 0 个(输出根节点)或多个Tensor。所以,TensorFlow 的工作模式分为以下两步:
- Define the
computation graph
- Run the graph (with data) in
Session
为什么需要提前定义好计算图,然后再去执行运算?
Save computation
:only run subgraphs that lead to the values you want to fetchFacilitate distributed computation
: spread the work across multiple CPUs, GPUs, or devicesFacilitates auto-differentiation
:break computation into small, differential pieces
1.2.2、动态计算图
Pytorch 中的计算图是动态图,由节点和边组成,节点表示 Tensor(
输入、输出、W、B
) 或者OP(运算符)
,边(数据流动边、梯度流动边)表示 Tensor 和 OP 之间的依赖关系
Tensor
在图中通过运算符(OP, eg:数学运算 ADD、激活函数、神经网络层)
进行传递和变换
, 一个op
可以获得 0 个(输入叶子节点)或多个张量(Tensor), 通过 OP 运算,产生 0 个(输出根节点)或多个Tensor,一般 op 可继承于torch.autograd.Function
,同时支持正向传播和反向传播- 动态图的正向传播是立即执行得出结果的(
方便 Debug
),无需等待完整的计算图创建完毕- 动态图在反向传播后立即销毁,释放存储空间,下次调用需要重新构建计算图
- 如果程序中使用了
torch.autograd.backward
方法执行了反向传播,那么创建的计算图会被立即销毁,下次调用需要重新创建- 如果程序中使用了
torch.autograd.grad
方法计算了梯度,那么创建的计算图会被立即销毁,下次调用需要重新创建- 若不想立即销毁,则需指明
retain_graph
参数为True
来保留计算图
1.2.3、叶子节点和根节点
- 叶子结点是最基础的结点(图中 w 和 x),其数据不是由运算生成的,因此是整个计算图的基石,是不可轻易”修改“的;张量有一个属性是
is_leaf
, 用来指示一个张量是否为叶子结点 - 最终计算得到的
y
就是根节点,就像一棵树一样,叶子在上面,根在下面 - 依赖于叶子结点的结点,
requires_grad
默认为True
:因为叶子结点梯度的计算是从后向前传播的,因此与其相关的结点均需要计算梯度 - 叶子张量不可以执行
in-place
操作:因为计算图的backward
过程都依赖于叶子结点的计算,所有的偏微分计算所需要用到的数据都是基于叶子结点的,因此叶子结点不允许in-place
操作
- 非叶子结点在梯度反向传播结束后释放;只有叶子节点的梯度得到保留,中间变量的梯度默认不保留
- 在pytorch中,非叶子结点的梯度在反向传播结束之后就会被释放掉,如果需要保留的话可以对该结点设置
retain_grad()
- 在调用 backward() 方法后,叶子节点的梯度(
grad
)会在反向传播的过程中累加。所以在梯度进行参数更新后(下一次反向传播前)需要把梯度清零x.grad.zero_() or optimizer.zero_grad()
- 在pytorch中,非叶子结点的梯度在反向传播结束之后就会被释放掉,如果需要保留的话可以对该结点设置
二、PyTorch 张量系统:torch
- 参考此 blog: Pytorch 张量系统:Tensor
三、PyTorch 自动求导机制:torch.autograd
torch.autograd
能够根据输入和前向传播过程 自动构建计算图,并自动计算梯度用于反向传播:
- 每个 Tensor 都有一个
requires_grad
标志,默认为False
,可用于冻结某些层的参数- 如果一个节点
requires_grad
被设置为True
,那么计算图中所有依赖它的节点都需要求解梯度- 优化器使用
autograd
来计算每个参数的梯度(存储在tensor
的.grad
属性中)并进行更新
# requires_grad=False by default
>>> x = torch.randn(5, 5)
>>> y = torch.randn(5, 5)
>>> z = torch.randn((5, 5), requires_grad=True)
>>> a = x + y
>>> a.requires_grad
False
>>> b = a + z
# 依赖于叶子结点的结点,requires_grad 默认为 True,叶子节点(z)不可执行 in-place 操作
>>> b.requires_grad
True
3.1、torch.autograd.backward:自动求取梯度
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None, inputs=None)
tensors(Sequence[Tensor] or Tensor)
: 用于求导的张量,如 lossgrad_tensors(Sequence[Tensor or None] or Tensor, optional)
:与根节点 loss 形状相同的张量,最终梯度为 loss 求出梯度与 grad_tensors 张量的点积
;在 Tensor 的方法中是gradient
参数- 若调用的 loss 是标量(或者 loss 张量通过
loss.sum()
转成标量),则可以不提供此参数,默认为 1;有多个 loss 时,若提供该参数则可以看做是各个 loss 的权重 - 若调用的 loss 是张量,则必须提供与 loss
相同形状
的参数
- 若调用的 loss 是标量(或者 loss 张量通过
retain_graph(bool, optional)
:- 保持计算图,如果需要多次调用 backward 可设置为 True,但梯度会累加
- 当有多个输出需要同时进行梯度反传时,需要将其设置为
True
,从而保证在计算多个输出的梯度时互不影响
create_graph(bool, optional)
: 创建导数计算图,用于高阶求导inputs (Sequence[Tensor] or Tensor, optional)
:输入数据Note
:- 我们使用时候都是在张量上直接调用
.backward()
函数,但这里却是torch.autograd.backward
,为什么不一样呢? 其实Tensor.backward()
接口内部调用了torch.autograd.backward
- 调用 backward() 方法后,叶子节点的梯度(
grad
)会在反向传播的过程中累加。所以在梯度进行参数更新后(下一次反向传播前)需要把梯度清零x.grad.zero_() or optimizer.zero_grad()
- 我们使用时候都是在张量上直接调用
########################## 0、Tensor 中 backward 方法的定义 ##########################
def backward(self, gradient=None, retain_graph=None, create_graph=False, inputs=None):
if has_torch_function_unary(self):
return handle_torch_function(
Tensor.backward,
(self,),
self,
gradient=gradient, # gradient == grad_tensors
retain_graph=retain_graph,
create_graph=create_graph,
inputs=inputs,
)
torch.autograd.backward(
self, gradient, retain_graph, create_graph, inputs=inputs
)
########################## 1、gradient 与 retain_graph 的用法 ##########################
import torch
x = torch.tensor([[0.0, 0.0], [1.0, 2.0]], requires_grad=True) # x 需要被求导
a = torch.tensor(1.0)
b = torch.tensor(-2.0)
c = torch.tensor(1.0)
y = a * torch.pow(x, 2) + b * x + c # f(x) = a*x**2 + b*x + c
gradient = torch.tensor([[1.0, 1.0], [1.0, 2.0]])
y.backward(gradient=gradient, retain_graph=True) # 转换成标量来进行反向传播:torch.sum(y*gradient).backward()
print("y_grad:\n", y.grad) # None, 为了节约存储空间,非叶子节点的梯度均为 None,只在计算过程中被用到,不会最终存储到 grad 属性中
print("x_grad:\n", x.grad)
y.backward(gradient=gradient)
print("x_grad:\n", x.grad) # retain_graph=True 会出现梯度累积,可在 backward 前执行 x.grad.zero_() 进行梯度清零
# 输出如下
y_grad:
None
x_grad:
tensor([[-2., -2.],
[ 0., 4.]])
x_grad: # 梯度未清零造成梯度累积
tensor([[-4., -4.],
[ 0., 8.]])
########################## 2、非叶子节点梯度显示 ##########################
import torch
# 正向传播
x = torch.tensor(3.0, requires_grad=True)
y1 = x + 1
y2 = 2 * x
loss = (y1 - y2) ** 2
# 非叶子节点梯度显示控制
# 1、利用 retain_grad 可以保留非叶子节点的梯度值
# 2、利用 register_hook 可以查看非叶子节点的梯度值(需要定义在 backward 前面),但不会保留其值
y1.register_hook(lambda grad: print('y1.register_hook grad: ', grad))
y2.register_hook(lambda grad: print('y2.register_hook grad: ', grad))
y1.retain_grad()
y2.retain_grad()
loss.retain_grad()
# 反向传播
loss.backward() # 通过 register_hook 可以查看非叶子节点的梯度值
print("loss.grad:", loss.grad)
print("y1.grad:", y1.grad)
print("y2.grad:", y2.grad)
print("x.grad:", x.grad)
# 输出如下
y2.register_hook grad: tensor(4.)
y1.register_hook grad: tensor(-4.)
loss.grad: tensor(1.) # 若 loss 为高维张量,则输出高维张量
y1.grad: tensor(-4.)
y2.grad: tensor(4.)
x.grad: tensor(4.)
########################## 3、网络前向传播和反向传播求解梯度,更新参数示例 ##########################
import torch
import torchvision
# 生成数据
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
# 构建模型
model = torchvision.models.resnet18(weights="ResNet18_Weights.DEFAULT")
# 指定参数学习算法
optimizer = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
# 模型训练
for i in range(500):
# 前向传播 [1, 1000]
prediction = model(data)
# 计算 loss:使用 .sum() 方法求和,loss 是一个标量
loss = (prediction - labels).sum() # 通常使用 loss = criterion(outputs, labels),返回标量
# 反向传播 loss:实际调用 torch.autograd.backward 为每个模型参数计算梯度并将其存储在参数的.grad 属性中
loss.backward()
# 参数更新:调用 .step() 启动梯度下降,优化器通过.grad 中存储的梯度来调整每个参数
optimizer.step()
# 清空梯度:调用 backward 会导致导数值在叶节点处累积,所以将其用于参数更新后,需要将梯度显式清零
optimizer.zero_grad()
print("iter {}".format(i))
########################## 4、网络微调示例 ##########################
import torch
import torchvision
# 生成数据
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 10)
# 构建模型
model = torchvision.models.resnet18(weights="ResNet18_Weights.DEFAULT")
for param in model.parameters():
param.requires_grad = False
# Replace the last fully-connected layer
# Parameters of newly constructed modules have requires_grad=True by default
model.fc = torch.nn.Linear(512, 10) # 1000-->10 类
# 指定参数学习算法:Optimize only the classifier
optimizer = torch.optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9) # 只更新 fc 层进行 finetune
# 模型训练
for i in range(500):
# 前向传播 [1, 10]
prediction = model(data)
# 计算 loss:使用 .sum() 方法求和,loss 是一个标量
loss = (prediction - labels).sum()
# 反向传播 loss:实际调用 torch.autograd.backward 为每个模型参数计算梯度并将其存储在参数的.grad 属性中
loss.backward()
# 参数更新:调用 .step() 启动梯度下降,优化器通过.grad 中存储的梯度来调整每个参数
optimizer.step()
print("iter {}".format(i))
3.2、torch.autograd.grad:求取梯度
torch.autograd.grad(outputs, inputs, grad_outputs = None, retain_graph = None, create_graph = False)
outputs
: 用于求导的张量,如 loss,如果有多个因变量,相当于把多个因变量的梯度结果求和inputs
: 需要求解梯度的张量,允许同时对多个自变量求导数create_graph
: 创建导数计算图,用于高阶求导retain_graph
: 保持计算图grad_outputs
:多梯度权重
import torch
x = torch.tensor(0.0, requires_grad=True) # x 需要被求导
a = torch.tensor(1.0)
b = torch.tensor(-2.0)
c = torch.tensor(1.0)
y = a * torch.pow(x, 2) + b * x + c # f(x) = ax**2 + bx + c
# create_graph 设置为 True 将允许创建更高阶的导数
dy_dx = torch.autograd.grad(y, x, create_graph=True)[0] # f(x)‘ = 2ax+b
print(dy_dx.data) # tensor(-2.)
# 求二阶导数
dy2_dx2 = torch.autograd.grad(dy_dx, x)[0] # f(x)" = 2
print(dy2_dx2.data) # tensor(2.)
3.3、使用梯度上下文管理器控制自动求导
- autograd 自动构建计算图过程中会保存一系列中间变量,以便于backward 的计算,这就必然需要花费额外的内存和时间。
- 而并不是所有情况下都需要backward,例如推理的时候,因此可以采用上下文管理器
torch.no_grad()
来管理上下文,让 pytorch 不记录相应的变量,以加快速度和节省空间
# torch.no_grad()/torch.set_grad_enabled()/torch.enable_grad() 的上下文管理器可用于控制是否需要自动求导
>>> x = torch.zeros(1, requires_grad=True)
>>> with torch.no_grad():
... y = x * 2
>>> y.requires_grad
False
>>> is_train = False
>>> with torch.set_grad_enabled(is_train):
... y = x * 2
>>> y.requires_grad
False
3.4、使用 torch.autograd.Function
实现自定义 OP
- 参考:https://tingsongyu.github.io/PyTorch-Tutorial-2nd/chapter-2/2.6-autograd.html
- 有的时候,想要实现自己的一些操作(op),如特殊的数学函数、pytorch 的 module 中没有的网络层,那就需要自己写一个 Function,在Function 中定义好 forward 的计算公式、backward 的计算公式,然后将这些 op 组合到模型中,模型就可以用 autograd 完成梯度求取。
- eg:假设需要一个计算指数的功能,并且能组合到模型中,实现 autograd,那么可以这样实现:
- 第一步:
继承 Function
- 第二步:
实现 forward
- 第三步:
实现 backward
- 注意事项:forward 和 backward 函数第一个参数为
ctx
,它的作用类似于类函数的self
一样,更详细解释可参考如下: In the forward pass we receive a Tensor containing the input and return a Tensor containing the output.ctx is a context object that can be used to stash information for backward computation.
You can cache arbitrary objects for use in the backward pass using the ctx.save_for_backward method; backward 函数返回的参数个数与 forward 的输入参数个数相同, 即,传入该 op 的参数,都需要给它们计算对应的梯度。
import torch
from torch.autograd.function import Function
class Exp(Function):
@staticmethod
def forward(ctx, i):
# ============== step1: 函数功能实现 ==============
result = i.exp()
# ============== step1: 函数功能实现 ==============
# ============== step2: 结果保存,用于反向传播 ==============
ctx.save_for_backward(result)
# ============== step2: 结果保存,用于反向传播 ==============
return result
@staticmethod
def backward(ctx, grad_output):
# ============== step1: 取出结果,用于反向传播 ==============
result, = ctx.saved_tensors
# ============== step1: 取出结果,用于反向传播 ==============
# ============== step2: 反向传播公式实现 ==============
grad_results = grad_output * result
# ============== step2: 反向传播公式实现 ==============
return grad_results
x = torch.tensor([1.], requires_grad=True)
y = Exp.apply(x) # 需要使用apply方法调用自定义autograd function
print(y) # y = e^x = e^1 = 2.7183
y.backward()
print(x.grad) # 反传梯度, x.grad = dy/dx = e^x = e^1 = 2.7183
# 关于本例子更详细解释,推荐阅读 https://zhuanlan.zhihu.com/p/321449610
四、PyTorch 层级结构: torch.nn
- 参考此 blog:Pytorch 层级结构
五、PyTorch 并行:torch.multiprocessing
- 参考此 blog: PyTorch 分布式训练
六、PyTorch 常用工具:torch.utils
- 参考如下 blog:
七、参考资料
1、https://pytorch.org/
2、https://github.com/pytorch
3、https://github.com/pytorch/pytorch
4、https://pytorch.org/docs/stable/index.html
5、PyTorch 中文文档
6、https://pytorch.apachecn.org/
7、https://github.com/awfssv/pytorch-cn
8、pytorch 多 gpu并行训练
9、Pytorch多机多卡分布式训练
10、https://download.pytorch.org/whl/torch_stable.html
11、https://tingsongyu.github.io/PyTorch-Tutorial-2nd/