pytorch模型(.pt)转onnx模型(.onnx)的方法详解(1)

1. pytorch模型转换到onnx模型

2.运行onnx模型

3.比对onnx模型和pytorch模型的输出结果

 我这里重点是第一点和第二点,第三部分  比较容易

首先你要安装 依赖库:onnx 和 onnxruntime,

pip install onnx
pip install onnxruntime 进行安装

也可以使用清华源镜像文件安装  速度会快些。

开始:

1. pytorch模型转换到onnx模型

pytorch 转 onnx 仅仅需要一个函数 torch.onnx.export 

torch.onnx.export(model, args, path, export_params, verbose, input_names, output_names, do_constant_folding, dynamic_axes, opset_version)

参数说明:

  • model——需要导出的pytorch模型
  • args——模型的输入参数,满足输入层的shape正确即可。
  • path——输出的onnx模型的位置。例如‘yolov5.onnx’。
  • export_params——输出模型是否可训练。default=True,表示导出trained model,否则untrained。
  • verbose——是否打印模型转换信息。default=False。
  • input_names——输入节点名称。default=None。
  • output_names——输出节点名称。default=None。
  • do_constant_folding——是否使用常量折叠,默认即可。default=True。
  • dynamic_axes——模型的输入输出有时是可变的,如Rnn,或者输出图像的batch可变,可通过该参数设置。如输入层的shape为(b,3,h,w),batch,height,width是可变的,但是chancel是固定三通道。
    格式如下 :
    1)仅list(int) dynamic_axes={‘input’:[0,2,3],‘output’:[0,1]}
    2)仅dict<int, string> dynamic_axes={‘input’:{0:‘batch’,2:‘height’,3:‘width’},‘output’:{0:‘batch’,1:‘c’}}
    3)mixed dynamic_axes={‘input’:{0:‘batch’,2:‘height’,3:‘width’},‘output’:[0,1]}
  • opset_version——opset的版本,低版本不支持upsample等操作。

转化代码:参考1:

import torch
import torch.nn
import onnx
 
model = torch.load('best.pt')
model.eval()
 
input_names = ['input']
output_names = ['output']
 
x = torch.randn(1,3,32,32,requires_grad=True)
 
torch.onnx.export(model, x, 'best.onnx', input_names=input_names, output_names=output_names, verbose='True')

 参考2:PlainC3AENetCBAM 是网络模型,如果你没有自己的网络模型,可能成功不了

import io
import torch
import torch.onnx
from models.C3AEModel import PlainC3AENetCBAM
 
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
 
def test():
  model = PlainC3AENetCBAM()
  
  pthfile = r'/home/joy/Projects/models/emotion/PlainC3AENet.pth'
  loaded_model = torch.load(pthfile, map_location='cpu')
  # try:
  #   loaded_model.eval()
  # except AttributeError as error:
  #   print(error)
 
  model.load_state_dict(loaded_model['state_dict'])
  # model = model.to(device)
 
  #data type nchw
  dummy_input1 = torch.randn(1, 3, 64, 64)
  # dummy_input2 = torch.randn(1, 3, 64, 64)
  # dummy_input3 = torch.randn(1, 3, 64, 64)
  input_names = [ "actual_input_1"]
  output_names = [ "output1" ]
  # torch.onnx.export(model, (dummy_input1, dummy_input2, dummy_input3), "C3AE.onnx", verbose=True, input_names=input_names, output_names=output_names)
  torch.onnx.export(model, dummy_input1, "C3AE_emotion.onnx", verbose=True, input_names=input_names, output_names=output_names)
 
if __name__ == "__main__":
 test()

直接将PlainC3AENetCBAM替换成需要转换的模型,然后修改pthfile,输入和onnx模型名字然后执行即可。

注意:上面代码中注释的dummy_input2,dummy_input3,torch.onnx.export对应的是多个输入的例子。

在转换过程中遇到的问题汇总

RuntimeError: Failed to export an ONNX attribute, since it's not constant, please try to make things (e.g., kernel size) static if possible

在转换过程中遇到RuntimeError: Failed to export an ONNX attribute, since it's not constant, please try to make things (e.g., kernel size) static if possible的错误。

我成功的案例,我直接把我训练的网络贴上,成功转换,没有from **   import 模型名词这么委婉,合法,我的比较粗暴

import torch
import torch.nn
import onnx
from torchvision import transforms
import torch.nn as nn
from torch.nn import Sequential

# 添加模型

# 设置数据转换方式
preprocess_transform = transforms.Compose([
    transforms.ToTensor(),  # 把数据转换为张量(Tensor)
    transforms.Normalize(  # 标准化,即使数据服从期望值为 0,标准差为 1 的正态分布
        mean=[0.5, ],  # 期望
        std=[0.5, ]  # 标准差
    )
])

class CNN(nn.Module):  # 从父类 nn.Module 继承
    def __init__(self):  # 相当于 C++ 的构造函数
        # super() 函数是用于调用父类(超类)的一个方法,是用来解决多重继承问题的
        super(CNN, self).__init__()

        # 第一层卷积层。Sequential(意为序列) 括号内表示要进行的操作
        self.conv1 = Sequential(
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )

        # 第二卷积层
        self.conv2 = Sequential(
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )

        # 全连接层(Dense,密集连接层)
        self.dense = Sequential(
            nn.Linear(7 * 7 * 128, 1024),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            nn.Linear(1024, 10)
        )

    def forward(self, x):  # 正向传播
        x1 = self.conv1(x)
        x2 = self.conv2(x1)
        x = x2.view(-1, 7 * 7 * 128)
        x = self.dense(x)
        return x

# 训练
# 训练和参数优化

# 定义求导函数
def get_Variable(x):
    x = torch.autograd.Variable(x)  # Pytorch 的自动求导
    # 判断是否有可用的 GPU
    return x.cuda() if torch.cuda.is_available() else x


# 判断是否GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# device1 = torch.device('cpu')
# 定义网络
model = CNN()




loaded_model = torch.load('save_model/model.pth', map_location='cuda:0')
model.load_state_dict(loaded_model)
model.eval()

input_names = ['input']
output_names = ['output']

# x = torch.randn(1,3,32,32,requires_grad=True)
x = torch.randn(1, 1, 28, 28, requires_grad=True)  # 这个要与你的训练模型网络输入一致。我的是黑白图像

torch.onnx.export(model, x, 'save_model/model.onnx', input_names=input_names, output_names=output_names, verbose='True')

前提是你要准备好*.pth模型保持文件

输出结果:

graph(%input : Float(1, 1, 28, 28, strides=[784, 784, 28, 1], requires_grad=1, device=cpu),
      %dense.0.weight : Float(1024, 6272, strides=[6272, 1], requires_grad=1, device=cpu),
      %dense.0.bias : Float(1024, strides=[1], requires_grad=1, device=cpu),
      %dense.3.weight : Float(10, 1024, strides=[1024, 1], requires_grad=1, device=cpu),
      %dense.3.bias : Float(10, strides=[1], requires_grad=1, device=cpu),
      %33 : Float(64, 1, 3, 3, strides=[9, 9, 3, 1], requires_grad=0, device=cpu),
      %34 : Float(64, strides=[1], requires_grad=0, device=cpu),
      %36 : Float(128, 64, 3, 3, strides=[576, 9, 3, 1], requires_grad=0, device=cpu),
      %37 : Float(128, strides=[1], requires_grad=0, device=cpu)):
  %input.4 : Float(1, 64, 28, 28, strides=[50176, 784, 28, 1], requires_grad=1, device=cpu) = onnx::Conv[dilations=[1, 1], group=1, kernel_shape=[3, 3], pads=[1, 1, 1, 1], strides=[1, 1]](%input, %33, %34) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\modules\conv.py:443:0
  %21 : Float(1, 64, 28, 28, strides=[50176, 784, 28, 1], requires_grad=1, device=cpu) = onnx::Relu(%input.4) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\functional.py:1442:0
  %input.8 : Float(1, 64, 14, 14, strides=[12544, 196, 14, 1], requires_grad=1, device=cpu) = onnx::MaxPool[kernel_shape=[2, 2], pads=[0, 0, 0, 0], strides=[2, 2]](%21) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\functional.py:797:0
  %input.16 : Float(1, 128, 14, 14, strides=[25088, 196, 14, 1], requires_grad=1, device=cpu) = onnx::Conv[dilations=[1, 1], group=1, kernel_shape=[3, 3], pads=[1, 1, 1, 1], strides=[1, 1]](%input.8, %36, %37) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\modules\conv.py:443:0
  %25 : Float(1, 128, 14, 14, strides=[25088, 196, 14, 1], requires_grad=1, device=cpu) = onnx::Relu(%input.16) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\functional.py:1442:0
  %26 : Float(1, 128, 7, 7, strides=[6272, 49, 7, 1], requires_grad=1, device=cpu) = onnx::MaxPool[kernel_shape=[2, 2], pads=[0, 0, 0, 0], strides=[2, 2]](%25) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\functional.py:797:0
  %27 : Long(2, strides=[1], device=cpu) = onnx::Constant[value=   -1  6272 [ CPULongType{2} ]]() # E:/paddle_project/Pytorch_Imag_Classify/zifu_fenlei/CNN/pt模型转onnx模型.py:51:0
  %28 : Float(1, 6272, strides=[6272, 1], requires_grad=1, device=cpu) = onnx::Reshape(%26, %27) # E:/paddle_project/Pytorch_Imag_Classify/zifu_fenlei/CNN/pt模型转onnx模型.py:51:0
  %input.20 : Float(1, 1024, strides=[1024, 1], requires_grad=1, device=cpu) = onnx::Gemm[alpha=1., beta=1., transB=1](%28, %dense.0.weight, %dense.0.bias) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\modules\linear.py:103:0
  %input.24 : Float(1, 1024, strides=[1024, 1], requires_grad=1, device=cpu) = onnx::Relu(%input.20) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\functional.py:1442:0
  %output : Float(1, 10, strides=[10, 1], requires_grad=1, device=cpu) = onnx::Gemm[alpha=1., beta=1., transB=1](%input.24, %dense.3.weight, %dense.3.bias) # D:\ProgramData\Anaconda3\envs\openmmlab\lib\site-packages\torch\nn\modules\linear.py:103:0
  return (%output)

输出结果的device  是CPU,模型加载的时候是GPU。这就是转换的意义吧

2.运行onnx模型

import onnx
import onnxruntime as ort
 
model = onnx.load('best.onnx')
onnx.checker.check_model(model)
 
session = ort.InferenceSession('best.onnx')
x=np.random.randn(1,3,32,32).astype(np.float32)  # 注意输入type一定要np.float32!!!!!
# x= torch.randn(batch_size,chancel,h,w)
 
 
outputs = session.run(None,input = { 'input' : x })

参考:

Pytorch模型转onnx模型实例_python_脚本之家 (jb51.net)

pytorch模型转onnx模型的方法详解_python_脚本之家 (jb51.net)

<think>好的,用户的问题是YOLOv5分割模型PT换为ONNX时出现精度丢失,需要解决方案。首先,我得回忆一下模型换过程中常见的精度问题来源。可能的原因包括导出时的操作支持差异、数据类型不匹配、输入尺寸设置错误、后处理步骤的不同,以及ONNX运行时与PyTorch的计算差异。 首先,用户可能没有正确设置输入输出节点的数据类型。比如,PyTorch默认使用FP32,而ONNX有时可能用FP16,导致精度下降。需要检查导出时的dtype参数,确保设置为FP32。 其次,在导出模型时,动态轴设置不正确可能导致尺寸问题。YOLOv5的分割模型可能有多个输出,包括检测框和掩膜,需要确认所有输出的动态轴是否正确配置,避免截断或尺寸错误。 另外,后处理步骤如果在模型中没有正确导出,也会导致精度差异。比如,在PyTorch模型中,后处理可能包含一些自定义操作,这些操作可能在换为ONNX时不被支持,或者需要手动注册自定义符号。用户可能需要简化后处理,或者确保这些操作在ONNX中有对应的实现。 还有,ONNX的版本和PyTorch的版本兼容性也是一个问题。需要确认使用的torch和torchvision版本与ONNX兼容,必要时升级或降级版本。此外,导出时启用opset_version为17或更高可能支持更多操作。 模型本身的验证步骤也很重要。在导出后,应该用相同的输入数据分别运行PTONNX模型,对比输出结果,找出差异较大的层,然后针对这些层进行调整。比如,某些激活函数或池化层可能在换时有不同的实现方式,需要检查是否有数值误差累积。 另外,用户可能使用了动态尺寸输入,但导出时没有正确设置动态轴,导致ONNX模型处理不同尺寸时出现问题。需要确保在导出时正确指定动态维度,例如使用dynamic_axes参数。 还有可能是在导出过程中,某些操作被替换或优化,比如舍入操作或自定义层,这些需要显式地在导出时保持原样。例如,使用operator_export_type=torch.onnx.OperatorExportTypes.ONNX保持原始操作。 最后,用户可能需要参考官方文档或社区解决方案,比如YOLOv5的GitHub仓库中是否有相关issue,或者ONNX的常见问题解答,寻找是否有已知的解决方案或补丁。 总结下来,解决方案可能包括检查数据类型、正确设置动态轴、验证模型输出、处理自定义操作、调整导出参数、版本兼容性检查等步骤。需要逐步排查每个可能的原因,并逐一验证,确保换后的ONNX模型尽可能接近原始PT模型的精度。</think>### YOLOv5分割模型PTONNX精度丢失解决方案 #### 1. 确保正确的导出参数设置 使用YOLOv5官方推荐的导出命令并添加精度保护参数: ```bash python export.py --weights yolov5s-seg.pt --include onnx --opset 17 --dynamic --simplify --half False ``` - `--opset 17`:使用更高版本的算子集支持复杂操作[^1] - `--half False`:强制保持FP32精度计算 - `--dynamic`:保留动态维度特性 - `--simplify`:启用模型简化优化 #### 2. 验证输入/输出对齐 在换后执行数值验证: ```python import torch import onnxruntime as ort # 生成测试数据 dummy_input = torch.randn(1, 3, 640, 640) # PyTorch推理 pt_output = model(dummy_input) # ONNX推理 ort_session = ort.InferenceSession("model.onnx") onnx_output = ort_session.run(None, {'images': dummy_input.numpy()}) # 对比输出差异 print(f"最大绝对误差:{np.max(np.abs(pt_output[0].detach().numpy() - onnx_output[0]))}") ``` #### 3. 处理自定义层问题 对于YOLOv5分割模型特有的`Detect`和`Segment`层: ```python # 在export.py中添加自定义符号注册 torch.onnx.register_custom_op_symbolic('torchvision::nms', lambda g, boxes, scores, iou_threshold: g.op("NonMaxSuppression", boxes, scores, iou_threshold), 11) ``` #### 4. 动态轴配置优化 在`export.py`中显式指定动态维度: ```python dynamic_axes = { 'images': {0: 'batch', 2: 'height', 3: 'width'}, # 仅保持空间维度动态 'output0': {0: 'batch', 1: 'anchors'}, 'output1': {0: 'batch', 2: 'mask_height', 3: 'mask_width'} } torch.onnx.export( ... dynamic_axes=dynamic_axes, ... ) ``` #### 5. 后处理统一化 建议将NMS等后处理步骤包含在ONNX模型中: ```yaml # 修改models/yolo.py中的Detect类 class Detect(nn.Module): def forward(self, x): # 添加ONNX兼容的NMS实现 if torch.onnx.is_in_onnx_export(): return self.export_onnx(x) # 原始实现... ``` #### 6. 版本兼容性检查 推荐使用已验证的版本组合: ``` torch==1.12.1 torchvision==0.13.1 onnx==1.13.0 onnxruntime-gpu==1.14.0 ``` #### 验证流程图 $$ \begin{array}{ccc} \text{原始PT模型} & \rightarrow & \text{导出参数检查} \\ \downarrow & & \downarrow \\ \text{数值验证} & \leftarrow & \text{ONNX模型生成} \\ & \downarrow & \\ \text{误差分析} & \rightarrow & \text{精度修复} \\ \end{array} $$
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值