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

正文

这次先来看一段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单独分析。

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

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

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

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

相关文章

【C语言】结构体实现位段

引言 对位段进行介绍,什么是位段,位段如何节省空间,位段的内存分布,位段存在的跨平台问题,及位段的应用。 ✨ 猪巴戒:个人主页✨ 所属专栏:《C语言进阶》 🎈跟着猪巴戒,…

鸿蒙系统扫盲(五):再谈鸿蒙开发用什么语言?

前段时间,发表了鸿蒙系统扫盲(三):鸿蒙开发用什么语言?这篇文章,收到一些网友的提问,一一解答了,还有网友对我进行了严厉的批评和尖锐的指责,说我有点颠倒是非&#xff0…

集成开发之如何用好明道云

内容来自演讲:张嵩 | 苏州睿能科技有限公司 | 公司负责人 摘要 这篇文章介绍了作者所在公司如何利用明道云进行集成开发,并分享了四个实际案例。在第一个数字化实验室项目中,该公司使用明道云取代现有的STARLIMS商业软件,并实现…

sleep和wait区别,并且查看线程运行状态

一、sleep和wait区别 区别一:语法使用不同 wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出 IllegalMonitorStateException 的异常 而 sleep 可以单独使用,无需配合 synchronized 一起使用。 区别二:所属类不同…

【Spring教程26】Spring框架实战:从零开始学习SpringMVC 之 bean加载控制

目录 1 问题分析2 思路分析3 环境准备4 设置bean加载控制5 知识点1:ComponentScan 欢迎大家回到《Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《如何在windows11下安装…

大数据技术6:大数据技术栈

前言:大数据相关的技术名词特别多,这些技术栈之间的关系是什么,对初学者来说很难找到抓手。我一开始从后端转大数据的时候有点懵逼,整体接触了一遍之后才把大数据技术栈给弄明白了。 一、大数据技术栈 做大数据开发,无…

YOLOv8改进 | 2023主干篇 | 利用RT-DETR特征提取网络PPHGNetV2改进YOLOv8(超级轻量化精度更高)

一、本文介绍 本文给大家带来利用RT-DETR模型主干HGNet去替换YOLOv8的主干,RT-DETR是今年由百度推出的第一款实时的ViT模型,其在实时检测的领域上号称是打败了YOLO系列,其利用两个主干一个是HGNet一个是ResNet,其中HGNet就是我们…

Logstash输入Kafka输出Es配置

Logstash介绍 Logstash是一个开源的数据收集引擎,具有实时管道功能。它可以从各种数据源中动态地统一和标准化数据,并将其发送到你选择的目的地。Logstash的早期目标主要是用于收集日志,但现在的功能已经远远超出这个范围。任何事件类型都可…

记录汇川:MODBUS-梯形图

H5U的MODBUS通信不需要编写程序,通过组态MODBUS通信配置表,实现数据通信。 相对自由口走报文的形式,这个更加的方便。配置结束,就可以监控数据或写入。

Linux - 进程间通信(中)- 管道的应用场景

前言 在上篇博客当中,对Linux 当中的进程通信,做了详细阐述,主要是针对父子进程的通信来阐述的同时,也进行了模拟实现。 对于管道也有了初步了解,但是这仅仅是 进程间通信的一部分,Linux 当中关于进程间通…

Unity光照模型实践

光照作为3D渲染中最重要的部分之一,如何去模拟真实环境的光照是重要的研究内容,但是现实环境光照过于复杂,有很多经典好用的光照模型去近似真实光照。 根据基础的Phong模型 最终某个点的结果为 环境光Ambient 漫反射光Diffuse 高光Specula…

安卓MediaRecorder(2)录制源码分析

文章目录 前言JAVA new MediaRecorder() 源码分析android_media_MediaRecorder.cpp native_init()MediaRecorder.java postEventFromNativeandroid_media_MediaRecorder.cpp native_setup() MediaRecorder 参数设置MediaRecorder.prepare 分析MediaRecorder.start 分析MediaRec…

Navicat 技术指引 | 适用于 GaussDB 分布式的服务器对象的创建/设计

Navicat Premium(16.3.3 Windows版或以上)正式支持 GaussDB 分布式数据库。GaussDB分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能,还提供强大的高阶功能(如模型、结构…

JavaSE知识点回顾,附学习思维导图

第一阶段 day01 java 发展,java 环境( path, java_home, class_path),java 原理, java 执行 , jvm , jre , jdk day02 变量 标识符命名规则 数据类型 数据类型的转换 运算符 day03 选择结构 if , switch day04 循环结构 for , whi…

java--Collection的遍历方式

1.迭代器概述 迭代器是用来遍历集合的专用方式(数组没有迭代器),在java中迭代器是Iterator。 2.Collection集合获取迭代器的方法 3.Iterator迭代器中的常用方法 4.增强for循环 ①增强for可以用来遍历集合或数组。 ②增强for遍历集合,本质就是迭代器遍…

005、Softmax损失

之——softmax与交叉熵 杂谈 我们常用到softmax函数与交叉熵的结合作为损失函数以监督学习,这里做一个小小的总结。 正文 1.softmax的基本改进 所谓softmax就是在对接全连接层输出时候把输出概率归一化,最基础的就是这样: 效果就是这样&…

(第65天)PDB 快照

介绍 PDB 快照是一个 PDB 指定时间点的副本。在创建快照时,源 PDB 可以是只读或者读写模式。 PDB 快照可以用于快速创建 PDB。 PDB 快照可以分为手动和自动两种创建方式(create pluggable database|alter pluggable database): 手动快照使用 SNAPSHOT 子句的方式来创建自动…

Jmeter beanshell编程实例

1、引言 BeanShell是一种小型的,免费的,可嵌入的符合Java语法规范的源代码解释器,具有对象脚本语言特性。 在Jmeter实践中,由于BeanShell组件较高的自由度,通常被用来处理较为复杂,其它组件难以处理的问题…

jmeter接口测试之登录测试

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

微表情检测(四)----SL-Swin

SL-Swin: A Transformer-Based Deep Learning Approach for Macro- and Micro-Expression Spotting on Small-Size Expression Datasets 在本文中,我们致力于解决从视频中检测面部宏观和微观表情的问题,并通过使用深度学习方法分析光流特征提出了引人注…