当前位置: 首页 > article >正文

深入解析ncnn::Net类——高效部署神经网络的核心组件

在这里插入图片描述

最近在学习ncnn推理框架,下面整理了ncnn::Net 的使用方法。

在移动端和嵌入式设备上进行高效的神经网络推理,要求框架具备轻量化、高性能以及灵活的扩展能力。作为腾讯开源的高性能神经网络推理框架,ncnn在这些方面表现出色。而在ncnn的核心组件中,ncnn::Net类扮演了至关重要的角色。本文将详细介绍ncnn::Net类的结构、功能及其使用方法,帮助开发者更好地理解和利用这一强大的工具。

目录

  1. Net类简介
  2. 构造与析构
  3. Option选项配置
  4. 自定义层注册
  5. 加载网络参数与模型
  6. 输入与输出管理
  7. 创建Extractor
  8. Vulkan支持
  9. 内存管理与优化
  10. 实例代码
  11. 使用示例
  12. 总结

1. Net类简介

ncnn::Net类是ncnn框架中用于管理和执行神经网络的主要接口。它负责加载网络结构和权重、配置推理选项、管理层与Blob等关键资源,并提供了与外部数据交互的接口。通过Net类,开发者可以方便地加载预训练模型、进行前向推理并获取结果,适用于多种应用场景,如图像分类、目标检测和语义分割等。

namespace ncnn {

class NCNN_EXPORT Net
{
public:
    // 构造函数
    Net();
    
    // 析构函数
    virtual ~Net();

public:
    // 配置选项
    Option opt;

    // Vulkan相关设置
    #if NCNN_VULKAN
        void set_vulkan_device(int device_index);
        void set_vulkan_device(const VulkanDevice* vkdev);
        const VulkanDevice* vulkan_device() const;
    #endif // NCNN_VULKAN

    // 自定义层注册
    int register_custom_layer(const char* type, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
    virtual int custom_layer_to_index(const char* type);
    int register_custom_layer(int index, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
    
    // 加载参数与模型
    int load_param_bin(const DataReader& dr);
    int load_model(const DataReader& dr);
    
    #if NCNN_STDIO
        #if NCNN_STRING
            int load_param(FILE* fp);
            int load_param(const char* protopath);
            int load_param_mem(const char* mem);
        #endif // NCNN_STRING
        int load_param_bin(FILE* fp);
        int load_param_bin(const char* protopath);
        int load_model(FILE* fp);
        int load_model(const char* modelpath);
    #endif // NCNN_STDIO

    // 清除网络
    void clear();

    // 创建提取器
    Extractor create_extractor() const;

    // 获取输入输出信息
    const std::vector<int>& input_indexes() const;
    const std::vector<int>& output_indexes() const;
    #if NCNN_STRING
        const std::vector<const char*>& input_names() const;
        const std::vector<const char*>& output_names() const;
    #endif
    const std::vector<Blob>& blobs() const;
    const std::vector<Layer*>& layers() const;
    std::vector<Blob>& mutable_blobs();
    std::vector<Layer*>& mutable_layers();

protected:
    // 自定义层创建
    virtual Layer* create_custom_layer(int index);
    virtual Layer* create_overwrite_builtin_layer(int typeindex);

private:
    Net(const Net&);
    Net& operator=(const Net&);

private:
    NetPrivate* const d;
};

} // namespace ncnn

2. 构造与析构

Net类提供了默认构造函数和析构函数,用于初始化和销毁网络对象。在构造函数中,网络的内部资源会被初始化,而析构函数则负责释放所有分配的资源,确保没有内存泄漏。

ncnn::Net net; // 创建Net对象
// 使用net进行操作

// Net对象在作用域结束时自动析构

3. Option选项配置

Net类中的Option成员用于配置网络的各种参数,如线程数、内存分配器、是否启用Vulkan计算等。这些选项可以在加载网络结构和权重之前进行设置,以优化推理性能和资源使用。

Option类概述

Option类定义在option.h文件中,包含了多个可配置选项,如下所示:

namespace ncnn {

class Allocator;
#if NCNN_VULKAN
class VkAllocator;
class PipelineCache;
#endif // NCNN_VULKAN

class NCNN_EXPORT Option
{
public:
    Option();

public:
    bool lightmode;
    int num_threads;
    Allocator* blob_allocator;
    Allocator* workspace_allocator;

#if NCNN_VULKAN
    VkAllocator* blob_vkallocator;
    VkAllocator* workspace_vkallocator;
    VkAllocator* staging_vkallocator;
    PipelineCache* pipeline_cache;
#endif // NCNN_VULKAN

    int openmp_blocktime;
    bool use_winograd_convolution;
    bool use_sgemm_convolution;
    bool use_int8_inference;
    bool use_vulkan_compute;
    bool use_bf16_storage;

    bool use_fp16_packed;
    bool use_fp16_storage;
    bool use_fp16_arithmetic;
    bool use_int8_packed;
    bool use_int8_storage;
    bool use_int8_arithmetic;

    bool use_packing_layout;
    bool use_shader_pack8;

    bool use_subgroup_basic;
    bool use_subgroup_vote;
    bool use_subgroup_ballot;
    bool use_subgroup_shuffle;

    bool use_image_storage;
    bool use_tensor_storage;

    bool use_reserved_0;

    int flush_denormals;

    bool use_local_pool_allocator;
    bool use_shader_local_memory;
    bool use_cooperative_matrix;

    bool use_winograd23_convolution;
    bool use_winograd43_convolution;
    bool use_winograd63_convolution;

    bool use_a53_a55_optimized_kernel;

    bool use_reserved_7;
    bool use_reserved_8;
    bool use_reserved_9;
    bool use_reserved_10;
    bool use_reserved_11;
};

} // namespace ncnn

配置选项示例

ncnn::Net net;
ncnn::Option opt;

// 启用轻量模式
opt.lightmode = true;

// 设置线程数为4
opt.num_threads = 4;

// 启用Vulkan计算
opt.use_vulkan_compute = true;

// 使用自定义内存分配器
CustomAllocator alloc;
opt.blob_allocator = &alloc;
opt.workspace_allocator = &alloc;

// 将配置应用到网络
net.opt = opt;

4. 自定义层注册

ncnn框架允许开发者注册自定义层,以扩展框架的功能或实现特定的操作。register_custom_layer函数用于注册自定义层,支持通过层类型名称或索引进行注册。

通过层类型名称注册

#if NCNN_STRING
    int register_custom_layer(const char* type, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);
    virtual int custom_layer_to_index(const char* type);
#endif // NCNN_STRING

通过层索引注册

int register_custom_layer(int index, layer_creator_func creator, layer_destroyer_func destroyer = 0, void* userdata = 0);

使用示例

假设我们有一个自定义层MyCustomLayer,其创建和销毁函数分别为create_my_custom_layerdestroy_my_custom_layer

// 定义创建函数
Layer* create_my_custom_layer();
void destroy_my_custom_layer(Layer* layer);

ncnn::Net net;

// 通过层类型名称注册
net.register_custom_layer("MyCustomLayer", create_my_custom_layer, destroy_my_custom_layer);

// 或者通过层索引注册(假设MyCustomLayer的索引为100)
net.register_custom_layer(100, create_my_custom_layer, destroy_my_custom_layer);

5. 加载网络参数与模型

Net类提供了多种加载网络参数和模型文件的方法,支持从文件系统和内存中加载网络结构(参数)和权重(模型)。以下将详细介绍这些方法。

加载参数

参数文件定义了网络的结构,包括各层的类型、序列及其连接关系。参数文件可以是文本格式(param)或二进制格式(param.bin)。

// 加载二进制参数文件
int ret = net.load_param_bin("model.param.bin");
if (ret != 0) {
    // 处理错误
}

// 加载内存中的参数数据
unsigned char* param_mem = ...; // 参数数据内存指针
int consumed = net.load_param(param_mem);

加载模型

模型文件包含了网络的权重数据。模型文件通常为二进制格式(model.bin),也可以从内存中加载。

// 加载二进制模型文件
int ret = net.load_model("model.bin");
if (ret != 0) {
    // 处理错误
}

// 加载内存中的模型数据
unsigned char* model_mem = ...; // 模型数据内存指针
int consumed = net.load_model(model_mem);

从文件加载参数与模型

#if NCNN_STDIO
    #if NCNN_STRING
        // 从文件路径加载参数
        int ret = net.load_param("model.param");
    #endif // NCNN_STRING
    // 从文件路径加载二进制参数
    int ret = net.load_param_bin("model.param.bin");

    // 从文件路径加载模型
    ret = net.load_model("model.bin");
#endif // NCNN_STDIO

从Android Asset加载(仅适用于Android平台)

#if NCNN_PLATFORM_API && __ANDROID_API__ >= 9
    #if NCNN_STRING
        // 从Android Asset加载参数文件
        net.load_param(asset);
        net.load_param(asset_mgr, "path/to/model.param");

        // 从Android Asset加载二进制参数文件
        net.load_param_bin(asset);
        net.load_param_bin(asset_mgr, "path/to/model.param.bin");
    #endif // NCNN_STRING
    // 从Android Asset加载模型文件
    net.load_model(asset);
    net.load_model(asset_mgr, "path/to/model.bin");
#endif // NCNN_PLATFORM_API

6. 输入与输出管理

ncnn::Net类通过Blob索引和名称管理输入和输出。开发者可以通过索引或名称设置输入数据,并提取推理结果。

获取输入输出索引与名称

const std::vector<int>& input_indexes = net.input_indexes();
const std::vector<int>& output_indexes = net.output_indexes();

#if NCNN_STRING
    const std::vector<const char*>& input_names = net.input_names();
    const std::vector<const char*>& output_names = net.output_names();
#endif

通过索引设置输入和提取输出

ncnn::Extractor ex = net.create_extractor();

// 设置输入
ex.input(0, input_mat); // 通过Blob索引设置

// 提取输出
ncnn::Mat output_mat;
ex.extract(0, output_mat); // 通过Blob索引提取

通过名称设置输入和提取输出(需要开启NCNN_STRING)

#if NCNN_STRING
    ncnn::Extractor ex = net.create_extractor();

    // 设置输入
    ex.input("input_blob_name", input_mat); // 通过Blob名称设置

    // 提取输出
    ncnn::Mat output_mat;
    ex.extract("output_blob_name", output_mat); // 通过Blob名称提取
#endif

7. 创建Extractor

Extractor类是用于执行前向推理的实例,通过Net类创建。Extractor支持多线程、内存分配器设置以及Vulkan加速等配置。

创建Extractor

ncnn::Extractor ex = net.create_extractor();

配置Extractor选项

ex.set_light_mode(true); // 启用轻量模式,回收中间Blob
ex.set_num_threads(4); // 设置Extractor的线程数

// 设置自定义内存分配器
CustomAllocator alloc;
ex.set_blob_allocator(&alloc);
ex.set_workspace_allocator(&alloc);

8. Vulkan支持

ncnn支持基于Vulkan的GPU加速,通过VkAllocator和相关配置,可以在支持Vulkan的设备上利用GPU进行高效推理。

设置Vulkan设备

#if NCNN_VULKAN
    // 通过设备索引设置Vulkan设备
    net.set_vulkan_device(0); // 使用第一个Vulkan设备

    // 或者通过VulkanDevice句柄设置
    const VulkanDevice* vkdev = ...;
    net.set_vulkan_device(vkdev);
#endif // NCNN_VULKAN

设置Extractor的Vulkan选项

#if NCNN_VULKAN
    ncnn::Extractor ex = net.create_extractor();
    ex.set_vulkan_compute(true); // 启用Vulkan计算

    // 设置Vulkan内存分配器
    VkAllocator vkalloc;
    ex.set_blob_vkallocator(&vkalloc);
    ex.set_workspace_vkallocator(&vkalloc);
    ex.set_staging_vkallocator(&vkalloc);
#endif // NCNN_VULKAN

9. 内存管理与优化

ncnn通过引用计数、自定义内存分配器和内存池复用等机制,实现高效的内存管理。同时,OptionExtractor类提供了多种配置选项,帮助开发者根据需求优化内存使用和推理性能。

自定义内存分配器

开发者可以实现自定义的Allocator,以满足特定的内存管理需求或优化策略。

class CustomAllocator : public ncnn::Allocator 
{
public:
    virtual void* fastMalloc(size_t size) override {
        // 自定义内存分配逻辑
        return malloc(size);
    }

    virtual void fastFree(void* ptr) override {
        // 自定义内存释放逻辑
        free(ptr);
    }
};

CustomAllocator alloc;
ncnn::Option opt;
opt.blob_allocator = &alloc;
opt.workspace_allocator = &alloc;
net.opt = opt;

内存池复用与对齐

通过内存池复用和自动内存对齐,ncnn提高了内存利用率和数据访问效率,减少了内存分配和释放的开销。

class PoolAllocator : public ncnn::Allocator 
{
    std::vector<std::pair<size_t, void*>> freed_memory;
public:
    virtual void* fastMalloc(size_t size) override {
        for (auto it = freed_memory.begin(); it != freed_memory.end(); ++it) {
            if (it->first >= size) {
                void* ptr = it->second;
                freed_memory.erase(it);
                return ptr;
            }
        }
        return malloc(size);
    }

    virtual void fastFree(void* ptr) override {
        size_t size = /* 获取ptr对应的大小 */;
        freed_memory.emplace_back(size, ptr);
    }
};

PoolAllocator pool_alloc;
ncnn::Option opt;
opt.blob_allocator = &pool_alloc;
opt.workspace_allocator = &pool_alloc;
net.opt = opt;

10. 实例代码

以下是一个完整的示例,展示如何使用ncnn::Net类加载模型、设置Option、创建Extractor并进行推理。

#include "net.h"
#include "mat.h"
#include <stdio.h>

int main() {
    // 创建Net对象
    ncnn::Net net;

    // 配置选项
    ncnn::Option opt;
    opt.lightmode = true;
    opt.num_threads = 4;
    opt.use_winograd_convolution = true;
    opt.use_sgemm_convolution = true;
    opt.use_int8_inference = true;
    opt.use_packing_layout = true;
    net.opt = opt;

    // 加载参数和模型
    if (net.load_param("model.param") != 0) {
        printf("Failed to load param file\n");
        return -1;
    }
    if (net.load_model("model.bin") != 0) {
        printf("Failed to load model file\n");
        return -1;
    }

    // 创建Extractor
    ncnn::Extractor ex = net.create_extractor();
    ex.set_num_threads(4);
    ex.set_light_mode(true);

    // 准备输入数据
    ncnn::Mat in = ncnn::Mat::from_pixels_resize(image_data, ncnn::Mat::PIXEL_RGB, width, height, 224, 224);
    in.substract_mean_normalize(mean_vals, norm_vals);

    // 设置输入
    ex.input("input_blob", in);

    // 提取输出
    ncnn::Mat out;
    ex.extract("output_blob", out);

    // 处理输出
    float* ptr = out;
    for (int i = 0; i < out.w; i++) {
        printf("Class %d: %f\n", i, ptr[i]);
    }

    return 0;
}

11. 使用示例

下面是一个更具体的示例,展示如何加载一个预训练的图像分类模型,并对一张图片进行推理。

#include "net.h"
#include "mat.h"
#include <stdio.h>

// 假设有一个函数读取图片并返回RGB数据
unsigned char* load_image(const char* image_path, int& width, int& height);

int main() {
    // 创建Net对象
    ncnn::Net net;

    // 配置选项
    ncnn::Option opt;
    opt.lightmode = true;
    opt.num_threads = 4;
    opt.use_int8_inference = true;
    net.opt = opt;

    // 加载网络结构和模型
    if (net.load_param("mobilenet_v1.param") != 0) {
        printf("加载参数文件失败\n");
        return -1;
    }
    if (net.load_model("mobilenet_v1.bin") != 0) {
        printf("加载模型文件失败\n");
        return -1;
    }

    // 创建Extractor
    ncnn::Extractor ex = net.create_extractor();

    // 加载并预处理图片
    int width, height;
    unsigned char* image_data = load_image("test.jpg", width, height);
    if (!image_data) {
        printf("无法加载图片\n");
        return -1;
    }

    ncnn::Mat in = ncnn::Mat::from_pixels_resize(image_data, ncnn::Mat::PIXEL_RGB, width, height, 224, 224);
    in.substract_mean_normalize(
        ncnn::Mat(3, 1, (float[]) {104.f, 117.f, 123.f}), // 均值
        ncnn::Mat(3, 1, (float[]) {1.f, 1.f, 1.f})      // 归一化
    );

    // 设置输入
    ex.input("data", in);

    // 提取输出
    ncnn::Mat out;
    ex.extract("prob", out);

    // 找到概率最高的分类
    float max_score = out[0];
    int max_index = 0;
    for (int i = 1; i < out.w; i++) {
        if (out[i] > max_score) {
            max_score = out[i];
            max_index = i;
        }
    }

    printf("预测结果: 类别索引 = %d, 置信度 = %f\n", max_index, max_score);

    // 释放图片数据
    free(image_data);

    return 0;
}

12. 总结

ncnn::Net类作为ncnn框架的核心组件,具备以下显著特点:

  1. 灵活的选项配置:通过Option类,开发者可以细粒度地配置推理参数,如线程数、内存分配策略、优化选项等。
  2. 高效的内存管理:支持自定义内存分配器、内存池复用和自动对齐,确保内存的高效利用和访问效率。
  3. 扩展性强:允许开发者注册自定义层,扩展框架功能,以适应特定的应用需求。
  4. 支持多种数据加载方式:支持从文件系统和内存中加载网络结构和权重,适用于不同的应用场景。
  5. Vulkan加速支持:通过Vulkan实现GPU加速,提升推理性能,特别适用于移动设备和嵌入式系统。
  6. 简便的输入输出管理:通过Blob索引和名称管理输入和输出,提供多种设置和提取接口,便于集成和使用。
  7. 易于使用的接口:提供简洁的API接口,结合丰富的示例代码,降低了神经网络部署的门槛。

通过本文的详细介绍,相信您已经对ncnn::Net类有了全面的了解。无论是在资源受限的移动设备上进行高效推理,还是在嵌入式系统中实现复杂的神经网络应用,ncnn::Net类都能为您提供强大的支持和高效的性能表现。


希望这篇博客能帮助您更好地理解和使用ncnn::Net类,在您的深度学习项目中发挥更大的作用。如果您有任何问题或建议,欢迎在评论区留言讨论!


http://www.kler.cn/a/520600.html

相关文章:

  • ospf动态路由配置,cost路径调整,ospf认证实验
  • Couchbase UI: Server
  • 001 mybatis入门
  • 电梯系统的UML文档11
  • SOME/IP--协议英文原文讲解1
  • 对神经网络基础的理解
  • 文献阅读 250125-Accurate predictions on small data with a tabular foundation model
  • SQL Server 使用SELECT INTO实现表备份
  • JWT 实战:在 Spring Boot 中的使用
  • 网络模型简介:OSI七层模型与TCP/IP模型
  • Learning Vue 读书笔记 Chapter 2
  • 【React+ts】 react项目中引入bootstrap、ts中的接口
  • JavaScript使用toFixed保留一位小数的踩坑记录:TypeError: xxx.toFixed is not a function
  • vue3中customRef的用法以及使用场景
  • LeetCode题练习与总结:两个字符串的删除操作--583
  • 9.4 GPT Action 开发实践:从设计到实现的实战指南
  • PoolingHttpClient试验
  • 独立游戏开发赚钱吗?
  • 从0到1:C++ 开启游戏开发奇幻之旅(一)
  • 重构(1)if-else
  • webview_flutter_android 4.3.0使用
  • java 字符串日期字段格式化前端显示
  • 并发操作下如何加锁,自动释放锁,异常情况可以主动释放锁
  • gitee——报错修改本地密码
  • 51单片机开发:独立键盘实验
  • 数据结构:log-structed结构MemTableSSTable