NCNN 源码学习【二】:模型加载

news2024/12/23 20:17:19

正文

这次先来看一段NCNN应用代码中,最先出现的部分,模型加载

ncnn::Net squeezenet;
squeezenet.load_param("squeezenet_v1.1.param");
squeezenet.load_model("squeezenet_v1.1.bin");

首先我们可以看到一个 ncnn的类Net,这个就是用来记录网络的,我们跳过这个,有四个东西是我们比较关心的:

方法:load_parm和load_model
文件:*.param 和 *.bin(可以用netron可视化)

一、Load_parm

​load_parm的具体代码在net.cpp的第92行处,考虑到该函数是加载param文件的,squeezenet_v1.1.param的内容,其中的各部分的含义我都按照源码的变量名指示了出来,看图:
在这里插入图片描述
在一个param文件里面,只有第一行与其他行的区别。

1. 第一行

  • 第一个数:表示了该param所表示的模型,共有多少layer,,layer的值是从第二行开始一直数下去的,所以如果增删了某几行,这里也要对应修改。
  • 第二个数:表示了该某些有多少个blob,可以理解成有多少个数据流节点。例如一个卷积就是一个输入blob一个输出blob,数据其实就是在blob之间流来流去。

2. 其他行:除了第一行,其他的都是layer行,上图用第三行举例,具体行内容依次为:

  • layer_type:该行layer对应的类型,如 Convolution、Pooling、ReLU 等。每种类型的层执行不同的操作,并具有不同的参数和配置。

  • layer_name:该行layer的名字,这是为网络中的每一层指定的唯一名称。它用于在网络的定义中引用特定的层,模型导出的时候导出工具自动生成的

  • bottom_count:这表示一个层的输入数量。在神经网络中,某些层可能从一个或多个其他层接收输入数据。bottom_count 指明了该层需要接收多少个输入。这层可以理解为我这个小弟上面有多少个大哥。

  • top_count:这表示一个层的输出数量。某些层可能向一个或多个其他层输出数据。top_count 指出该层产生多少个输出。该层参数可以理解为我这个大哥下面有多少个小弟。

  • bottom_name:这些是输入到该层的底部(或输入)blob(数据块)的名称。在 NCNN 中,blob 是指流经网络的数据。每个 blob 有一个唯一的名称,这样在定义网络时可以清楚地指出数据从哪里来,到哪里去。上面大哥叫什么名字

  • blob_name:这些是该层输出的顶部(或输出)blob的名称。这些名称用于在网络定义中将这些数据传递到其他层。我下面小弟叫什么名字。

  • 特有参数:这个是该layer特有的一些参数,不是同一参数,由各具体layer实例读。例如卷积有有kernel_size、stride_size、padding_size,ReLU又是无参的,softmax则需要一个指示维度的参数,具体参具体分析。

代码详细分析如下:

75 83
Input            data             0 1 data 3 227 227
Convolution      conv1            1 1 data conv1 64 3 1 2 0 1 1728
ReLU             relu_conv1       1 1 conv1 conv1_relu_conv1 0.000000
Pooling          pool1            1 1 conv1_relu_conv1 pool1 0 3 2 0 0

文件开头

  • 78: 表示网络中层的数量。NCNN 中的每个层代表一个操作,如卷积、池化、激活等。
  • 83: 表示网络中 blob 的总数。在 NCNN 中,blob 是指流经网络的数据,每个层的输入和输出都可以被视为一个或多个 blob。

各层定义

  1. Input Layer:

    • Input: 层的类型,这里表示它是一个输入层。
    • data: 层的名称,这里将该层命名为 data
    • 0: 输出数量,输入层不产生输出,所以是0。
    • 1: 输入数量,这是一个常规的输入层,因此只有一个输入(即图像本身)。
    • data: 输入blob的名称。
    • 3, 227, 227: 输入数据的维度。这里指的是输入图像有3个通道(彩色图像),宽和高分别为227像素。
  2. Convolution Layer:

    • Convolution: 层的类型,表示这是一个卷积层。
    • conv1: 层的名称。
    • 1: 输出数量,这层有一个输出。
    • 1: 输入数量,这层有一个输入。
    • data: 输入blob的名称,即接受 data 层的输出作为输入。
    • conv1: 输出blob的名称。
    • 64: 卷积核的数量。
    • 3: 卷积核的尺寸(宽和高),这里是3x3。
    • 1: 步长,表示卷积操作在宽和高方向上的移动步长。
    • 2: 填充,表示在输入数据的周围添加的零的层数。
    • 0: 群组数量,用于分组卷积。
    • 1: 卷积方式,1表示使用常规卷积。
    • 1728: 权重数据的数量。
  3. ReLU Layer:

    • ReLU: 层的类型,表示这是一个激活层,使用ReLU激活函数。
    • relu_conv1: 层的名称。
    • 1: 输出数量。
    • 1: 输入数量。
    • conv1: 输入blob的名称,接受 conv1 层的输出。
    • conv1_relu_conv1: 输出blob的名称。
    • 0.000000: ReLU激活函数的负斜率(Leaky ReLU的参数),这里为0表示标准ReLU。
  4. Pooling Layer:

    • Pooling: 层的类型,表示这是一个池化层。
    • pool1: 层的名称。
    • 1: 输出数量。
    • 1: 输入数量。
    • conv1_relu_conv1: 输入blob的名称,接受 relu_conv1 层的输出。
    • pool1: 输出blob的名称。
    • 0: 池化方式,0通常表示最大池化。
    • 3: 池化核的尺寸。
    • 2: 步长,表示池化操作的移动步长。
    • 0: 填充,表示在输入数据周围添加的零的层数。
    • 0: 全局池化标志,0表示不使用全局池化。

有了param文件的分析,我们结合源码对比写出load_param的伪代码了:

# layer列表,存下所有layer
# blobs列表,背后维护,为find_blob服务
layer_count, blob_count = read(param_file
for param_file is not EOF: # 循环读取每一行的layer数据
	layer_type, layername, bottom_count, top_count = read(param_file)	# 读取前四个固定参数
	layer = create_layer(layer_type)	# 根据layer类型创建一个layer
	for bottom_count:
		bottom_name = read(param_file)	# 读取每一个bottom_name
		blob = find_blob(bottom_name)	# 查找该blob,没有的话就要新建一个
		blob.consumers.append(layer)	# 当前层是这个blob的消费者,这里的blob是是大哥,当前层是小弟,没钱花找大哥要
		layer.bootoms.append(blob)	# 记住谁才是你的大哥
	for top_count:
		blob_name = read(param_file)	# 读取每一个blob_name
		blob = find_blob(bottom_name)	# 查找该blob,没有的话就要新建一个
		blob.producer = layer	# 当前层是这个blob的生产者,这里的blob是小弟,当前层是大哥
		layer.tops.append(blob)	# 花名册上把小弟名字写一些
	layer.param = read(param_file)
	layers.append(layer)

其实整个流程是很简单,对于某一层数据来说,首先要认清自己的身份,记住谁是你的大哥(bottom_name),记住谁是你的小弟(blob_name),认清自己几斤几两(特有参数)。

后面的load_model这里暂不分析,因为这里涉及到layer的特有参数,这个我们后面拉几个layer单独分析。

这里大哥小弟思想是受到一篇帖子的启发链接,感觉挺有意思的。

  • 方案一:每个人都发,大家都有钱,有些小弟还不需要钱,你也给他发了
    • 优点:每个人你都发了,缺钱也别来问,都给你了(直接完整推理,要什么数据取就行了)
    • 缺点:大哥都发出去了,累死累活的(全部计算量)
  • 方案二:来要钱才给他,有些不要钱的不给了
    • 优点:大哥省事,谁要给谁(节省计算量)
    • 缺点:每个小弟要钱都要往上打报告,大哥再给他们发(取不同节点数据中间需要再推理)

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

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

相关文章

【@Cacheable的使用,及设置过期时间 配置方式】

Cacheable的使用,及设置过期时间 配置方式 使用方式 使用方式 Cacheable(cacheNames “ssss#30” ,key “#aaa‘‘#beginTime’’#endTime”) cacheNames/value :用来指定缓存组件的名字key :缓存数据时使用的 key,可以用它来指…

【Oracle】backup备份时报错ORA-19809,ORA-9804

Oracle备份数据库时报错 ORA-19809: limit exceeded for recovery files ORA-19804: cannot reclaim 10305536 bytes disk space from 4385144832 limit 1.清理过时的备份: 使用RMAN删除不再需要的过时备份,以释放空间。执行以下命令: DEL…

模块一——双指针:11.盛最多水的容器

文章目录 题目解析算法原理解法一:暴力枚举(超时)解法二:双指针单调性 代码实现暴力枚举(超时)双指针单调性(时间复杂度为O(N),空间复杂度为O(1)) 题目解析 题目链接:11.盛最多水的容器 这道题…

uniapp实战 —— 轮播图【数字下标】(含组件封装,点击图片放大全屏预览)

组件封装 src\components\SUI_Swiper2.vue <script setup lang"ts"> import { ref } from vue const props defineProps({config: Object, })const activeIndex ref(0) const change: UniHelper.SwiperOnChange (e) > {activeIndex.value e.detail.cur…

C# Socket通信从入门到精通(14)——多个异步UDP客户端C#代码实现

前言: 在之前的文章C# Socket通信从入门到精通(13)——单个异步UDP客户端C#代码实现我介绍了单个异步Udp客户端的c#代码实现,但是有的时候,我们需要连接多个服务器,并且对于每个服务器,我们都有一些比如异步发送、异步接收的操作,那么这时候我们使用之前单个异步Udp客…

jmeter接口测试之登录测试

注册登录_登陆接口文档 1.登录 请求地址&#xff1a; POST xxxxxx/Home/Login 请求参数&#xff1a; args{LoginName:"mtest", // 登录名&#xff0c;可以为用户名或邮箱Password:"123456" // 密码" }响应数据&#xff1a; 成功 {"S…

浅谈linux缓冲区的认识!

今天来为大家分享一波关于缓冲区的知识&#xff01;那么既然我们要谈缓冲区&#xff0c;那么就得从是什么&#xff1f;为什么&#xff1f;有什么作用这几个方面来谈论一下缓冲区&#xff01;然后再通过一些代码来更加深刻的理解缓冲区的知识&#xff01; 引言&#xff1a; 是…

ServletJSP

Servlet 1.Servlet生命周期 2.HttpServletRequest与HttpServletResponse 2.1HttpServletRequest 获取请求参数 请求乱码问题&#xff1a; 请求转发 request作用域 2.2HttpServletResponse 输出 乱码 重定向 3.Cookie 4.Sessions 5.ServletContext 获取方式及常用方法&a…

selenium自动化(中)

显式等待与隐式等待 简介 在实际工作中等待机制可以保证代码的稳定性&#xff0c;保证代码不会受网速、电脑性能等条件的约束。 等待就是当运行代码时&#xff0c;如果页面的渲染速度跟不上代码的运行速度&#xff0c;就需要人为的去限制代码执行的速度。 在做 Web 自动化时…

经典策略筛选-20231212

策略1&#xff1a; 龙头战法只做最强&#xff1a;国企改革 ----四川金顶 1、十日交易内出现 涨停或 &#xff08;涨幅大于7个点且量比大于3&#xff09; 2、JDK MACD RSI OBV BBI LWR MTM 六指标共振 3、均线多头 4、 筹码峰 &#xff08;锁仓&#xff09; 5、现价>…

虹科分享 | CanEasy多场景应用,让汽车总线测试更简单

CanEasy是一个基于Windows的总线工具&#xff0c;用于分析和测试CAN、CAN FD和LIN以及汽车以太网系统。通过高度自动化和简单的配置模拟总线流量&#xff0c;CanEasy可用于分析真实网络、模拟虚拟系统&#xff0c;以及在整个开发过程中进行剩余总线模拟&#xff0c;实现从测试到…

FFmpeg-基础组件-AVFrame

本章主要介绍FFmpeg基础组件AVFrame. 文章目录 1.结构体成员2.成员函数AVFrame Host内存的获取 av_frame_get_bufferAVFrame device内存获取av_hwframe_get_buffer&#xff08;&#xff09; 1.结构体成员 我们把所有的代码先粘贴上来&#xff0c;在后边一个一个解释。 typede…

鸿蒙Stage模型开发—创建你的第一个ArkTS应用

Stage模型开发概述 基本概念 下图展示了Stage模型中的基本概念。 图1 Stage模型概念图 UIAbility组件和ExtensionAbility组件 Stage模型提供UIAbility和ExtensionAbility两种类型的组件&#xff0c;这两种组件都有具体的类承载&#xff0c;支持面向对象的开发方式。UIAbility…

ARM:作业3

按键中断代码编写 代码: key_it.h #ifndef __KEY_IT_H__ #define __KEY_IT_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gic.h"void key1_it_config(); voi…

冯诺依曼体系与操作系统的理解

目录 一.冯诺依曼体系结构 存储分级 为什么程序运行之前&#xff0c;必须加载到内存上&#xff1f; 二.操作系统 操作系统是什么&#xff1f; 为什么需要操作系统&#xff1f; 操作系统是如何管理软硬件资源&#xff1f; 一.冯诺依曼体系结构 我们常见的计算机&#xff…

Qt 容器QGroupBox带有标题的组框框架

控件简介 QGroupBox 小部件提供一个带有标题的组框框架。一般与一组或者是同类型的部件一起使用。教你会用,怎么用的强大就靠你了靓仔、靓妹。 用法示例 例 qgroupbox,组框示例(难度:简单),使用 3 个 QRadioButton 单选框按钮,与QVBoxLayout(垂直布局)来展示组框的…

BearPi Std 板从入门到放弃 - 先天神魂篇(2)(RT-Thread LED PWM驱动)

简介 基于 BearPi Std 板从入门到放弃 - 先天神魂篇&#xff08;1&#xff09;(RT-Thread 指令点亮LED) 创建的项目, 添加PWM驱动LED的方式实现呼吸灯功能, 电路板及相关使用到的配件说明 开发板 &#xff1a; Bearpi Std(小熊派标准板) 主芯片: STM32L431RCT6 E53_ST1扩展板/…

【acwing】92. 递归实现指数型枚举

穿越隧道 递归枚举、位运算 方法① 从1到n&#xff0c;顺序访问每位数&#xff0c;是否选择&#xff0c;每位数有两种状态&#xff0c;选1或不选0. AC代码如下&#xff1a; #include <iostream> using namespace std;const int N 100; // bool st[N]; int n;void dfs(in…

智能优化算法应用:基于指数分布算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于指数分布算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于指数分布算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.指数分布算法4.实验参数设定5.算法结果6.参考…

优雅玩转实验室服务器(一)登录服务器

这篇文章更加偏向于使用python程序进行研究的朋友们 原料 Windows主机实验室Linux服务器&#xff08;可以访问互联网&#xff09;一点点耐心 step.0 windows terminal is all you need 别跟我说什么putty&#xff0c;什么winscp&#xff0c;我就是单推Win11自带的软件——win…