ONNX的使用:从转化到推理(通用)

前言

       模型部署的过程中,不同的硬件可能支持不同的模型框架,通过onnx可以实现模型在多种框架之间进行转换,简化从研究到产品化的过程。本文以pytorch模型为例介绍训练模型转换为.onnx格式的实现过程,以及对转换后的onnx模型进行加载和使用onnxruntime进行模型推理。

(一)什么是onnx

ONNX(Open Neural Network Exchange)是一个开放的生态系统,它允许人工智能开发者在不同的框架和工具之间轻松地移动模型。这意味着,一个用PyTorch或TensorFlow训练的模型可以被转换成ONNX模型,然后在不同的框架中部署和运行,如TensorRT或TVM。ONNX充当了一个中间层,使得模型转换和部署变得更加灵活和方便。

ONNX模型的优势

  • 框架无关性:ONNX提供了一种在不同深度学习框架之间迁移模型的标准方式。

  • 支持多种工具和库:许多工具和库都支持ONNX,这使得模型的部署和优化变得更加容易。

  • 可视化和调试:工具如Netron可以用来可视化ONNX模型,这有助于理解模型结构和调试。

(二)Pytorch分类模型转onnx

实验环境:Pytorch2.0 + Ubuntu20.04

2.1 Pytorch模型之保存和加载

2.1.1 保存和加载整个模型

# Save:
torch.save(model_object, 'best.pt')
# Load:
model = torch.load('best.pt')
model.eval()

2.1.2 仅保存和加载模型参数(推荐使用)

# Save:
torch.save(model.state_dict(), 'best.pth')
# Load:
model = Model(*args, **kwargs)
model.load_state_dict(torch.load('best.pth'))
model.eval()

注意

        1)必须调用model.eval(),以便在运行推断之前将dropout和batch规范化层设置为评估模式。如果不这样做,将会产生不一致的推断结果。
        2)在保存用于推理或恢复训练的通用检查点时,必须保存模型的state_dict。

2.2 Pytorch分类模型转onnx

我的模型是调用resnet50训练的20分类模型,训练过程调用gpu,转换过程如下:

2.2.1 如果保存的是整个模型

import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
model = torch.load("best.pt") # pytorch模型加载
model.eval()  # set the model to inference mode

batch_size = 1  # 批处理大小
input_shape = (3, 224, 224)   # 输入数据,改成自己的输入shape
 
x = torch.randn(batch_size, *input_shape)   # 生成张量
x = x.to(device)
export_onnx_file = "best.onnx"  # 目标ONNX文件名
torch.onnx.export(model,
                    x,
                    export_onnx_file,
                    opset_version=10,
                    do_constant_folding=True, # 是否执行常量折叠优化
                    input_names=["input"],    # 输入名
                    output_names=["output"],  # 输出名
                    dynamic_axes={            # variable Length axes,specifies which dimensions of the input/output tensor are dynamic
                        input_names: {0: 'batch_size', 2 : 'in_width', 3: 'int_height'},
                        output_names: {0: 'batch_size', 2: 'out_width', 3:'out_height'}})

2.2.2 如果保存的是模型参数

import torch
import torchvision.models as models
 
model_dict = torch.load("best.pth") # pytorch模型加载
 
model = models.resnet50()
model.fc = torch.nn.Linear(2048, 20)
model.load_state_dict(model_dict) 
model.eval()  # set the model to inference mode
 
batch_size = 1  # 批处理大小
input_shape = (3, 224, 224)   # 输入数据,改成自己的输入shape
 
x = torch.randn(batch_size, *input_shape) # 生成张量
x.to(device)
export_onnx_file = "best.onnx"   # 目标ONNX文件名
torch.onnx.export(model,                      # model
                  x,                          # model input
                  export_onnx_file,           # where to save the model
                  opset_version=10,           # the ONNX version to export the model to
                  do_constant_folding=True,   # whether to execute constant folding for optimization
                  input_names=["input"],      # the model's input names
                  output_names=["output"],    # the model's output names
                  dynamic_axes={              # variable Length axes,specifies which dimensions of the input/output tensor are dynamic
                        input_names: {0: 'batch_size', 2 : 'in_width', 3: 'int_height'},
                        output_names: {0: 'batch_size', 2: 'out_width', 3:'out_height'}})) 
                                    

 参数说明:

必选参数:

        模型

        模型输入

        导出的 onnx 文件名

可选参数:

        export_params:模型中是否存储模型权重。一般中间表示包含两大类信息:模型结构和模型权重,这两类信息可以在同一个文件里存储,也可以分文件存储。ONNX 用同一个文件表示记录模型的结构和权重。
部署时一般都默认这个参数为 True。如果 onnx 文件是用来在不同框架间传递模型(比如 PyTorch 到 Tensorflow)而不是用于部署,则可以令这个参数为 False。

        opset_version:转换时参考哪个 ONNX 算子集版本,默认为 9。

        do_constant_folding:是否执行常量折叠优化。

        input_names, output_names:设置输入和输出张量的名称。如果不设置的话,会自动分配一些简单的名字(如数字)。ONNX 模型的每个输入和输出张量都有一个名字。很多推理引擎在运行 ONNX 文件时,都需要以“名称-张量值”的数据对来输入数据,并根据输出张量的名称来获取输出数据。在进行跟张量有关的设置(比如添加动态维度)时,也需要知道张量的名字。
在实际的部署流水线中,我们都需要设置输入和输出张量的名称,并保证 ONNX 和推理引擎中使用同一套名称。
        dynamic_axes:导出动态维度模型时需要,指定输入输出张量的哪些维度是动态的。用法:"输入输出名:[支持动态的纬度]",如“支持动态的纬度设置为[0, 2, 3]”则表示第0纬,第2纬,第3维支持动态输入输出。

        verbose=False:是否输出详细的导出信息。

        example_outputs:用于确定导出ONNX模型输出形状的样本输出。

        keep_initializers_as_inputs:是否将模型的初始化器作为输入输出。如果为True,模型初始化器将被作为输入的一部分导出。

2.3 check和验证onnx模型

安装onnx和onnxruntime,在命令行运行:

pip install onnx
pip install onnxruntime

2.3.1 check模型:

使用 onnx.checker.check_model() 验证模型的结构并确认模型具有有效的架构。

# check model
onnx_model = onnx.load(export_onnx_file)
check = onnx.checker.check_model(onnx_model)
print('check: ', check)

通过检查模型的版本,图的结构以及节点及其输入和输出来验证 ONNX 图的有效性。如果有效,则输出为None。

2.3.2 验证模型是否匹配:

验证 ONNX 运行时和 PyTorch 正在为网络计算相同的值。

# compare ONNX Runtime and PyTorch results
out = model(im)
preds = decode(out)
        
resnet_session = onnxruntime.InferenceSession(onnx_path)
inputs = {resnet_session.get_inputs()[0].name: to_numpy(im)}
outs = resnet_session.run(None, inputs)[0]

print('weights predicts: ', out.detach().cpu().numpy())
print('onnx prediction: ', outs)
print('\nCorrect!' if (outs.argmax(axis=2)[0] == np.array(preds)).all() else "Error!")

(三)onnx模型可视化

if args.vis:
    import netron

    modelData = export_onnx_file
    netron.start(modelData)

onnx结构可视化结果:

(四)模型转换&测试

【代码仅供参考】

import os
...


# 数据预处理
trans = T.Compose([
    T.Resize((224,224)),
    T.ToTensor(),
    T.Normalize(mean=[0.4850, 0.4560, 0.4060], std=[0.2290, 0.2240, 0.2250])
])

def to_numpy(tensor):
    return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy() 

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

# 输入数据准备
im = 'data/test/car.jpg'
im = Image.open(im).convert('RGB')
im = trans(im).unsqueeze(0).to(device)

weight_path = 'outputs/resnet50/driver_attr.pth'
onnx_path = 'onnx/resnet50/driver_attr.onnx'

# 模型和权重加载
model = build_model('resnet50', bn_wd=True, export=True)
model = load(model, weight_path)
model.eval()
model.to(device)


out = model(im)
preds = decode(out)
  
# 加载onnx      
resnet_session = onnxruntime.InferenceSession(onnx_path)
inputs = {resnet_session.get_inputs()[0].name: to_numpy(im)}
# 推理
outs = resnet_session.run(None, inputs)[0]

print('weights predicts: ', out.detach().cpu().numpy())
print('onnx prediction: ', outs)
print('\nCorrect!' if (outs.argmax(axis=2)[0] == np.array(preds)).all() else "Error!")

预测结果:

pth在转onnx后有微小的差别,不影响输出结果。

由于yolov5的模型和整个项目相互关联,所以转onnx无法用常规方法,只能用内部的转onnx方法,参考YOLOV5模型转ONNX及测试https://blog.csdn.net/weich_hou/article/details/145328016?sharetype=blogdetail&sharerId=145328016&sharerefer=PC&sharesource=weich_hou&spm=1011.2480.3001.8118

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值