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 协议 ,转载请注明出处!