PyTorch详细教程

一、参考资料

PyTorch中文文档

PyTorch官方文档

PyTorch官方源码:GitHub - pytorch/pytorch: Tensors and Dynamic neural networks in Python with strong GPU acceleration

PyTorch Python API

PyTorch C++ API

PyTorch 中文教程 & 文档

PyTorch零基础入门教程,CSDN专栏

PyTorch 专栏,CSDN专栏

二、相关介绍

1. 静态图

为了加速计算,一些框架会使用对神经网络“先编译,后执行”的静态图来描述网络。静态图的缺点是难以描述控制流(比如 if-else 分支语句和 for 循环语句),直接对其引入控制语句会导致产生不同的计算图。比如循环执行 n 次 a=a+b,对于不同的 n,会生成不同的计算图。

2. 推理模式

dropoutbatchnorm这样的运算符在推断和训练模式下的行为会有所不同。

# 将模型转换为推理模式
torch_model.eval()
torch_model.train(False)

4. ATen 算子

ATen 是 PyTorch 内置的 C++ 张量计算库,PyTorch 算子在底层绝大多数计算都是用 ATen 实现的。

5. Graph

Graph 拥有许多的 Node,这些 Node 由一个 Block 管理。所有 Node 组织成双向链表的形式,方便插入删除,其中返回值节点“Return Node”会作为这个双向链表的“哨兵”。双向链表通常会被拓扑排序,保证执行的正确性。

6. Block

编译原理中,Block基本块表示一系列不包含任何跳转指令的指令序列,由于基本块内的内容可以保证是顺序执行的,因此很多的优化都会以基本块作为前提。

Block表示一个 Node 的有序列表,代表输入的 Node 的kind=Param,代表输出的 Node 的kind=Return

实际上 Graph 本身隐含一个 root Block 对象,用来管理所有的 Node。部分 Node 可能还会存在 sub Block。

7. pass

TorchScript 解读(二):Torch jit tracer 实现解析

pass是一个来源于编译原理的概念,一个 TorchScript 的 pass 会接收一种中间表示(IR),遍历图中所有元素进行某种变换,生成满足某种条件的新 IR。

TorchScript 中定义了许多 pass 来优化 Graph。比如对于常规编译器很常见的 DeadCodeElimination(DCE),CommonSubgraphElimination(CSE)等等;也有一些针对深度学习的融合优化,比如 FuseConvBN 等;还有针对特殊任务的 pass,ONNX 的导出就是其中一类 pass。

三、分布式训练

pytorch set_epoch()方法

在分布式模式下,需要在每个 epoch 开始时调用 set_epoch() 方法,然后再创建 DataLoader迭代器,以使 shuffle 操作能够在多个 epoch 中正常工作。 否则,dataloader迭代器产生的数据将始终使用相同的顺序。

sampler = DistributedSampler(dataset) if is_distributed else None
loader = DataLoader(dataset, shuffle=(sampler is None),
                    sampler=sampler)
for epoch in range(start_epoch, n_epochs):
    if is_distributed:
        sampler.set_epoch(epoch)
    train(loader)

测试代码

Pytorch DistributedDataParallel 数据采样 shuffle - 知乎 (zhihu.com)

数据长度16,两张卡,每张卡8个数据,batch size 2,两个epoch,一个gpu 4次输出为一个 epoch。可以看到 cuda 1 和 cuda 0的结果是重复的。

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from torch.utils.data.distributed import DistributedSampler


torch.distributed.init_process_group(backend="nccl")

input_size = 5
output_size = 2
batch_size = 2
data_size = 16

local_rank = torch.distributed.get_rank()
torch.cuda.set_device(local_rank)
device = torch.device("cuda", local_rank)

class RandomDataset(Dataset):
    def __init__(self, size, length, local_rank):
        self.len = length
        self.data = torch.stack([torch.ones(5), torch.ones(5)*2,
                                 torch.ones(5)*3,torch.ones(5)*4,
                                 torch.ones(5)*5,torch.ones(5)*6,
                                 torch.ones(5)*7,torch.ones(5)*8,
                                 torch.ones(5)*9, torch.ones(5)*10,
                                 torch.ones(5)*11,torch.ones(5)*12,
                                 torch.ones(5)*13,torch.ones(5)*14,
                                 torch.ones(5)*15,torch.ones(5)*16]).to('cuda')

        self.local_rank = local_rank
    def __getitem__(self, index):

        return self.data[index]

    def __len__(self):
        return self.len
    
dataset = RandomDataset(input_size, data_size, local_rank)
sampler = DistributedSampler(dataset)
rand_loader = DataLoader(dataset=dataset,
                         batch_size=batch_size,
                         sampler=sampler)

e = 0
while e < 2:
    t = 0
    # 设置set_epoch(),可实现每次epoch每个GPU拿到的数据不同
    # sampler.set_epoch(e)
    for data in rand_loader:
        print(data)
    e+=1

四、新特性

8比特优化器

github仓库

一训练就显存爆炸?Facebook 推出 8 比特优化器,两行代码拯救你的显存!

超大模型训练

独家 | 如何在GPU资源受限情况下微调超大模型

五、PyTorch库

1. TorchServe

TorchServe官方仓库

TorchServe 详解:5 步将模型部署到生产环境

torchserve使用教程(踩坑记录)

TorchServe 是 PyTorch 中将模型部署到生产环境的首选解决方案。它是一个性能良好且可扩展的工具,用 HTTP 或 HTTPS API 封装模型。
在这里插入图片描述

七、常用API

torch.nn.Module.register_buffer()

PyTorch nn.Module中的self.register_buffer()解析

pytorch 中register_buffer()

深入理解Pytorch之register_buffer

功能:训练不更新,最后可保存。定义一组参数,该组参数的特别之处在于,模型训练时不会更新(即调用 optimizer.step() 后该组参数不会变化,只可人为地改变它们的值),但是保存模型时,该组参数又作为模型参数不可或缺的一部分被保存。
import torch.nn as nn
import torch
class net(nn.Module):
    def __init__(self):
        super(net,self).__init__()
        self.register_buffer("a",torch.ones(2,3))  #从此,self.a其实就是torch.ones(2,3)。
    def forward(self,x):
        return x+self.a  #使用

解释

register_buffer的作用是将 torch.ones(2,3) 这个tensor注册到模型的 buffers() 属性中,并命名为a,这代表a对应的是一个 持久态,不会梯度更新,但是能被模型的state_dict记录下来。可以理解为模型的常数。

注意,没有保存到模型的 buffers() 或 parameters() 属性中的参数是不会被记录到state_dict中的,在 buffers() 中的参数默认不会有梯度,parameters() 中的参数默认有梯度。

requires_grad=False

不会注册到模型参数中model.parameters()
会注册到模型model.state_dict()

torch.onnx.export()

模型部署入门教程(三):PyTorch 转 ONNX 详解

torch.onnx.export中需要的模型实际上是一个torch.jit.ScriptModule

trace跟踪模式通过实际运行一遍模型的方法导出模型的静态图,即无法识别出模型中的控制流(如循环);script脚本模式则能通过解析模型来正确记录所有的控制流。
在这里插入图片描述

在这里插入图片描述

import torch
import torchvision


def export_onnx():
    model = torchvision.models.mobilenet_v2()
    model_name = "mobilenet_v2"

    model.eval()  # 若存在batchnorm、dropout层则一定要eval()!!!!再export

    BATCH_SIZE = 1
    dummy_input = torch.randn(BATCH_SIZE, 3, 224, 224)

    # 保存trace模型
    model_trace = torch.jit.trace(model, dummy_input)
    model_trace.save(f"{model_name}.pt")

    model_onnx = torch.jit.load(f"{model_name}.pt")
    model_onnx.eval()

    # 保存onnx模型
    torch.onnx.export(model_onnx,
                      dummy_input,
                      f"{model_name}.onnx",
                      opset_version=13,
                      do_constant_folding=True,
                      input_names=["input_0"],
                      output_names=["output_0"]
                      )


if __name__ == '__main__':
    export_onnx()
    
import torch 
 

class Model(torch.nn.Module): 
    def __init__(self, n): 
        super().__init__() 
        self.n = n 
        self.conv = torch.nn.Conv2d(3, 3, 3) 
 
    def forward(self, x): 
        for i in range(self.n): 
            x = self.conv(x) 
        return x 
 
 
models = [Model(2), Model(3)] 
model_names = ['model_2', 'model_3'] 
 
for model, model_name in zip(models, model_names): 
    dummy_input = torch.rand(1, 3, 10, 10) 
    dummy_output = model(dummy_input) 

    # 跟踪模式与直接 torch.onnx.export(model, ...)等价 
    model_trace = torch.jit.trace(model, dummy_input) 
    torch.onnx.export(model_trace, dummy_input, f'{model_name}_trace.onnx', example_outputs=dummy_output) 

    # 脚本模式必须先调用 torch.jit.script 
    model_script = torch.jit.script(model) 
    torch.onnx.export(model_script, dummy_input, f'{model_name}_script.onnx', example_outputs=dummy_output) 

scatter()

【Pytorch】scatter函数详解

target.scatter(dim, index, src)

按照指定的dim轴方向和index对应位置关系,用src张量中的元素逐个映射到target张量中的元素。

参数解释

  • target:即目标张量;
  • src:即源张量,将该张量上的元素逐个映射到目标张量上;
  • dim:指定轴方向,定义了填充方式。对于二维张量,dim=0表示逐列进行行填充,而dim=1表示逐列进行行填充;
  • index: 按照轴方向,在target张量中需要填充的位置;
import torch
a = torch.arange(10).reshape(2,5).float()
b = torch.zeros(3, 5))
b_= b.scatter(dim=0, index=torch.LongTensor([[1, 2, 1, 1, 2], [2, 0, 2, 1, 0]]),src=a)
print(b_)

# tensor([[0, 6, 0, 0, 9],
#        [0, 0, 2, 8, 0],
#        [5, 1, 7, 0, 4]])

整个函数的操作过程见下面的示意图。因为设定了dim=0,所以会逐列将source中的元素按照index中的位置信息,放入target张量中。
在这里插入图片描述
scatter函数的一个典型应用就是在分类问题中,将目标标签转换为one-hot编码形式,如:

labels = torch.LongTensor([1,3])
targets = torch.zeros(2, 5)
targets.scatter(dim=1, index=labels.unsqueeze(-1), src=torch.tensor(1))
# 注意dim=1,即逐样本的进行列填充
# 返回值为 tensor([[0, 1, 0, 0, 0],
#        [0, 0, 0, 1, 0]])

squeeze()

pytorch学习 中 torch.squeeze() 和torch.unsqueeze()的用法

功能:压缩维度,删除维度为1的维度。

import torch

"""
a = tensor([[[[1.],
          [1.]]]])
"""
a = torch.ones([1, 1, 2, 1])  # 维度(1, 1, 2, 1)
# 删掉a中所有维度为1的,不为1的维度没有影响

"""
a = tensor([1., 1.])
"""
a = torch.squeeze(a)  # 维度(2, )

"""
b = tensor([[[[1.],
          [1.]]]])
"""
b = torch.ones([1, 1, 2, 1])  # 维度(1, 1, 2, 1)

"""
tensor([[[1.],
         [1.]]])
"""
# 如果第N维度为1,则删除该维度
b = b.squeeze(0)  # 维度(1, 2, 1)

"""
tensor([[1.],
        [1.]])
"""
# 如果第N维度为1,则删除该维度
b = torch.squeeze(b, 0)  # 维度(2, 1)

unsqueeze()

功能:在指定位置扩充维度。
函数原型:unsqueeze(dim)

import torch

# tensor([3])
a = torch.tensor([3])  # 维度 (1,)

# tensor([[3]])
# 在a的0位置加上一个维度为1的维度
a = a.unsqueeze(0)  # 维度 (1, 1)

# tensor([[[3]]])
# 在a的0位置加上一个维度为1的维度
b = torch.unsqueeze(a, 0)  # 维度 (1, 1, 1)

torch.cuda

# Use the GPU if there is one, otherwise CPU
device = 'cuda:0' if torch.cuda.is_available() else 'cpu'

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model.eval()

pytorch保存模型后加载模型遇到的大坑

在模型推理之前必须调用 model.eval(),将 dropout 和 BN层设置为eval模式。如果不这样做,将会产生不一致的推断结果。

# 保存模型
torch.save(model.state_dict(), '/PATH/TO/params.pt')

# 加载模型
# 实例化model模型,重构模型结构
model = My_model(*args, **kwargs)  
# 根据模型结构,加载模型参数
model.load_state_dict(torch.load('/PATH/TO/params.pt'))  
model.eval()  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花花少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值