4.6.tensorRT基础(1)-实际模型上onnx文件的各种操作

news2024/11/24 6:31:19

目录

    • 前言
    • 1. onnx
      • 1.1 读取节点
      • 1.2 修改节点
      • 1.3 替换节点
      • 1.4 删除节点
      • 1.5 修改input和output
      • 1.6 预处理的接入
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 基础-实际模型上 onnx 文件的各种操作

课程大纲可看下面的思维导图

在这里插入图片描述

1. onnx

本节课程我们来学习 onnx 的补充,拿到一个实际的 onnx 后如何读取到你想要的东西

本次演示使用的是 yolov5s.onnx,这并不重要,可以任意拿一个 onnx,我们的目的是学习如何读取实际的 onnx

1.1 读取节点

我们先来获取第一个卷积 model.0.conv.weight 的权重信息

import onnx
import numpy as np

model = onnx.load("yolov5s.onnx")

for item in model.graph.initializer:
    if item.name == "model.0.conv.weight":
        print("shape: ", item.dims)

        weight = np.frombuffer(item.raw_data, dtype=np.float32).reshape(32, 3, 6, 6)
        print(weight)

运行效果如下:

在这里插入图片描述

图1-1 model.0.conv.weight

上节课有提到 conv 的权重存储在 initiallizer 中,因此我们可以遍历然后通过 name 进行筛选,获取我们想要得到的 model.0.conv.weight,通过 numpy 将 raw_data 数据转换成 numpy 格式的 float32 的数据,从获取的权重和 onnx 模型的权重对比可看出二者都一一对应,说明我们获取的信息没问题。

1.2 修改节点

我们再来看下 Add 节点中的 Constant 类型:

import onnx
import numpy as np

for item in model.graph.node:
    if item.op_type == "Constant":
        if '357' in item.output:
            t = item.attribute[0].t
            data = np.frombuffer(t.raw_data, np.float32).reshape(*t.dims)
            print(data.shape)
            print(data)

前面我们提到过对于 anchor grid 类的常量数据,通常会储存在 model.graph.node 中,并指定类型为 Constant,因此我们遍历了 node 节点,通过 name 来筛选,筛选之后打印的 item 数据量非常大,看不出我们想要的东西,我们可以通过调试进一步观察:

在这里插入图片描述

图1-2 constant_debug

可以看到 item 有一个 attribute 方法,它是一个数组,且长度为 1,我们通过访问 item.attribute 可以看到对应的 name、dims、raw_data 信息了,我们可以进一步调试:

在这里插入图片描述

图1-3 constant_debug1

可以看到最终我们想要获取的数据可以通过 item.attribute[0].t.raw_data 获得

在这里插入图片描述

图1-4 constant

可以发现我们最终的 shape 和数据信息都可以对应上,这是关于 constant 的读取

接下来我们尝试修改下 Constant 的值,修改的是 Slice 的 start:

import onnx
import numpy as np

model = onnx.load("yolov5s.onnx")

for item in model.graph.node:
    if item.op_type == "Constant":
        if '373' in item.output:
            t = item.attribute[0].t
            print(type(t))
            print(t)
            print(np.frombuffer(t.raw_data, dtype=np.int64))
            t.raw_data = np.array([666], dtype=np.int64).tobytes()

onnx.save(model, "new.onnx")

我们先获取了对应的 raw_data 数据,然后将其修改为我们想要的值,运行效果如下:

在这里插入图片描述

图1-5 修改constant

从图中可以看出修改后导出的 onnx 模型的 Constant 的值已经修改为了 666

Slice 中的 Constant 数据类型并不是 float32 而是 int64,我们打印其对应的 data_type 显示数字为 7,然后去 onnx-ml.proto 中可以找到 message TensorProto 可以查询数字 7 对应的数据类型是什么

在这里插入图片描述

图1-6 message TensorProto

1.3 替换节点

这次我们来个高级点的,替换 reshape 节点:

import onnx
import numpy as np
import onnx.helper as helper

model = onnx.load("yolov5s.onnx")

for item in model.graph.node:
    if item.name == "Reshape_242":
        # print(item)
        new_reshape = helper.make_node("Reshape", ["377", "378"], "379", "Reshape_666", myname="Zhou")
        item.CopyFrom(new_reshape)

onnx.save(model, "new.onnx")

导出的 onnx 前后对比图如下:

在这里插入图片描述

图1-7 替换reshape节点

可以看到修改后导出的 onnx 确实是按照我们想要的都修改了,同时还添加了新的属性。值得注意的是,我们在替换节点时不能直接 = 替换,如果想要替换节点,你需要调用 protobuf 提供的函数 CopyFrom

1.4 删除节点

我们接下来演示下如何删除某个节点,以 Transpose 节点为例:

import onnx
import numpy as np
import onnx.helper as helper

model = onnx.load("yolov5s.onnx")

find_node_with_input  = lambda name: [item for item in model.graph.node if name in item.input][0]
find_node_with_output = lambda name: [item for item in model.graph.node if name in item.output][0]

remove_nodes = []
for item in model.graph.node:
    if item.name == "Transpose_201":
        # 上一个节点的输出是当前节点的输入
        prev = find_node_with_output(item.input[0])

        # 下一个节点的输入是当前节点的输出
        next = find_node_with_input(item.output[0])
        next.input[0] = prev.output[0]
        remove_nodes.append(item)

for item in remove_nodes[::-1]:
    model.graph.node.remove(item)

onnx.save(model, "new.onnx")

在这里插入图片描述

图1-8 删除transpose节点

我们在删除了某个节点后,你需要将 input 和 output 对接起来,不能让它们直接断开。因此需要将删除节点的上个节点输出和删除节点的下个节点的输入拼接,然后再删除。删除的时候也要注意,最好不要边循环边删除,应该分为两部分来做这个事情

1.5 修改input和output

我们同样可以将输入和输出的动态 batch 修改为静态 batch,代码如下:

import onnx
import numpy as np
import onnx.helper as helper

model = onnx.load("yolov5s.onnx")

static_batch_size = 3
input = model.graph.input[0]
print(type(input))
new_input = helper.make_tensor_value_info("images", 1, [static_batch_size, 3, 640, 640])
print(new_input)
model.graph.input[0].CopyFrom(new_input)

new_output = helper.make_tensor_value_info("output", 1, [static_batch_size, 25200, 7])
model.graph.output[0].CopyFrom(new_output)

onnx.save(model, "new.onnx")

运行效果如下:

在这里插入图片描述

图1-9 input

我们可以打印 input 节点的信息方便后续我们修改为静态 batch,可以看到其 name 为 images,elem_type 为 1,代表 float32。下图对比了修改前后的 onnx,可以看到确实 input 和 output 发生了修改

在这里插入图片描述

图1-10 动态batch修改为静态batch

同样的,你想静态的修改为动态的,做法也是一样的,因此虽然你的 onnx 导出来是一个确定的东西,但是后期如果你想要修改它,随便改!!!

1.6 预处理的接入

我们同样可以将一些复杂的预处理塞到 onnx 中,其代码如下:

import onnx
import numpy as np
import onnx.helper as helper

model = onnx.load("yolov5s.onnx")

import torch

class Preprocess(torch.nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.mean = torch.rand(1, 1, 1, 3)
        self.std  = torch.rand(1, 1, 1, 3)

    def forward(self, x):
        # x = B x H x W x C  Uint8
        # y = B x C x H x W  Float32  减均值除标准差
        x = x.float()
        x = (x / 255.0 - self.mean) / self.std
        x = x.permute(0, 3, 1, 2)
        return x

pre = Preprocess()
torch.onnx.export(
    pre, (torch.zeros(1, 640, 640, 3, dtype=torch.uint8)), "preprocess.onnx"
)

pre_onnx = onnx.load("preprocess.onnx")

# 1. 先把 pre_onnx 的所有节点以及输入输出名称加上前缀,防止命名冲突
# 2. 把 yolov5s.image 为输入的节点,修改为 pre_onnx 的输出节点
# 3. 把 pre_onnx 的 node 全部放到 yolov5s 的 node 中
# 4. 把 pre_onnx 的输入作为 yolov5s 的 input名称

for n in pre_onnx.graph.node:
    n.name = f"pre_{n.name}"

    for i in range(len(n.input)):
        n.input[i] = f"pre_{n.input[i]}"
    
    for i in range(len(n.output)):
        n.output[i] = f"pre_{n.output[i]}"

for n in model.graph.node:
    if n.name == "Conv_0":
        n.input[0] = "pre_" + pre_onnx.graph.output[0].name

for n in pre_onnx.graph.node:
    model.graph.node.append(n)

input_name = "pre_" + pre_onnx.graph.input[0].name
model.graph.input[0].CopyFrom(pre_onnx.graph.input[0])
model.graph.input[0].name = input_name

onnx.save(model, "new.onnx")

首先,我们导出了一个预处理的 onnx,如下图所示:

在这里插入图片描述

图1-11 preprocess.onnx

然后我们需要考虑如何将两个 onnx 对接起来,主要步骤如下:

1、 先把 pre_onnx 的所有节点以及输入输出名称加上前缀,防止命名冲突
2、 把 yolov5s.image 为输入的节点,修改为 pre_onnx 的输出节点
3、 把 pre_onnx 的 node 全部放到 yolov5s 的 node 中
4、 把 pre_onnx 的输入作为 yolov5s 的 input名称

拼接后的 onnx 如下图所示:

在这里插入图片描述

图1-12 拼接onnx

当你的算子很难搞定时,可以考虑 pytorch 生成下来然后塞进去,这样灵活度就很高了。预处理可以接入,同样后处理也可以这样接入,所以你可以把 onnx 当成一个类似可编辑的记事本一样的东西就行,因此无论你遇到多复杂的 onnx,你都可以去编辑修改它,而不至于束手无策。

总结

本节课程我们以实际的 yolov5s.onnx 模型为例,学习了 onnx 文件的各种操作,包括基本的读取某个节点的信息,修改某个节点的值,高级一点的包括替换对应的节点,删除对应的节点,还有将动态 shape 修改为 静态 shape,最后我们还尝试在原有s onnx 的基础上接了一段预处理的代码。

通过本次课程的学习,我们需要知道对于 onnx 而言,我们是可以操作的,读取、修改、删除、增加等等,你把它当成一个可编辑的东西就行,后续无论是多么复杂的 onnx,你都应该要知道如何处理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在if/else中进行函数声明

console.log("第一次输出a: ", a) //输出 本地a if (true) {// 这里js隐式的把function a的定义放到这里来了,此刻这里有一个 块aa 1 // 将 块a的值 由函数修改为1console.log("第二次输出a: ",a) // 此时输出的是 块a的值function a() {} // …

【Kubernetes运维篇】RBAC之准入控制器详解

文章目录 一、ResourceQuota准入控制器1、ResourceQuota是什么?2、限制CPU、内存、Pod数量、Deployment数量3、限制存储空间大小 二、LimitRanger准入控制器1、LimitRanger是什么?2、LimitRanger限制案例 一、ResourceQuota准入控制器 中文官方参考文档…

前端Vue仿美团右侧侧边栏弹框筛选框popup alert

随着技术的发展,开发的复杂度也越来越高,传统开发方式将一个系统做成了整块应用,经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改,造成牵一发而动全身。 通过组件化开发,可以有效实现…

Linux dpkg和dpkg-deb常用参数使用说明

名词解释 “dpkg ”是“ Debian Packager ”的简写。为“Debian” 专门开发的套件管理系统,方便软件的安装、更新及移除。所有源自“Debian”的“Linux ”发行版都会使用 “dpkg”,例如 “ Ubuntu ”、“Knoppix ”等。 dpkg-deb和dpkg的区别 dpkg-de…

golang使用bcrypt包对密码进行加密

bcrypt bcrypt是一个由美国计算机科学家尼尔斯普罗沃斯(Niels Provos)以及大卫马齐耶(David Mazires)根据Blowfish加密算法所设计的密码散列函数,于1999年在USENIX中展示。实现中bcrypt会使用一个加盐的流程以防御彩虹…

深入理解Windows操作系统机制(四)

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。 我们每天都在用Windows操作系统,但是其实我们每天直接在打交道的并不是Windows操作系统的内核,而是Windows操作系统…

H3C-Cloud Lab实验-PPPoE实验

实验拓扑图: 实验需求: 1. 如图,私网内部配置为 192.168.1.0/24 网段,R2 上配置 Loopback0 口模拟互联网地址 2. 配置 R2 为 PPPoE Server,为 R1 提供 PPPoE 拨号服务,并为 R1 自动分配公网 IP 地址 3. …

【unity之IMGUI实践】抽象父类继承实现【三】

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

Bypass!Burp分块传输绕WAF插件

插件简介 本插件主要用于分块传输绕WAF,不了解分块传输绕WAF的可阅读作者这篇文章:【第8周】编写Burp分块传输插件绕WAF 。 关注【Hack分享吧】公众号,回复关键字【230605】获取下载链接 插件使用 延时分块传输

CentOS7.8离线安装Docker24.0.2,离线安装gcc与g++环境

背景 有时候运维时要求在内网环境下操作,即服务器无法连接互联网,那么就无法通过 yum 源在线安装。。这时,一般通过以下3种方式来安装需要的软件: 下载源码包编译安装;下载对应平台编译好的安装包,解压即…

了解JVM

PS:本文以下部分,默认都是使用HotSpot,也就是Oracle Java 默认的虚拟机为前提来进行介绍的。 1.JVM执行流程 程序在执行之前先要把Java代码转换成字节码(.class文件),JVM首先需要把字节码通过一定的方式类加载器(Clas…

python调用oepnai API

目录 apiAI官网介绍([Introduction](https://platform.openai.com/docs/api-reference/introduction))安装官方SDK认证(Authentication)申请API KEY请求组织(Requesting organization) 发送请求关于chat to…

CAJ文献如何转成PDF?分享两个免费的方法!

CAJ格式是中国知网(CNKI)常见的电子文献格式,但有时我们可能更倾向于将其转换为PDF格式以便于查阅、存储和共享。为了帮助大家完成这个任务,下面将分享两种免费的方法来将CAJ文献转换为PDF格式。无论您是研究学者、学生还是对特定…

学校招生报名小程序开发笔记(一)

背景 这是一个以报名为核心的职业学校招生小程序,目的是方便想要系统学习技能,入门某项技能或者领域的初高中毕业生,了解该学校的基本情况及各个专业,并提供报名路径,致力于技能型人才培养 功能规划 主要功能包括专…

使用 Pytest 运行 yaml 文件来驱动 Appium 自动化测试

目录 前言: 获取 yaml 文件 YamlTest 测试类 Appium 初始化 Pytest 测试类 自定义 runtest demo: 自定义错误输出 Yaml 使用方式规则 前言: 使用Pytest来运行yaml文件来驱动Appium自动化测试是一种方便且灵活的方法。通过将测试数据…

【异常解决】postman请求提示Full authentication is required to access this resource

Full authentication is required to access this resource解决办法 报错问题:在使用 postman 测试接口时,该接口需要在 Header 中传入 access_token,实际上也在请求的 Header 中添加上了 access_token 参数,但是服务端还是返回4…

【STM32零基础入门教程01】STM32入门基础知识

本篇内容为STM32零基础入门教程的第一篇,网上STM32的教程很多,有些初学者还是望而却步。其实STM32并不难,只是一个新的事物出现在我们面前一时间不适应,思来想去我打算写点东西一方面自己有点知识的积累,另一方面希望可…

Python教程(4)——Python开发工具PyCharm的下载与安装

PyCharm是一种专业的Python集成开发环境(IDE),由JetBrains公司开发和维护。它提供了丰富的功能和工具,帮助开发人员更高效地编写、调试和测试Python代码。如果是一些大型Python项目强烈推荐用这个来开发。今天我们来介绍一下PyCha…

【实战】 七、Hook,路由,与 URL 状态管理(中) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十二)

文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…

Docker基础(二)

1、Docker工作原理 Docker是一个Clinet-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器,是一个运行时环境,就是我们前面说的…