ncnn源码阅读(四)----模型推理过程

模型推理过程

推理过程主要涉及两个类:Net、Extractor,在示例中的使用如下:

 	ncnn::Net squeezenet;
    squeezenet.load_param("squeezenet_v1.1.param");
    squeezenet.load_model("squeezenet_v1.1.bin");

    ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, bgr.cols, bgr.rows, 227, 227);

    const float mean_vals[3] = {104.f, 117.f, 123.f};
    in.substract_mean_normalize(mean_vals, 0);
	
	//利用Net得到Extractor类的实例
    ncnn::Extractor ex = squeezenet.create_extractor();
    //为Extractor设置模式和输入数据
    ex.set_light_mode(true);
    ex.input("data", in);
	
	//通过extract得到推理结果
    ncnn::Mat out;
    ex.extract("prob", out);

    cls_scores.resize(out.c);
    for (int j=0; j<out.c; j++)
    {
        const float* prob = out.data + out.cstep * j;
        cls_scores[j] = prob[0];
    }

    return 0;

ncnn中的调用逻辑

  • 利用net的实例和blob的数量创建Extractor实例
Extractor Net::create_extractor() const
{
    return Extractor(this, blobs.size());
}

Extractor的构造函数:

Extractor::Extractor(const Net* _net, int blob_count) : net(_net)
{
    blob_mats.resize(blob_count);
    lightmode = false;
    num_threads = 0;
}

blob_mats存储所有blob的数据内容,它的定义为std::vector<Mat> blob_mats;
Extractor类中的set_light_mode方法主要用来修改类中的lightmode成员变量,input方法则是对对应的blob进行赋值:

int Extractor::input(const char* blob_name, const Mat& in)
{
	//根据blob的名字得到在blob集合中的索引
    int blob_index = net->find_blob_index_by_name(blob_name);
    if (blob_index == -1)
        return -1;
	//将对应的输入数据赋值给对应索引的Mat
    blob_mats[blob_index] = in;
    return 0;
}

在Extractor类中的blob_mats是这个类共享的,而Net类中的blob是数据节点。blob_mats中存储的是每个数据节点的具体数值,与Net类中的blob依次对应。
使用Extractor类中的extract方法,可以根据blob名字得到对应blob的数据,具体逻辑就是根据blob名字找到对应的index,然后根据index将blob_mats中Mat进行返回。

int Extractor::extract(const char* blob_name, Mat& feat)
{
	//根据名字得到对应的索引
    int blob_index = net->find_blob_index_by_name(blob_name);
    if (blob_index == -1)
        return -1;

    int ret = 0;
	
	//根据索引判断blob_mats中对应的数据是否有效,如果没有效,就执行推理,得到填充后的blob_mats
    if (blob_mats[blob_index].dims == 0)
    {
        int layer_index = net->blobs[blob_index].producer;
        ret = net->forward_layer(layer_index, blob_mats, lightmode);
    }
	//根据索引返回对应位置的Mat
    feat = blob_mats[blob_index];

    return ret;
}

递归调用实现网络逐层的执行

根据上面的extract方法,可以看到,对blob_mats填充的过程由Net类中的forward_layer来完成,所以需要重点看一下这个推理的过程,也就是下面这段代码的具体实现:

 if (blob_mats[blob_index].dims == 0)
 {
     int layer_index = net->blobs[blob_index].producer;
     ret = net->forward_layer(layer_index, blob_mats, lightmode);
 }

上述分支中表达的意思是:根据blob名字得到索引,然后根据索引在blob_mats获取的数据是无效数据,所以需要进行网络的前向计算得到当前blob数据节点的具体数据。
首先,根据blob的produce可以知道前blob的数据应该由网络的哪一层产生;为了减少计算量,并不需要推理完成所有的网络,只要计算到当前需要的blob的那一层即可,所以在前向计算的时候,需要知道网络中目标层的索引,以及运行的模式。
基于上述的过程,int layer_index = net->blobs[blob_index].producer;就是获得目标层的索引,然后将目标层的索引,和存储数据节点数据的blob_mats以及运行模式lightmode,告诉前向推理。也就是这行代码:ret = net->forward_layer(layer_index, blob_mats, lightmode);下面将详细分析一下Net类中的forward_layer方法:

  • 根据layer_index也就是层的索引,获取层的实例;
const Layer* layer = layers[layer_index];
  • 根据层是不是单输入,单输出,进行分支处理;
    • 是单输入、单输出的层
      1、首先获取输入和输出blob的index;
      2、根据索引从blob_mats中取得输入的blob;
      3、根据输入blob的dims判断输入数据是否有效;
      4、如果无效,则利用当前blob的producer,递归调用当前forward_layer函数;
      5、如果有效,则取出当前层的输入blob;
      lightmode表示轻量级模式在网络推理中会不断地进行垃圾回收;
      根据lightmode为false,则执行逻辑相对简单:
      6、调用层的forward函数,将输入的blob输入,得到输出的blob,然后再将输出的blob对blob_mats相应的位置赋值;
      如果lightmode为true:
      将blob_mats中对应输入blob位置的mat进行release();后面的操作或者inplace计算中,也要保证使用Mat的独立性,方便资源的释放。
if (layer->one_blob_only)
{
    // load bottom blob
    int bottom_blob_index = layer->bottoms[0];
    int top_blob_index = layer->tops[0];

    if (blob_mats[bottom_blob_index].dims == 0)
    {
        int ret = forward_layer(blobs[bottom_blob_index].producer, blob_mats, lightmode);
        if (ret != 0)
            return ret;
    }

    Mat bottom_blob = blob_mats[bottom_blob_index];

    if (lightmode)
    {
        // delete after taken in light mode
        blob_mats[bottom_blob_index].release();
        // deep copy for inplace forward if data is shared
        if (layer->support_inplace && *bottom_blob.refcount != 1)
        {
            bottom_blob = bottom_blob.clone();
        }
    }

    // forward
    if (lightmode && layer->support_inplace)
    {
        Mat& bottom_top_blob = bottom_blob;
        int ret = layer->forward_inplace(bottom_top_blob);
        if (ret != 0)
            return ret;

        // store top blob
        blob_mats[top_blob_index] = bottom_top_blob;
    }
    else
    {
        Mat top_blob;
        int ret = layer->forward(bottom_blob, top_blob);
        if (ret != 0)
            return ret;

        // store top blob
        blob_mats[top_blob_index] = top_blob;
    }

}
- 不是单输入、单输出的层

1、根据当前layer的输入数量多少,定义一个存储输入blob的Mat的vector;
2、根据输入数量的多少进行遍历,每次遍历进行的操作与单输入单输出的流程相同;

 std::vector<Mat> bottom_blobs;
 bottom_blobs.resize(layer->bottoms.size());
 for (size_t i=0; i<layer->bottoms.size(); i++)
 {
     int bottom_blob_index = layer->bottoms[i];

     if (blob_mats[bottom_blob_index].dims == 0)
     {
         int ret = forward_layer(blobs[bottom_blob_index].producer, blob_mats, lightmode);
         if (ret != 0)
             return ret;
     }

     bottom_blobs[i] = blob_mats[bottom_blob_index];

     if (lightmode)
     {
         // delete after taken in light mode
         blob_mats[bottom_blob_index].release();
         // deep copy for inplace forward if data is shared
         if (layer->support_inplace && *bottom_blobs[i].refcount != 1)
         {
             bottom_blobs[i] = bottom_blobs[i].clone();
         }
     }
 }

3、forward的过程与单输入单输出的流程相似,在输出的时候需要通过遍历赋值

 // forward
 if (lightmode && layer->support_inplace)
  {
      std::vector<Mat>& bottom_top_blobs = bottom_blobs;
      int ret = layer->forward_inplace(bottom_top_blobs);
      if (ret != 0)
          return ret;

      // store top blobs
      for (size_t i=0; i<layer->tops.size(); i++)
      {
          int top_blob_index = layer->tops[i];

          blob_mats[top_blob_index] = bottom_top_blobs[i];
      }
  }
  else
  {
      std::vector<Mat> top_blobs;
      top_blobs.resize(layer->tops.size());
      int ret = layer->forward(bottom_blobs, top_blobs);
      if (ret != 0)
          return ret;

      // store top blobs
      for (size_t i=0; i<layer->tops.size(); i++)
      {
          int top_blob_index = layer->tops[i];

          blob_mats[top_blob_index] = top_blobs[i];
      }
  }

完整的多输入多输出的代码如下:

 // load bottom blobs
 std::vector<Mat> bottom_blobs;
 bottom_blobs.resize(layer->bottoms.size());
 for (size_t i=0; i<layer->bottoms.size(); i++)
 {
     int bottom_blob_index = layer->bottoms[i];

     if (blob_mats[bottom_blob_index].dims == 0)
     {
         int ret = forward_layer(blobs[bottom_blob_index].producer, blob_mats, lightmode);
         if (ret != 0)
             return ret;
     }

     bottom_blobs[i] = blob_mats[bottom_blob_index];

     if (lightmode)
     {
         // delete after taken in light mode
         blob_mats[bottom_blob_index].release();
         // deep copy for inplace forward if data is shared
         if (layer->support_inplace && *bottom_blobs[i].refcount != 1)
         {
             bottom_blobs[i] = bottom_blobs[i].clone();
         }
     }
 }

 // forward
 if (lightmode && layer->support_inplace)
 {
     std::vector<Mat>& bottom_top_blobs = bottom_blobs;
     int ret = layer->forward_inplace(bottom_top_blobs);
     if (ret != 0)
         return ret;

     // store top blobs
     for (size_t i=0; i<layer->tops.size(); i++)
     {
         int top_blob_index = layer->tops[i];

         blob_mats[top_blob_index] = bottom_top_blobs[i];
     }
 }
 else
 {
     std::vector<Mat> top_blobs;
     top_blobs.resize(layer->tops.size());
     int ret = layer->forward(bottom_blobs, top_blobs);
     if (ret != 0)
         return ret;

     // store top blobs
     for (size_t i=0; i<layer->tops.size(); i++)
     {
         int top_blob_index = layer->tops[i];

         blob_mats[top_blob_index] = top_blobs[i];
     }
 }

猜你喜欢

转载自blog.csdn.net/qq_25105061/article/details/131761864