模型部署 - onnx 的导出和分析 -(1) - PyTorch 导出 ONNX - 学习记录

onnx 的导出和分析

  • 一、PyTorch 导出 ONNX 的方法
    • 1.1、一个简单的例子 -- 将线性模型转成 onnx
    • 1.2、导出多个输出头的模型
    • 1.3、导出含有动态维度的模型
  • 二、pytorch 导出 onnx 不成功的时候如何解决
    • 2.1、修改 opset 的版本
    • 2.2、替换 pytorch 中的算子组合
    • 2.3、在 pytorch 登记( 注册 ) onnx 中某些算子
      • 案例一:
      • 2.3.1、注册方法一
      • 2.3.2、注册方法二
      • 案例二:
    • 2.4、直接修改 onnx,创建 plugin

一、PyTorch 导出 ONNX 的方法

1.1、一个简单的例子 – 将线性模型转成 onnx

首先我们用 pytorch 定义一个线性模型,nn.Linear : 线性层执行的操作是 y = x * W^T + b,其中 x 是输入,W 是权重,b 是偏置。(实际上就是一个矩阵乘法)

class Model(torch.nn.Module):
    def __init__(self, in_features, out_features, weights, bias=False):
        super().__init__()
        self.linear = nn.Linear(in_features, out_features, bias)
        with torch.no_grad():
            self.linear.weight.copy_(weights)
    
    def forward(self, x):
        x = self.linear(x)
        return x

然后我们再定义一个函数,用于导出 onnx

def export_onnx():
    input   = torch.zeros(1, 1, 1, 4)
    weights = torch.tensor([
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6]
    ],dtype=torch.float32)
    model   = Model(4, 3, weights)
    model.eval() #添加eval防止权重继续更新
    
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = "model.onnx",
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 12)
    print("Finished onnx export")

可以看到,这里面的关键在函数 torch.onnx.export(),这是 pytorch 导出 onnx 的基本方式,这个函数的参数有很多,但只要一些基本的参数即可导出模型,下面是一些基本参数的定义:

  • model (torch.nn.Module): 需要导出的PyTorch模型
  • args (tuple or Tensor): 一个元组,其中包含传递给模型的输入张量
  • f (str): 要保存导出模型的文件路径。
  • input_names (list of str): 输入节点的名字的列表
  • output_names (list of str): 输出节点的名字的列表
  • opset_version (int): 用于导出模型的 ONNX 操作集版本

最后我们完整的运行一下代码:

import torch
import torch.nn as nn
import torch.onnx

class Model(torch.nn.Module):
    def __init__(self, in_features, out_features, weights, bias=False):
        super().__init__()
        self.linear = nn.Linear(in_features, out_features, bias)
        with torch.no_grad():
            self.linear.weight.copy_(weights)
    
    def forward(self, x):
        x = self.linear(x)
        return x

def export_onnx():
    input   = torch.zeros(1, 1, 1, 4)
    weights = torch.tensor([
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6]
    ],dtype=torch.float32)
    model   = Model(4, 3, weights)
    model.eval() #添加eval防止权重继续更新

    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = "model.onnx",
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 12)
    print("Finished onnx export")


if __name__ == "__main__":
    export_onnx()

导出模型后,我们用 netron 查看模型,在终端输入

netron model.onnx

在这里插入图片描述

1.2、导出多个输出头的模型

第一步:定义一个多输出的模型:

class Model(torch.nn.Module):
    def __init__(self, in_features, out_features, weights1, weights2, bias=False):
        super().__init__()
        self.linear1 = nn.Linear(in_features, out_features, bias)
        self.linear2 = nn.Linear(in_features, out_features, bias)
        with torch.no_grad():
            self.linear1.weight.copy_(weights1)
            self.linear2.weight.copy_(weights2)
 
    def forward(self, x):
        x1 = self.linear1(x)
        x2 = self.linear2(x)
        return x1, x2

第二步:编写导出 onnx 的函数

def export_onnx():
    input    = torch.zeros(1, 1, 1, 4)
    weights1 = torch.tensor([
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6]
    ],dtype=torch.float32)
    weights2 = torch.tensor([
        [2, 3, 4, 5],
        [3, 4, 5, 6],
        [4, 5, 6, 7]
    ],dtype=torch.float32)
    model   = Model(4, 3, weights1, weights2)
    model.eval() #添加eval防止权重继续更新

    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = "model.onnx",
        input_names   = ["input0"],
        output_names  = ["output0", "output1"],
        opset_version = 12)
    print("Finished onnx export")

可以看到,和例 1.1 不一样的地方是 torch.onnx.export 的 output_names
例1.1:output_names = [“output0”]
例1.2:output_names = [“output0”, “output1”]

运行一下完整代码:

import torch
import torch.nn as nn
import torch.onnx

class Model(torch.nn.Module):
    def __init__(self, in_features, out_features, weights1, weights2, bias=False):
        super().__init__()
        self.linear1 = nn.Linear(in_features, out_features, bias)
        self.linear2 = nn.Linear(in_features, out_features, bias)
        with torch.no_grad():
            self.linear1.weight.copy_(weights1)
            self.linear2.weight.copy_(weights2)
              
    def forward(self, x):
        x1 = self.linear1(x)
        x2 = self.linear2(x)
        return x1, x2

def export_onnx():
    input    = torch.zeros(1, 1, 1, 4)
    weights1 = torch.tensor([
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6]
    ],dtype=torch.float32)
    weights2 = torch.tensor([
        [2, 3, 4, 5],
        [3, 4, 5, 6],
        [4, 5, 6, 7]
    ],dtype=torch.float32)
    model   = Model(4, 3, weights1, weights2)
    model.eval() #添加eval防止权重继续更新
    
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = "model.onnx",
        input_names   = ["input0"],
        output_names  = ["output0", "output1"],
        opset_version = 12)
    print("Finished onnx export")

if __name__ == "__main__":
    export_onnx()

用 netron 查看模型,结果如下,模型多出了一个输出结果
在这里插入图片描述

1.3、导出含有动态维度的模型

完整运行代码如下:

import torch
import torch.nn as nn
import torch.onnx

class Model(torch.nn.Module):
    def __init__(self, in_features, out_features, weights, bias=False):
        super().__init__()
        self.linear = nn.Linear(in_features, out_features, bias)
        with torch.no_grad():
            self.linear.weight.copy_(weights)
    
    def forward(self, x):
        x = self.linear(x)
        return x

def export_onnx():
    input   = torch.zeros(1, 1, 1, 4)
    weights = torch.tensor([
        [1, 2, 3, 4],
        [2, 3, 4, 5],
        [3, 4, 5, 6]
    ],dtype=torch.float32)
    model   = Model(4, 3, weights)
    model.eval() #添加eval防止权重继续更新
    
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = "model.onnx",
        input_names   = ["input0"],
        output_names  = ["output0"],
        dynamic_axes  = {
            'input0':  {0: 'batch'},
            'output0': {0: 'batch'}
        },
        opset_version = 12)
    print("Finished onnx export")

if __name__ == "__main__":
    export_onnx()

可以看到,比例 1.1 多了一行 torch.onnx.export 的 dynamic_axes 。我们可以用 dynamic_axes 来指定动态维度,其中 'input0': {0: 'batch'} 中的 0 表示在第 0 维度上的元素是动态的,这里取名为 ‘batch’

用 netron 查看模型:
在这里插入图片描述
可以看到相对于例1.1,他的维度 0 变成了动态的,并且名为 ‘batch’

二、pytorch 导出 onnx 不成功的时候如何解决

上面是 onnx 可以直接被导出的情况,是因为对应的 pytorch 和 onnx 版本都有相应支持的算子在里面。但是有些时候,我们不能顺利的导出 onnx,下面记录一下常见的解决思路 。

2.1、修改 opset 的版本

这是首先应该考虑的思路,因为有可能只是版本过低然后有些算子还不支持,所以考虑提高 opset 的版本

比如下面的这个报错,提示当前 onnx 的 opset 版本不支持这个算子,那我们可以去官方手册搜索一下是否在高的版本支持了这个算子
在这里插入图片描述

官方手册地址:https://github.com/onnx/onnx/blob/main/docs/Operators.md

在这里插入图片描述
又比如说 Acosh 这个算子,在 since version 9 才开始支持,那我们用 7 的时候就是不合适的,升级 opset 版本即可

2.2、替换 pytorch 中的算子组合

有些时候 pytorch 中的一些算子操作在 onnx 中并没有,那我们可以把这些算子替换成 onnx 支持的算子

2.3、在 pytorch 登记( 注册 ) onnx 中某些算子

案例一:

有些算子在 onnx 中是有的,但是在 pytorch 中没被登记,则需要注册一下
比如下面这个案例,我们想要导出 asinh 这个算子的模型

import torch
import torch.onnx

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
    
    def forward(self, x):
        x = torch.asinh(x)
        return x
        
def export_norm_onnx():
    input   = torch.rand(1, 5)
    model   = Model()
    model.eval()

    file    = "asinh.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 9)
    print("Finished normal onnx export")

if __name__ == "__main__":
    export_norm_onnx()

但是报错,提示 opset_version = 9 不支持这个算子
在这里插入图片描述

但是我们打开官方手册去搜索发现 asinh 在 version 9 又是支持的
在这里插入图片描述
这里的问题是 PyTorch 与 onnx 之间没有建立 asinh 的映射 (没有搭建桥梁),所以我们编写一个注册代码,来手动注册一下这个算子

2.3.1、注册方法一

完整代码如下:

import torch
import torch.onnx
import onnxruntime
from torch.onnx import register_custom_op_symbolic

def asinh_symbolic(g, input, *, out=None):
    return g.op("Asinh", input)
register_custom_op_symbolic('aten::asinh', asinh_symbolic, 12)

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
    
    def forward(self, x):
        x = torch.asinh(x)
        return x

def validate_onnx():
    input = torch.rand(1, 5)

    # PyTorch的推理
    model = Model()
    x     = model(input)
    print("result from Pytorch is :", x)

    # onnxruntime的推理
    sess  = onnxruntime.InferenceSession('asinh.onnx')
    x     = sess.run(None, {'input0': input.numpy()})
    print("result from onnx is:    ", x)

def export_norm_onnx():
    input   = torch.rand(1, 5)
    model   = Model()
    model.eval()

    file    = "asinh.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 12)
    print("Finished normal onnx export")

if __name__ == "__main__":
    export_norm_onnx()
    
    # 自定义完onnx以后必须要进行一下验证
    validate_onnx()

这段代码的关键在于 算子的注册:

1、定义 asinh_symbolic 函数

def asinh_symbolic(g, input, *, out=None):
    return g.op("Asinh", input)
  1. 函数必须是 asinh_symbolic 这个名字
  2. g: 就是 graph,计算图 (在计算图中添加onnx算子)
  3. input :symblic的参数需要与Pytorch的asinh接口函数的参数对齐
    (def asinh( input: Tensor, *, out: Optional[Tensor]=None) -> Tensor: … )
  4. 符号函数内部调用 g.op, 为 onnx 计算图添加 Asinh 算子
  5. g.op中的第一个参数是onnx中的算子名字: Asinh

2、使用 register_custom_op_symbolic 函数

register_custom_op_symbolic('aten::asinh', asinh_symbolic, 12)
  1. aten 是"a Tensor Library"的缩写,是一个实现张量运算的C++库
  2. asinh 是在名为 aten 的一个c++命名空间下进行实现的
  3. 将 asinh_symbolic 这个符号函数,与PyTorch的 asinh 算子绑定
  4. register_op 中的第一个参数是PyTorch中的算子名字: aten::asinh
  5. 最后一个参数表示从第几个 opset 开始支持(可自己设置)

3、自定义完 onnx 以后必须要进行一下验证,可使用 onnxruntime

2.3.2、注册方法二

import torch
import torch.onnx
import onnxruntime
import functools
from torch.onnx import register_custom_op_symbolic
from torch.onnx._internal import registration

_onnx_symbolic = functools.partial(registration.onnx_symbolic, opset=9)

@_onnx_symbolic('aten::asinh')
def asinh_symbolic(g, input, *, out=None):
    return g.op("Asinh", input)

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
    
    def forward(self, x):
        x = torch.asinh(x)
        return x

def validate_onnx():
    input = torch.rand(1, 5)

    # PyTorch的推理
    model = Model()
    x     = model(input)
    print("result from Pytorch is :", x)

    # onnxruntime的推理
    sess  = onnxruntime.InferenceSession('asinh2.onnx')
    x     = sess.run(None, {'input0': input.numpy()})
    print("result from onnx is:    ", x)

def export_norm_onnx():
    input   = torch.rand(1, 5)
    model   = Model()
    model.eval()

    file    = "asinh2.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 12)
    print("Finished normal onnx export")

if __name__ == "__main__":
    export_norm_onnx()

    # 自定义完onnx以后必须要进行一下验证
    validate_onnx()

与上面例子不同的是,这个注册方式跟底层文件的写法是一样的(文件在虚拟环境中的 torch/onnx/symbolic_opset*.py )

通过torch._internal 中的 registration 来注册这个算子,让这个算子可以与底层C++实现的 aten::asinh 绑定

_onnx_symbolic = functools.partial(registration.onnx_symbolic, opset=9)
@_onnx_symbolic('aten::asinh')
def asinh_symbolic(g, input, *, out=None):
    return g.op("Asinh", input)

案例二:

对于下面这个案例,我们想导出这个算子,这个算子在pytorch 中是存在的,并且可以运行 ,但是直接导出会报错

import torch
import torch.nn as nn
import torchvision
import torch.onnx

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 18, 3)
        self.conv2 = torchvision.ops.DeformConv2d(3, 3, 3)
    
    def forward(self, x):
        x = self.conv2(x, self.conv1(x))
        return x

def infer():
    input = torch.rand(1, 3, 5, 5)
    
    model = Model()
    x = model(input)
    print("input is: ", input.data)
    print("result is: ", x.data)

def export_norm_onnx():
    input   = torch.rand(1, 3, 5, 5)
    model   = Model()
    model.eval()

    file    = "../models/sample-deformable-conv.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 12)
    print("Finished normal onnx export")

if __name__ == "__main__":
    infer()
    export_norm_onnx()

运行报错如下:torchvision.ops.DeformConv2d 这个算子无法被识别
在这里插入图片描述

所以我们需要注册一下,写一个注册函数:

@parse_args("v", "v", "v", "v", "v", "i", "i", "i", "i", "i","i", "i", "i", "none")
def dcn_symbolic(
        g,
        input,
        weight,
        offset,
        mask,
        bias,
        stride_h, stride_w,
        pad_h, pad_w,
        dil_h, dil_w,
        n_weight_grps,
        n_offset_grps,
        use_mask):
    return g.op("custom::deform_conv2d", input, offset)

register_custom_op_symbolic("torchvision::deform_conv2d", dcn_symbolic, 12)

注意:

  • 这里需要把args的各个参数的类型都指定
  • 注册的时候指定 "torchvision::deform_conv2d"dcn_symbolic 绑定
  • dcn_symbolic 的每个参数要跟 "torchvision::deform_conv2d" 一一对应

2.4、直接修改 onnx,创建 plugin

直接手动创建一个 onnx (这是一个思路,会在后续博客进行总结记录)

参考链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/426715.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

deep learning with pytorch(一)

1.create a basic nerual network model with pytorch 数据集 Iris UCI Machine Learning Repository fully connected 目标:创建从输入层的代码开始,向前移动到隐藏层,最后到输出层 # %% import torch import torch.nn as nn import torch.nn.funct…

Angular基础---HelloWorld---Day1

文章目录 1. 创建Angular 项目2.对Angular架构的最基本了解3.创建并引用新的组件(component)4.对Angular架构新的认识(多组件)5.组件中业务逻辑文件的编辑(ts文件)6.标签中属性的绑定(1) ID的绑定(2) class…

AI大模型与小模型之间的“脱胎”与“反哺”(第二篇)

此图片来源于网络 21. **跨模态学习(Cross-Modal Learning)**: 如果各个行业AI小模型涉及多种数据类型或模态,可以通过跨模态学习技术让大模型理解并整合这些不同模态之间的关联,从而提升对多行业复杂问题的理解和解…

【Redis 主从复制】

文章目录 1 :peach:环境配置:peach:1.1 :apple:三种配置方式:apple:1.2 :apple:验证:apple:1.3 :apple:断开复制和切主:apple:1.4 :apple:安全性:apple:1.5 :apple:只读:apple:1.6 :apple:传输延迟:apple: 2 :peach:拓扑结构:peach:2.1 :apple:⼀主⼀从结构:apple:2.2 :apple:⼀…

【FPGA/IC】CRC电路的Verilog实现

前言 在通信过程中由于存在各种各样的干扰因素,可能会导致发送的信息与接收的信息不一致,比如发送数据为 1010_1010,传输过程中由于某些干扰,导致接收方接收的数据却成了0110_1010。为了保证数据传输的正确性,工程师们…

30天JS挑战(第十五天)------本地存储菜谱

第十五天挑战(本地存储菜谱) 地址:https://javascript30.com/ 所有内容均上传至gitee,答案不唯一,仅代表本人思路 中文详解:https://github.com/soyaine/JavaScript30 该详解是Soyaine及其团队整理编撰的,是对源代…

【数据结构】B树

1 B树介绍 B树(英语:B-tree),是一种在计算机科学自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的…

CAS外部云迁移vmware虚拟机兼容性问题处理

CAS外部云迁移vmware虚拟机兼容性问题处理 1、迁移过程中报错实图 2、问题原因 打开虚拟机存储的位置,发现文件夹下存在ctk.vmdk的文件 3、在vmware右键虚拟机编辑设置 注:虚拟机需要先关机 点击虚拟机选项——高级——编辑设置 将ctk.ENABLED改为…

第五套CCF信息学奥赛c++练习题 CSP-J认证初级组 中小学信奥赛入门组初赛考前模拟冲刺题(选择题)

第五套中小学信息学奥赛CSP-J考前冲刺题 1、不同类型的存储器组成了多层次结构的存储器体系,按存取速度从快到慢排列的是 A、快存/辅存/主存 B、外存/主存/辅存 C、快存/主存/辅存 D、主存/辅存/外存 答案:C 考点分析:主要考查计算机相关知识&…

在ubuntu上安装hadoop完分布式

准备工作 Xshell安装包 Xftp7安装包 虚拟机安装包 Ubuntu镜像源文件 Hadoop包 Java包 一、安装虚拟机 创建ubuntu系统 完成之后会弹出一个新的窗口 跑完之后会重启一下 按住首先用ctrlaltf3进入命令界面,输入root,密码登录管理员账号 按Esc 然后输入 …

蓝牙BLE 5.0、5.1、5.2和5.3区别

随着科技的不断发展,蓝牙技术也在不断进步,其中蓝牙BLE(Bluetooth Low Energy)是目前应用广泛的一种蓝牙技术,而BLE 5.0、5.1、5.2和5.3则是其不断升级的版本。本文将对这四个版本的区别进行详细的比较。 一、BLE 5.0…

为啥要用C艹不用C?

在很多时候,有人会有这样的疑问 ——为什么要用C?C相对于C优势是什么? 最近两年一直在做Linux应用,能明显的感受到C带来到帮助以及快感 之前,我在文章里面提到环形队列 C语言,环形队列 环形队列到底是怎么回…

FPGA高端项目:FPGA基于GS2971的SDI视频接收+纯verilog图像缩放+多路视频拼接,提供8套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收HLS动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案的SDI接收GTX…

【代码】Android|获取压力传感器、屏幕压感数据(大气压、原生和Processing)

首先需要分清自己需要的是大气压还是触摸压力,如果是大气压那么就是TYPE_PRESSURE,可以参考https://source.android.google.cn/docs/core/interaction/sensors/sensor-types?hlzh-cn。如果是触摸压力就是另一回事,我需要的是触摸压力。 不过…

【算法沉淀】刷题笔记:并查集 带权并查集+实战讲解

🎉🎉欢迎光临🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟特别推荐给大家我的最新专栏《数据结构与算法:初学者入门指南》📘&am…

Windows Server 各版本搭建文件服务器实现共享文件(03~19)

一、Windows Server 2003 打开服务器,点击左下角开始➡管理工具➡管理您的服务器➡添加或删除角色 点击下一步等待测试 勾选自定义配置,点击下一步 选择文件服务器,点击下一步 勾选设置默认磁盘空间,数据自己更改,最…

Onenote软件新建笔记本时报错:无法在以下位置新建笔记本

报错现象: 当在OneNote软件上,新建笔记本时: 然后,尝试重新登录微软账户,也不行,提示报错: 解决办法: 打开一个新的记事本,复制粘贴以下内容: C:\Users\Adm…

如何防御跨站请求伪造(CSRF)攻击?

CSRF 英文全称是 Cross-site request forgery,所以又称为“跨站请求伪造”,是指恶意诱导用户打开被精心构造的网站,在该网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 就是利用了用户的登录状态&#xff0c…

WordPress建站入门教程:如何在本地电脑搭建WordPress网站?

前面跟大家分享了『WordPress建站入门教程:如何安装本地WordPress网站运行环境?』,接下来boke112百科就继续跟大家分享本地电脑如何搭建WordPress网站。 小皮面板(phpstudy)的“软件管理 – 网站程序”虽然可以一键部…

excel统计分析——拉丁方设计

参考资料:生物统计学 拉丁方设计也是随机区组设计,是对随机区组设计的一种改进。它在行的方向和列的方向都可以看成区组,因此能实现双向误差的控制。在一般的试验设计中,拉丁方常被看作双区组设计,用于提高发现处理效应…
最新文章