NCNN 模型推理详解及实战
一,依赖库知识速学
aarch64
aarch64
,也被称为 ARM64,是一种基于 ARMv8-A
架构的 64
位指令集体系结构。它是 ARM 体系结构的最新版本,旨在提供更好的性能和能效比。与先前的 32
位 ARM
架构相比,aarch64 具有更大的寻址空间、更多的寄存器和更好的浮点性能。
在 Linux 系统终端下输入以下命令,查看 cpu
架构。
uname -m # 我的英特尔服务器输出 x86_64,m1 pro 苹果电脑输出 arm64
OpenMP
OpenMP
(Open Multi-Processing)是一种基于共享内存的并行编程 API,用于编写多线程并行程序。使用 OpenMP
,程序员可以通过在程序中插入指令来指示程序中的并行性。这些指令是以 #pragma
开头的编译指示符,告诉编译器如何并行化代码。
#include <stdio.h>
#include <omp.h>
int main() {
int i;
#pragma omp parallel for
for(i = 0; i < 10; i++) {
printf("Thread %d executing iteration %d\n", omp_get_thread_num(), i);
}
return 0;
}
AVX512
AVX
全称是 Advanced Vector Extension,高级矢量扩展,用于处理 N
维数据的,例如 8
维及以下的 64
位双精度浮点矢量或 16
维及以下的单精度浮点矢量。
AVX512
是 SIMD
指令(单指令多数据),x86
架构上最早的 SIMD 指令是 128bit 的 SSE
,然后是 256bit 的 AVX/AVX2,最后是现在 512bit 的 AVX512。
submodule
github submodule(子模块)允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
apt upgrade
apt update
:只检查,不更新(已安装的软件包是否有可用的更新,给出汇总报告)。apt upgrade
:更新已安装的软件包。
二,硬件基础知识速学
2.1,内存
RAM
(随机访问存储)的一些关键特性是带宽(bandwidth
)和延迟(latency
)。
2.2,CPU
中央处理器(central processing unit,CPU
)是任何计算机的核心,其由许多关键组件组成:
- 处理器核心 (processor cores): 用于执行机器代码的。
- 总线(bus): 用于连接不同组件(注意,总线会因为处理器型号、 各代产品和供应商之间的特定拓扑结构有明显不同)
- 缓存(cache): 一般是三级缓(L1/L2/L3 cache),相比主内存实现更高的读取带宽和更低的延迟内存访问。
现代 CPU 都包含向量处理单元,都提供了 SIMD
指令,可以在单个指令中同时处理多个数据,从而支持高性能线性代数和卷积运算。这些 SIMD
指令有不同的名称: 在 ARM 上叫做 NEON,在 x86 上被称 为AVX2156。
一个典型的 Intel Skylake 消费级四核 CPU,其核心架构如下图所示。
三,ncnn 推理模型
3.1,shufflenetv2 模型推理解析
这里以分类网络 shufflenetv2 为例,分析如何使用 ncnn
框架模型推理。先源码在 ncnn/examples/shufflenetv2.cpp
文件中,程序主要分为两个函数,分别是 detect_shufflenetv2()
和 print_topk()
。前者用于运行图片分类网络,后者用于输出前 N 个分类结果。代码流程总结如下:
在
detect_shufflenetv2
函数中,主要使用了ncnn::Net
类进行模型加载和推理,主要流程如下:- 加载模型参数和模型二进制文件。
- 将输入图片
cv::Mat
格式转换为ncnn::Mat
格式,同时进行 resize 和归一化操作。 - 创建
ncnn::Extractor
对象,并设置输入和输出。 - 进行推理计算,得到分类输出结果。
- 对输出结果进行
softmax
操作。 - 将输出结果转换为 vector 类型的数据,存储到 cls_scores 中。
调用
print_topk
函数输出 cls_scores 的前topk
个类别及其得分,具体实现步骤如下:- 定义一个向量
std::vector<std::pair<float, int>> vec
,其元素类型为<float, int>
,其中第一个元素为分类得分,第二个元素为该分类的索引。 - 遍历分类模型输出结果
cls_scores
,将其与索引值组成一个<float, int>
类型的元素,放入向量vec
中。 - 使用
std::partial_sort()
函数,将向量vec
进行部分排序,按照得分从大到小的顺序排列。 - 遍历排好序的向量
vec
,输出前topk
个元素的索引和得分值。
- 定义一个向量
最后主函数 main 中先调用 cv::imread 函数完成图像的读取操作,而后调用
detect_shufflenetv2
和print_topk
函数,完成 shufflenetv2 网络推理和图片分类结果概率值输出的操作。
print_topk
函数代码及其注释如下:
// 定义函数,输入为一个向量 cls_scores 和需要输出的 topk 数量
static int print_topk(const std::vector<float>& cls_scores, int topk)
{
// 1,定义一个向量 vec,其元素类型为 <float, int>,用于存储分类得分和索引值
int size = cls_scores.size();
std::vector<std::pair<float, int> > vec;
vec.resize(size);
// 2,遍历分类得分,将其与索引值组成 <float, int> 元素,并存入向量 vec 中
for (int i = 0; i < size; i++)
{
vec[i] = std::make_pair(cls_scores[i], i);
}
// 3,使用 std::partial_sort() 函数,将向量 vec 进行部分排序,按照得分从大到小的顺序排列
std::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),
std::greater<std::pair<float, int> >());
// 4,遍历排好序的向量 vec,输出前 topk 个元素的索引和得分值
for (int i = 0; i < topk; i++)
{
float score = vec[i].first;
int index = vec[i].second;
fprintf(stderr, "%d = %f\n", index, score);
}
return 0;
}
值得注意的是,虽然调用 print_topk
函数得到了最高得分及其类别索引,但还需要将类别索引转换为类别字符串。这通常需要预先定义一个包含所有类别字符串的向量 class_names
,并将其与类别索引一一对应。另外, class_names
的定义需与模型训练时的类别标签一致,否则会出现类别不匹配的情况。
最后,实际跑下 sample 看下运行结果,这里模型用的是 imagenet 训练的 shufflenetv2 模型,然后用编译好的 shufflenetv2 程序去跑测试图片,输入图片和程序运行结果如下:
/ncnn/build/examples# ./shufflenetv2 demo.jpeg
270 = 0.455700
279 = 0.303561
174 = 0.057936
输入图像的类别索引是 270
,参考文章ImageNet 2012 1000分类名称和编号,可知该类别是 dog(狗)。
3.2,网络推理过程解析
下面再看下网络推理代码的整体流程解析:
1,首先需要 Net
对象,然后使用 load_param
和 load_bin
两个接口载入模型结构参数和模型权重参数文件:
// 为了方便阅读,和官方代码比有所删减
ncnn::Net shufflenetv2;
shufflenetv2.load_param("shufflenet_v2_x0.5.param")
shufflenetv2.load_model("shufflenet_v2_x0.5.bin")
2,定义好 Net 对象后,可以调用相应的 create_extractor 接口创建 Extractor
,Extractor 对象是完成图像数据输入和模型推理的类,虽然它也是对 Net 的相关接口做了封装。
ncnn::Extractor ex = shufflenetv2.create_extractor();
ex.input("data", in);
ncnn::Mat out;
ex.extract("fc", out); // 提取网络输出结果到 out 矩阵中
3,模型推理结果后处理,对网络推理结果执行 softmax 操作得到概率矩阵,而后转换为 vector 类型的数据。
// 对输出结果矩阵进行 softmax 操作
// manually call softmax on the fc output
// convert result into probability
// skip if your model already has softmax operation
{
ncnn::Layer* softmax = ncnn::create_layer("Softmax");
ncnn::ParamDict pd;
softmax->load_param(pd);
softmax->forward_inplace(out, shufflenetv2.opt);
delete softmax;
}
// 将softmax输出结果转换为 vector<float> 类型的数据,存储到 cls_scores 中
out = out.reshape(out.w * out.h * out.c);
cls_scores.resize(out.w);
for (int j = 0; j < out.w; j++)
{
cls_scores[j] = out[j];
}
这里之所以需要手动调用 softmax 层,是因为官方提供的 shufflenetv2 模型结构文件的最后一层是 fc
层,没有 softmax
层。
值得注意的是,ncnn::Mat 类型默认采用的是 NCHW (通道在前,即 Number-Channel-Height-Width)的格式。在常见的分类任务中,ncnn 网络输出的一般是一个大小为 [1, 1, num_classes] 的张量,其中第三个维度的大小为类别数,上述代码即 out.w
表示类别数量,而 out.h 和 out.c 都为 1。
3.3,模型推理过程总结
1,模型推理过程可总结为下述步骤:
- 输入数据准备:输入数据可以是图像、文本或其他形式的数据。在ncnn中,输入数据通常被转化为多维张量,其中第一维是数据的数量,其余维度表示数据的形状和尺寸。
- 加载模型参数和模型权重文件:通过 Net 类的
load_param
和load_bin
两个接口实现。 - 模型前向计算:从模型的输入层开始,逐层计算模型的输出。每个层接收上一层的输出作为输入,并执行特定的算子,比如:卷积、池化、全连接等。在逐层计算过程中,模型各层的参数和权重数据也被用于更新模型的输出。最终,模型的输出被传递到模型的输出层。
- 输出数据解析:模型的输出数据通常被转化为外部应用程序可用的格式。例如,在图像分类任务中,模型的输出可以是一个概率向量,表示输入图像属于每个类别的概率分布。在ncnn中,输出数据可以转化为多维张量或其他形式的数据。
2,ncnn 加载/解析模型参数和权重文件的步骤还是很复杂的,可总结如下:
- 读取二进制参数和权重文件,并存储为字节数组。
- 解析字节数组中的头部信息,包括文件版本号、模型结构信息等。
- 解析层级信息,包括每个层的名称、类型、输入输出维度等信息,并保存在
blobs
中,Blob 类由:网络层 name、依赖层索引:producer 和 consumer,及上一层和下一网络层索引、网络层 shape 组成。 - 解析每个层的参数和权重数据,将其存储为矩阵或向量。
参考资料
NCNN 模型推理详解及实战的更多相关文章
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)
前言 Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework.Canvas有许多的知识内容,构建了一个武器库一般,所谓十 ...
- BS模式的模型结构详解
编号:1004时间:2016年4月12日16:59:17功能:BS模式的模型结构详解 URL:http://blog.csdn.net/icerock2000/article/details/4000 ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)
LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...
- 事件驱动模型实例详解(Java篇)
或许每个软件从业者都有从学习控制台应用程序到学习可视化编程的转变过程,控制台应用程序的优点在于可以方便的练习某个语言的语法和开发习惯(如.net和java),而可视化编程的学习又可以非常方便开发出各类 ...
- (转)Linux下select, poll和epoll IO模型的详解
Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...
- (转)sudo配置文件/etc/sudoers详解及实战用法
sudo配置文件/etc/sudoers详解及实战用法 原文:http://blog.csdn.net/field_yang/article/details/51547804 一.sudo执行命令的流 ...
- Java内存模型(JMM)详解
在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...
- Java8 Stream新特性详解及实战
Java8 Stream新特性详解及实战 背景介绍 在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了.为此, ...
- 【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入
系列目录 上|理论基础+实战控制台程序实现AutoFac注入 下|详解AutoFac+实战Mvc.Api以及.NET Core的依赖注入 前言 本来计划是五篇文章的,每章发个半小时随便翻翻就能懂,但是 ...
- Css盒模型属性详解(margin和padding)
Css盒模型属性详解(margin和padding) 大家好,我是逆战班的一名学员,今天我来给大家分享一下关于盒模型的知识! 关于盒模型的属性详解及用法 盒模型基本属性有两个:padding和marg ...
随机推荐
- ρars/ey 题解
给个链接:ρars/ey. 我们考虑一个树上背包. 设 \(f_{u,i}\) 表示在 \(u\) 号节点的子树内删除 \(i\) 个点的最小代价.显然有答案为 \(f_{1,siz_1-1}\). ...
- Java静态相关问题
问题1: public class Demo01_StaticTest { private static Demo01_StaticTest st = new Demo01_StaticTest(); ...
- 3. 从0开始学ARM-ARM模式、寄存器、流水线
关于ARM的一些基本概念,大家可以参考我之前的文章: <到底什么是Cortex.ARMv8.arm架构.ARM指令集.soc?一文帮你梳理基础概念[科普]> 关于ARM指令用到的IDE开发 ...
- 【CMake系列】01-CMake是什么
在很多开源项目中,经常可以看到CMakeLists.txt 这一文件,依靠它才能完成项目的配置运行过程.那它是什么? 接下来,在这个专栏中,我们将系统学习CMake这一个重要工具. 本专栏的实践代码全 ...
- String究竟能存储多少字符?
能存储多少字符,通过以下步骤来看 首先String的length方法返回是int.所以理论上长度一定不会超过int的最大值. 编译器对字符串字面量长度的限制源自Java编译器(如javac)在处理常量 ...
- get方法传参后端接收数据异常 - 特殊字符需转义
get方法传参的时候,如果有特殊字符,如 + 等,无法被识别,导致后端处理异常,所以,get方式,如果有特殊字符,需要转义后再请求接口 1.java 特殊字符转义 URLEncoder.encode( ...
- 手把手教你安装Jupyter Notebook(保姆级教程)
来源于:https://blog.csdn.net/weixin_43855159/article/details/137738714 1. 什么是Jupyter Notebook Jupyter N ...
- 【YashanDB知识库】yac修改参数后关闭数据库hang住
[标题]yac修改参数后关闭数据库hang住 [问题分类]性能优化 [关键词]YashanDB, yac, shutdown hang [问题描述]修改yac参数后执行shutdown immedia ...
- 学习笔记:robots.txt文件
1.1 介绍 robots.txt文件是一种用于指导搜索引擎爬虫在网站上哪些页面可以被抓取,哪些页面不应该被抓取的文本文件.这个文件通常放置在网站的根目录下. 1.2 由来 robots.txt标准最 ...
- 使用 Helm 在 Kubernetes 上安装 Consul
Consul Sync 部署 官方文档部署:https://developer.hashicorp.com/consul/docs/k8s/installation/install 部署版本 1.14 ...