ncnn源码分析_5

ncnn 提供了两种解析网络 layer 的方法:

方法一: 在推理中进行解析

模型转换之后,某些 layer 没有转换成功:(在转换的过程中)。 如 shufflenetv2 中,param 文件只转换到了 fc 这一个 layer。这时需要添加一层softmax,可以先对param文件中转换成功的layer做推理,然后手动添加一个softmax层:

ncnn::Extractor ex = shufflenetv2.create_extractor();
ex.input("data", in);

ncnn::Mat out;
ex.extract("fc", out);
// 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;
}

out = out.reshape(out.w * out.h * out.c);

方法二: 自定义层

1. 新建空的类
// 在 ncnn/src/layer/ 下新建两个文件 mylayer.h mylayer.cpp
// 1.mylayer.h
#include "layer.h"
using namespace ncnn;
      
class MyLayer : public Layer
{
};

// 2. mylayer.cpp
#include "mylayer.h"
DEFINE_LAYER_CREATOR(MyLayer)   // 注册新定义的层
2. 定义层参数 parameters 和 权重 weights, 并实现载入函数
// mylayer.h
#include "layer.h"
using namespace ncnn;

class MyLayer : public Layer
{
public:  // 公有方法
      virtual int load_param(const ParamDic& pd);// 虚函数,可以被子类覆盖
      virtual int load_model(const ModelBin& mb);   // 

private: // 私有参数
      int channels;   // 参数1 通道数量
      float eps;      // 参数2 精度
      Mat gamma_data; // 权重
};

// 2. mylayer.cpp
#include "mylayer.h"
DEFINE_LAYER_CREATOR(MyLayer) 

// 实现 load_param() 载入网络层参数
int MyLayer::load_param(const ParamDict& pd)
{
      // 使用pd.get(key,default_val); 从param文件中(key=val)获取参数
      channels = pd.get(0, 0);      // 解析 0=<int value>, 默认为0
      eps      = pd.get(1, 0.001f); // 解析 1=<float value>, 默认为0.001f
      
      return 0; // 载入成功返回0
}

// 实现 load_model() 载入模型权重
int MyLayer::load_model(const ModelBin& mb)
{
      // 读取二进制数据的长度为 channels * sizeof(float)
      // 0 自动判断数据类型, float32 float16 int8
      // 1 按 float32读取  2 按float16读取 3 按int8读取
      gamma_data = mb.load(channels, 1);// 按 float32读取 
      if(gamma_data.empty())
            return -100; // 错误返回非0数,-100表示 out-of-memory 
      return 0; //  载入成功返回0
}
3. 定义类构造函数,确定前向传播行为
// mylayer.h
#include "layer.h"
using namespace ncnn;

class MyLayer : public Layer
{
public:  
      MyLayer();  // 构造函数
      virtual int load_param(const ParamDic& pd);
      virtual int load_model(const ModelBin& mb);  

private:
      int channels;   
      float eps; 
      Mat gamma_data; 
};


// mylayer.cpp
#include "mylayer.h"
DEFINE_LAYER_CREATOR(MyLayer) 

int MyLayer::load_param(const ParamDict& pd)
{
      channels = pd.get(0, 0);     
      eps      = pd.get(1, 0.001f);
      
      return 0;
}


int MyLayer::load_model(const ModelBin& mb)
{
      gamma_data = mb.load(channels, 1);
      if(gamma_data.empty())
            return -100;
      return 0;
}

// 构造函数
MyLayer::MyLayer()
{
      // 是否为 1输入1输出层
      // 1输入1输出层: Convolution, Pooling, ReLU, Softmax ...
      // 反例       : Eltwise, Split, Concat, Slice ...
      one_blob_only = true;

      // 是否可以在 输入blob 上直接修改 后输出
      // 支持在原位置上修改: Relu、BN、scale、Sigmod...
      // 不支持: Convolution、Pooling ...
      support_inplace = true;
}
4. 选择合适的 forward()函数接口, 并实现对应的 forward 函数

Layer类定义了四种 forward()函数:

  • 多输入多输出,const 不可直接对输入进行修改

    virtual int forward(const std::vector<Mat>& bottom_blobs, std::vector<Mat>& top_blobs, const Option& opt) const;
  • 单输入单输出,const 不可直接对输入进行修改

    virtual int forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const;
  • 多输入多输出,可直接对输入进行修改

    virtual int forward_inplace(std::vector<Mat>& bottom_top_blobs, const Option& opt) const;
  • 单输入单输出,可直接对输入进行修改

    virtual int forward_inplace(Mat& bottom_top_blob, const Option& opt) const;

具体实现:

// mylayer.h
#include "layer.h"
using namespace ncnn;

class MyLayer : public Layer
{
public:
      MyLayer();
      virtual int load_param(const ParamDic& pd);
      virtual int load_model(const ModelBin& mb);
      
      virtual int forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const; // 单输入单输出
      virtual int forward_inplace(Mat& bottom_top_blob, const Option& opt) const;          // 单入单出本地修改 

private: 
      int channels;  
      float eps; 
      Mat gamma_data;
};


// mylayer.cpp 
#include "mylayer.h"
DEFINE_LAYER_CREATOR(MyLayer)

MyLayer::MyLayer()
{
      one_blob_only = true;
      support_inplace = true;
}


int MyLayer::load_param(const ParamDict& pd)
{
      channels = pd.get(0, 0);
      eps      = pd.get(1, 0.001f); 
      
      return 0;
}

int MyLayer::load_model(const ModelBin& mb)
{
      gamma_data = mb.load(channels, 1);
      if(gamma_data.empty())
            return -100;

      return 0;
}


// 单入单出 前向传播网络 不可修改 非const
int MyLayer::forward(const Mat& bottom_blob, Mat& top_blob, const Option& opt) const
{
      if(bottom_blob.c != channels)
            return -1;
      
      // 实现运算 x = (x + eps) * gamma_per_channel
      int w = bottom_blob.w;
      int h = bottom_blob.h;
      size_t elemsize = bottom_blob.elemsize;
      int size = w * h;
      
      // 输出需要新建,不可直接在输入blob上修改
      top_blob.create(w, h, channels, elemsize, opt.blob_allocator);
      if(top_blob.empty())
            return -100;
      
      #pragam omp parallel for num_threads(opt.num_threads);
      for (int q=0; q<channels; q++)
      {
            const float* in_ptr = bottom_blob.channel(q);
            float* out_ptr = top_blob.channel(q);
            const float gamma = gamma_data[q];
            
            for (int i=0; i<size; i++)
            {
                  out_ptr[i] = (in_ptr[i] + eps)*gamma;
            }
      
      }
      
      return 0;
}


// 单入单出 前向传播网络  可在输入blob上修改
int MyLayer::forward_inplace(Mat& bottom_top_blob, const Option& opt) const
{
      if(bottom_blob.c != channels)
            return -1; 
      
      // 实现运算 x = (x + eps) * gamma_per_channel
      int w = bottom_blob.w;
      int h = bottom_blob.h;
      int size = w * h;
      
      // 输出不需要新建,可直接在输入blob上修改
      #pragam omp parallel for num_threads(opt.num_threads);
      for (int q=0; q<channels; q++)
      {
            float* in_out_ptr = bottom_top_blob.channel(q);
            const float gamma = gamma_data[q];
            for (int i=0; i<size; i++)
            {
                  in_out_ptr[i] = (in_out_ptr[i] + eps)*gamma;
            }
      
      }
      
      return 0;
}
5. 集成进 ncnn 库
// 层类型       层名称   输入数量  输出数量   输入层   输出层
// MyLayer     mylayer    1       1     conv2d   mylayer0  0=32 1=0.2 // 对应 param
// 层类型: 和对应的注册的名名称一致
// 注册新层
ncnn::Net net;
net.register_custom_layer("MyLayer", MyLayer_layer_creator); // 注册新层

net.load_param("model.param");
net.load_model("model.bin");


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!