ncnn源码阅读笔记(二)

2019-10-10  本文已影响0人  半笔闪

Net

使用ncnn部署模型时,我们要先定义一个Net对象,然后使用load_param和load_model载入模型结构参数和模型权重参数

ncnn::Net xxxnet;       //声明一个Net对象

xxxnet.load_param(xxx.param);     //载入模型结构参数文件
xxxnet.load_model(xxx.bin);           //载入模型权重参数文件

在src/目录下有一个net.h和net.cpp,这就是Net类的定义。其中包含了vulkan的代码,上一篇介绍过vulkan主要时用来做计算加速的,这里先暂时剔除valkan的相关代码,来看原始的代码。在#if NCNN_VULKAN和#endif // NCNN_VULKAN之间的代码都是vulkan相关的代码。剔除vulkan相关代码和头文件后,来看net.h的源码,有两个class,Net和Extractor,先来看Net:

namespace ncnn {
class Extractor;
class Net
{
public:
    // empty init
    //空构造函数
    Net();
    // clear and destroy
    //析构函数
    ~Net();

public:
    // option can be changed before loading
    //Option对象是用于load参数之前传入基本设置,比如线程数等
    Option opt;
/*这一块用于用于注册用户自定义的layer,可以先略过不看,主要是两个注册自定义layer的函数,一个是传入命名自定义,一个是传入索引自定义
#if NCNN_STRING
    // register custom layer by layer type name
    // return 0 if success
    int register_custom_layer(const char* type, layer_creator_func creator);
#endif // NCNN_STRING
    // register custom layer by layer type
    // return 0 if success
    int register_custom_layer(int index, layer_creator_func creator);
*/
//接下来是多态的两个函数load_param和load_model
#if NCNN_STDIO
#if NCNN_STRING
    // load network structure from plain param file
    // return 0 if success
    //通过文本文件加载网络结构
    int load_param(FILE* fp);
    //通过路径加载网络结构
    int load_param(const char* protopath);
    //通过内存加载网络结构
    int load_param_mem(const char* mem);
#endif // NCNN_STRING
    // load network structure from binary param file
    // return 0 if success
    //通过二进制文件加载网络结构
    int load_param_bin(FILE* fp);
    int load_param_bin(const char* protopath);

    // load network weight data from model file
    // return 0 if success
    //通过model文件加载网络权重
    int load_model(FILE* fp);
    int load_model(const char* modelpath);
#endif // NCNN_STDIO

    // load network structure from external memory
    // memory pointer must be 32-bit aligned
    // return bytes consumed
    //通过外置内存加载网络结构
    int load_param(const unsigned char* mem);

    // reference network weight data from external memory
    // weight data is not copied but referenced
    // so external memory should be retained when used
    // memory pointer must be 32-bit aligned
    // return bytes consumed
    //通过外置内存加载网络权重
    int load_model(const unsigned char* mem);

    // unload network structure and weight data
    //清除网络结构和网络权重
    void clear();

    // construct an Extractor from network
    //在Net内构建一个Extractor对象
    Extractor create_extractor() const;

protected:
    // parse the structure of network
    // fuse int8 op dequantize and quantize by requantize
    //重置网络,用于重用网络
    int fuse_network();
    //友元类,主要作用让Extractor对象可以访问Net对象中的私有和保护的属性和函数
    friend class Extractor;
#if NCNN_STRING
    //通过name查找对应的blob索引
    int find_blob_index_by_name(const char* name) const;
    //通过name查找对应的layer索引
    int find_layer_index_by_name(const char* name) const;
    //通过layer类型查找索引
    int custom_layer_to_index(const char* type);
    //根据类型创建layer
    Layer* create_custom_layer(const char* type);
#endif // NCNN_STRING
    //根据索引创建layer
    Layer* create_custom_layer(int index);
    //前向推理层
    int forward_layer(int layer_index, std::vector<Mat>& blob_mats, Option& opt) const;

protected:
    //用于存储网络的blob的vector
    std::vector<Blob> blobs;
    //用于存储网络的layer的vector
    std::vector<Layer*> layers;
    用于存储注册的layer
    std::vector<layer_registry_entry> custom_layer_registry;
};

load_param

看完了net.h,根据在上一篇最后的说的阅读顺序,我们来看看其中的函数实现,首先来看load_param函数,在net.cpp中,主要来看最常用的通过路径加载网络结构的load_param

int Net::load_param(const char* protopath)
{
    //打开网络结构文件
    FILE* fp = fopen(protopath, "rb");
    if (!fp)//如果打开失败
    {
        fprintf(stderr, "fopen %s failed\n", protopath);
        return -1;
    }
    //最终还是调用int Net::load_param(FILE* fp)
    int ret = load_param(fp);
    //关闭文件
    fclose(fp);
    return ret;
}

可以看到最终调用的还是int Net::load_param(FILE* fp),所以就来看这个函数,这个函数代码行数有点多,我们分段来阅读:
1)fp参数
首先看看传进来的参数是什么,传进来的参数就是我们的xxx.param文件里的内容,如下(以=LeNet的网络结构为例):

7767517
9 9
Input            data             0 1 data 0=28 1=28 2=1
Convolution      conv1            1 1 data conv1 0=20 1=5 2=1 3=1 4=0 5=1 6=500
Pooling          pool1            1 1 conv1 pool1 0=0 1=2 2=2 3=0 4=0
Convolution      conv2            1 1 pool1 conv2 0=50 1=5 2=1 3=1 4=0 5=1 6=25000
Pooling          pool2            1 1 conv2 pool2 0=0 1=2 2=2 3=0 4=0
InnerProduct     ip1              1 1 pool2 ip1 0=500 1=1 2=400000
ReLU             relu1            1 1 ip1 ip1_relu1
InnerProduct     ip2              1 1 ip1_relu1 ip2 0=10 1=1 2=5000
Softmax          prob             1 1 ip2 prob 0=0

2)xxx.param第一行,magic数
先来看xxx.param文件的第一行,跟Java的magic数类似,ncnn也有一个magic数7767517,这个magic数的作用是确定读进来的xxx.param文件是最新版本的

int magic = 0;
//读取第一行的magic数
int nbr = fscanf(fp, "%d", &magic);
if (nbr != 1)
{
    fprintf(stderr, "issue with param file\n");
    return -1;
}
if (magic != 7767517)
{
    fprintf(stderr, "param is too old, please regenerate\n");
    return -1;
}

3)xxx.param第二行,网络的layer层数及blob数

// parse
    int layer_count = 0;
    int blob_count = 0;
    //读取第二行的9 9,layer层数和blob数
    nbr = fscanf(fp, "%d %d", &layer_count, &blob_count);
    //读取失败
    if (nbr != 2 || layer_count <= 0 || blob_count <= 0)
    {
        fprintf(stderr, "issue with param file\n");
        return -1;
    }
    //读取成功则把在Net对象中用来存储layer和blob的两个vector resize出来
    layers.resize((size_t)layer_count);
    blobs.resize((size_t)blob_count);

4)xxx.param第三行到最后一行,解析网络结构的每一层
从第三行开始是网络结构的层,每一行都有7类元素

  1. 层类型
  2. 层名称
  3. 输入数据结构数量(bottom blob)
  4. 输出数据结构数量(top blob)
  5. 网络输入层名(一个或多个)
  6. 网络输出层名(一个或多个)
  7. 特殊参数(0个或多个): 一种是k=v的类型;另一种是k=len,v1,v2,v3….(数组类型)。该层在ncnn中是存放到paramDict结构中,不同类型层,各种参数意义不一样。
    以下面这层卷积层为例:
Convolution      conv1            1 1 data conv1 0=20 1=5 2=1 3=1 4=0 5=1 6=500
//层类型:Convolution  层名称:conv1     bottom blob:1    top blob:1  网络输入层名:data   网络输出层名:conv1   特殊参数:0=20 1=5 2=1 3=1 4=0 5=1 6=500

这里给出不同层类型的对应特殊参数的对照表https://github.com/Tencent/ncnn/wiki/operation-param-weight-table
理论解析完层,再来看代码:

//特殊参数存放的数据结构
ParamDict pd;
//初始化blob的索引
    int blob_index = 0;
//遍历每一层
    for (int i=0; i<layer_count; i++)
    {
        int nscan = 0;
        //用来存储layer的类型
        char layer_type[257];
        //用来存储layer的名称
        char layer_name[257];
        //用来存储输入数据结构数量(bottom blob)
        int bottom_count = 0;
        //用来存储输出数据结构数量(top blob)
        int top_count = 0;
        //读入网络结构的层的type,name,输入bottom数和输出top数目
        nscan = fscanf(fp, "%256s %256s %d %d", layer_type, layer_name, &bottom_count, &top_count);
        每层读前面四个数是否成功
        if (nscan != 4)
        {
            continue;
        }
        //创建layer
        Layer* layer = create_layer(layer_type);
        //如果layer不是默认类型,创建自定义layer
        if (!layer)
        {
            layer = create_custom_layer(layer_type);
        }
        //如果自定义layer没有注册过
        if (!layer)
        {
            fprintf(stderr, "layer %s not exists or registered\n", layer_type);
            clear();
            return -1;
        }
        //把读入的layer的类型和名称赋值给创建的layer对象
        layer->type = std::string(layer_type);
        layer->name = std::string(layer_name);
        //根据读入的bottom blob的数量resize layer的输入数据结构
        layer->bottoms.resize(bottom_count);
        //解析layer的输入
        for (int j=0; j<bottom_count; j++)
        {
            //用来存储bottom的名字
            char bottom_name[257];
            //读入botoom的名字
            nscan = fscanf(fp, "%256s", bottom_name);
            if (nscan != 1)
            {
                continue;
            }
            //Net对象的函数,通过bottom的名字查找对应的blob 的索引
            int bottom_blob_index = find_blob_index_by_name(bottom_name);
            //如果没有找到,则向blobs的vector中插入一个名为bottom_name的blob
            if (bottom_blob_index == -1)
            {
                //设置第“index索引”个blob的参数
                Blob& blob = blobs[blob_index];
                //blob的索引
                bottom_blob_index = blob_index;
                //blob的名字
                blob.name = std::string(bottom_name);
//                 fprintf(stderr, "new blob %s\n", bottom_name);
                //更新blob索引
                blob_index++;
            }
            //设置当前blob的参数
            Blob& blob = blobs[bottom_blob_index];
            //第i层以当前blob作为层的输入
            blob.consumers.push_back(i);
            //第i层的输入数据结构的第j个输入
            layer->bottoms[j] = bottom_blob_index;
        }
        //输出数据结构的初始化基本和输入数据结构的初始化相同
        //解析layer的输入
        layer->tops.resize(top_count);
        for (int j=0; j<top_count; j++)
        {
            Blob& blob = blobs[blob_index];
            //用来存储top的名字
            char blob_name[257];
            //读入top的名字
            nscan = fscanf(fp, "%256s", blob_name);
            if (nscan != 1)
            {
                continue;
            }
            //设置输出blob对应的名字
            blob.name = std::string(blob_name);
            //设置这个blob的生产者,即输出这个blob的层索引
            blob.producer = i;
            //设置第i层输出数据结构的第j个输入
            layer->tops[j] = blob_index;
            //更新blob索引
            blob_index++;
        }

        // layer specific params
        //用ParamDict 对象接收xxx.param第三行以后的每一行后面的特殊参数
        // 一种是k=v的类型;另一种是k=len,v1,v2,v3….(数组类型)。该层在ncnn中是存放到paramDict结构中,不同类型层,各种参数意义不一样。
        int pdlr = pd.load_param(fp);
        if (pdlr != 0)
        {
            fprintf(stderr, "ParamDict load_param failed\n");
            continue;
        }
        //传递给对应layer对象
        int lr = layer->load_param(pd);
        if (lr != 0)
        {
            fprintf(stderr, "layer load_param failed\n");
            continue;
        }
        //把解析初始化好的layer对象放入Net对象的layer的vector中
        layers[i] = layer;
    }
上一篇下一篇

猜你喜欢

热点阅读