PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=068)
本文发布于 2018-07-19 11:05:52,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=068)
环境说明
时间:2018.07.19
ncnn master commit id:b3e24cafc37483dcc97ee61e6f0f6ff1b094300e
前言
前面两篇文章我们分析了ncnn的加载参数和网络的基本工作流程。其实这一切只是为了给这篇文章做准备。因为我觉得ncnn作为一个前向框架,写的还是比较简单的,方便我们这些小菜鸟对其工作原理进行分析。而分析的方法,还是得从哪里来,从哪里去(读源码)。
前置内容(非常重要的)
本文作为例子的网络
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
ncnn的基本调用流程
#include "net.h"
ncnn::Net abc_net;
ncnn::Mat in_img;
ncnn::Mat out_img;
abc_net.load_param(param_path);
abc_net.load_model(model_path);
ncnn::Extractor ex = abc_net.create_extractor();
ex.set_num_threads(4);
ex.set_light_mode(true);
ex.input("data", in_img);
ex.extract("prob", out_img);
这里简要说明一下:
前两篇文章分别介绍了load_param load_model的基本工作原理,这里要介绍的就是剩下的所有内容。
相关数据结构准备(此小节内容可作为前两篇文章的内容补充)
在load_param时:
ncnn::Net::blobs存放着每一个blob的相关信息,主要信息为name、producer、consumers,含义分别为:名字、产生这个blob数据的层、消费这个blob数据的层。
ncnn::Net::layers存放的是:
- ncnn::Net::layers::type
- ncnn::Net::layers::name
- ncnn::Net::layers::bottoms 存的是此层需要的输入blob的idx
- ncnn::Net::layers::tops 存的是此层输出的blob的idx
在load_param中会根据我们读入的type来create_layer,这里建立这个layer也挺有意思的。
//这里的layer_to_index会去layer_registry去查找对应的层类型的idx,这里的layer_registry数组是我们在编译ncnn的时候初始化的,里面存放的是如下的东西。
#if NCNN_STRING
{"Convolution",Convolution_x86_layer_creator},
#else
{Convolution_x86_layer_creator},
//这个数组的作用就是用来查询具体层的idx和其提供的构造接口creator。每个层都会实现这个creator,比如Convolution层在x86架构下,其构造接口名字叫做Convolution_x86_layer_creator。其原理如下:
DEFINE_LAYER_CREATOR(Convolution_x86)//通过宏定义Convolution_x86_layer_creator这个全局函数
#define DEFINE_LAYER_CREATOR(name) \
::ncnn::Layer* name##_layer_creator() { return new name; }
以前文网络为例分析
这里的分析入口为:
ncnn::Extractor::input()
//图中blob_mats 就是整个框架工作时的数据存放向量。
std::vector<Mat> blob_mats;//blob_mats 定义
//blob_mats的大小初始化在ncnn::Net::create_extractor()中完成,这里唯一需要注意的是,此函数是类Extractor的友元类成员函数,这样写的原因是为了访问其protect的构造函数。
Extractor Net::create_extractor() const
{
return Extractor(this, blobs.size());
}
Extractor::Extractor(const Net* _net, int blob_count) : net(_net)
{
blob_mats.resize(blob_count);
lightmode = true;
num_threads = 0;
}
//find_blob_index_by_name 就是在ncnn::Net::blobs中去循环遍历,得到其idx。
//然后把输入的mat数据,放入到blob_mats中相应的位置去。到这里,输入数据就填充完了。
ncnn::Extractor::extract()
//此调用的开始时,根据名字通过find_blob_index_by_name 查找我们需要的输出层的idx,然后把blob_mats(携带输入数据)、lightmode、我们需要的输出层的idx一起传入给ncnn::Net::forward_layer()。然后将上述调用处理好的数据放入feature返回。一个网络的前向计算就完成了。
ncnn::Net::forward_layer()
上图这个if语句是这层网络只有一个输入和输出
此图的else是这层网络非一个输入和输出
//这里我只分析只有一个输入和输出的情况,另外一种和它非常相近。
//图中line 637-line 642,这里通过递归调用一层层倒推回去,直到我们的网络输入层。因为这一层在input的时候给blob_mats赋值,其dims不为零。
//图中line646-line 655是set_light_mode的作用,其作用为是否释放计算过程中,存入blob_mats中,在前面层计算的得到的数据
//图中line 658-line 691是开始前向计算。这里的计算分为两类,一类是在输入数据上计算,并把输出数据放入到输入数据的变量中。另外一种就是分别传入两个变量,一个存输入,一个存输出。至于为啥这样写,不知道,节约内存?
//后续只会分析一种情况,分别传入两个变量,一个存输入,一个存输入。这里以前文网络中的第二层Convolution层为例。在line 677-line 690中,layer->forward()就是执行具体层的计算。
ncnn::Convolution::forward()
//这里只分析kernel为1*1*1的这种卷积
//这里构造了一个InnerProduct层操作,这里简短的几句话,其实就是我前面几篇文章中的部分内容,load_param做了什么,load_model做了什么
ncnn::InnerProduct::forward()
这里核心是计算两个向量的内积。
到这里为止,一个卷积操作就完成了。然后forward_layer会从递归中一级级返回,最后得到我们需要的那一层的值。
后记
无
参考文献
- 无
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。