《TencentNCNN系列》 之工作原理简要解析(以LeNet-5为例)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011728480/article/details/81108982

#PS:要转载请注明出处,本人版权所有

#PS:这个只是 《 我自己 》理解,如果和你的

#原则相冲突,请谅解,勿喷

时间: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:要转载请注明出处,本人版权所有.

有问题请留言,看到后我会第一时间回复

猜你喜欢

转载自blog.csdn.net/u011728480/article/details/81108982