【caffe实用技巧与bug排查】三步添加新layer及可能出现的bug

来源:互联网 发布:华为交换机端口模式 编辑:程序博客网 时间:2024/05/20 15:37

【caffe实用技巧与bug排查】caffe添加新层(添加layer)及可能出现的bug

标签(空格分隔): caffe layer

作者:贾金让

第一部分:三步添加新layer

在caffe中添加新的layer是一件非常必要的事,而且通过添加新的层,还可以增加对caffe底层框架实现的了解。
caffe新增layer具体来说只有三步
1.在caffe_root/include/caffe/layers/文件夹下添加your_layer.hpp头文件;
2.在caffe_root/src/caffe/layers/文件夹下添加your_layer.cpp(以及your_layer.cu)。后缀为.cu的文件是用gou实现你的层的源代码,需要会一些cuda编程;
3.如果你添加的层需要参数,则需要在caffe_root/src/caffe/proto/caffe.proto中新增关于你的参数的声明和定义。如果你添加的层没有参数,则不需要在caffe.proto中做任何改变。

第二部分:跟随例子说明如何执行上面三步

比如我新增了一个类型为MyLoss的层,它要实现的功能是计算欧氏距离loss。
1.首先写出我的my_loss_layer.hpp

#ifndef CAFFE_MY_LOSS_LAYER_HPP_#define CAFFE_MY_LOSS_LAYER_HPP_#include <vector>#include "caffe/blob.hpp"#include "caffe/layer.hpp"#include "caffe/proto/caffe.pb.h"#include "caffe/layers/loss_layer.hpp"namespace caffe {/** * @brief Computes the Euclidean (L2) loss @f$ *          E = \frac{1}{2N} \sum\limits_{n=1}^N \left| \left| \hat{y}_n - y_n *        \right| \right|_2^2 @f$ for real-valued regression tasks. * * @param bottom input Blob vector (length 2) *   -# @f$ (N \times C \times H \times W) @f$ *      the predictions @f$ \hat{y} \in [-\infty, +\infty]@f$ *   -# @f$ (N \times C \times H \times W) @f$ *      the targets @f$ y \in [-\infty, +\infty]@f$ * @param top output Blob vector (length 1) *   -# @f$ (1 \times 1 \times 1 \times 1) @f$ *      the computed Euclidean loss: @f$ E = *          \frac{1}{2n} \sum\limits_{n=1}^N \left| \left| \hat{y}_n - y_n *        \right| \right|_2^2 @f$ * * This can be used for least-squares regression tasks.  An InnerProductLayer * input to a EuclideanLossLayer exactly formulates a linear least squares * regression problem. With non-zero weight decay the problem becomes one of * ridge regression -- see src/caffe/test/test_sgd_solver.cpp for a concrete * example wherein we check that the gradients computed for a Net with exactly * this structure match hand-computed gradient formulas for ridge regression. * * (Note: Caffe, and SGD in general, is certainly \b not the best way to solve * linear least squares problems! We use it only as an instructive example.) */template <typename Dtype>class MyLossLayer : public LossLayer<Dtype> { public:  explicit MyLossLayer(const LayerParameter& param)      : LossLayer<Dtype>(param), diff_() {}  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,      const vector<Blob<Dtype>*>& top);  virtual inline const char* type() const { return "MyLoss"; }  /**   * Unlike most loss layers, in the EuclideanLossLayer we can backpropagate   * to both inputs -- override to return true and always allow force_backward.   */  virtual inline bool AllowForceBackward(const int bottom_index) const {    return true;  } protected:  /// @copydoc EuclideanLossLayer  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,      const vector<Blob<Dtype>*>& top);  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,      const vector<Blob<Dtype>*>& top);  /**   * @brief Computes the Euclidean error gradient w.r.t. the inputs.   *   * Unlike other children of LossLayer, EuclideanLossLayer \b can compute   * gradients with respect to the label inputs bottom[1] (but still only will   * if propagate_down[1] is set, due to being produced by learnable parameters   * or if force_backward is set). In fact, this layer is "commutative" -- the   * result is the same regardless of the order of the two bottoms.   *   * @param top output Blob vector (length 1), providing the error gradient with   *      respect to the outputs   *   -# @f$ (1 \times 1 \times 1 \times 1) @f$   *      This Blob's diff will simply contain the loss_weight* @f$ \lambda @f$,   *      as @f$ \lambda @f$ is the coefficient of this layer's output   *      @f$\ell_i@f$ in the overall Net loss   *      @f$ E = \lambda_i \ell_i + \mbox{other loss terms}@f$; hence   *      @f$ \frac{\partial E}{\partial \ell_i} = \lambda_i @f$.   *      (*Assuming that this top Blob is not used as a bottom (input) by any   *      other layer of the Net.)   * @param propagate_down see Layer::Backward.   * @param bottom input Blob vector (length 2)   *   -# @f$ (N \times C \times H \times W) @f$   *      the predictions @f$\hat{y}@f$; Backward fills their diff with   *      gradients @f$   *        \frac{\partial E}{\partial \hat{y}} =   *            \frac{1}{n} \sum\limits_{n=1}^N (\hat{y}_n - y_n)   *      @f$ if propagate_down[0]   *   -# @f$ (N \times C \times H \times W) @f$   *      the targets @f$y@f$; Backward fills their diff with gradients   *      @f$ \frac{\partial E}{\partial y} =   *          \frac{1}{n} \sum\limits_{n=1}^N (y_n - \hat{y}_n)   *      @f$ if propagate_down[1]   */  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);  Blob<Dtype> diff_;};}  // namespace caffe#endif  // CAFFE_EUCLIDEAN_LOSS_LAYER_HPP_

这里没有在步骤上的特殊内容,需要注意的就是代码书写不要有错误。

2.接着写出my_loss_layer.cpp以及my_loss_layer.cu
首先是my_loss_layer.cpp

#include <vector>#include "caffe/layers/my_loss_layer.hpp"#include "caffe/util/math_functions.hpp"#include <iostream>  using namespace std;  #define DEBUG_AP(str) cout<<str<<endl  namespace caffe {template <typename Dtype>void MyLossLayer<Dtype>::Reshape(  const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {  LossLayer<Dtype>::Reshape(bottom, top);  CHECK_EQ(bottom[0]->count(1), bottom[1]->count(1))      << "Inputs must have the same dimension.";  diff_.ReshapeLike(*bottom[0]);}template <typename Dtype>void MyLossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,    const vector<Blob<Dtype>*>& top) {  int count = bottom[0]->count();  caffe_sub(      count,      bottom[0]->cpu_data(),      bottom[1]->cpu_data(),      diff_.mutable_cpu_data());  Dtype dot = caffe_cpu_dot(count, diff_.cpu_data(), diff_.cpu_data());  Dtype loss = dot / bottom[0]->num() / Dtype(2);  top[0]->mutable_cpu_data()[0] = loss; DEBUG_AP(this->layer_param_.my_loss_param().key());}template <typename Dtype>void MyLossLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  for (int i = 0; i < 2; ++i) {    if (propagate_down[i]) {      const Dtype sign = (i == 0) ? 1 : -1;      const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();      caffe_cpu_axpby(          bottom[i]->count(),              // count          alpha,                              // alpha          diff_.cpu_data(),                   // a          Dtype(0),                           // beta          bottom[i]->mutable_cpu_diff());  // b    }  }  DEBUG_AP(this->layer_param_.my_loss_param().key());}#ifdef CPU_ONLYSTUB_GPU(MyLossLayer);#endifINSTANTIATE_CLASS(MyLossLayer);REGISTER_LAYER_CLASS(MyLoss);}  // namespace caffe

这里有一个需要注意的地方,那就是代码的最后两段:

INSTANTIATE_CLASS(MyLossLayer);REGISTER_LAYER_CLASS(MyLoss);

作用是在layer_factory中注册你添加的层,所以,MyLoss这个名字就被注册进去了,在之后写deploy.prototxt时,你的这个层的名字就是MyLoss。注意INSTANTIATE后括号是层名加Layer,而REGISTER_LAYER_CLASS后括号里就只有层名。

接下来是my_loss_layer.cu

#include <vector>#include "caffe/layers/my_loss_layer.hpp"#include "caffe/util/math_functions.hpp"namespace caffe {template <typename Dtype>void MyLossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,    const vector<Blob<Dtype>*>& top) {  int count = bottom[0]->count();  caffe_gpu_sub(      count,      bottom[0]->gpu_data(),      bottom[1]->gpu_data(),      diff_.mutable_gpu_data());  Dtype dot;  caffe_gpu_dot(count, diff_.gpu_data(), diff_.gpu_data(), &dot);  Dtype loss = dot / bottom[0]->num() / Dtype(2);  top[0]->mutable_cpu_data()[0] = loss;}template <typename Dtype>void MyLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  for (int i = 0; i < 2; ++i) {    if (propagate_down[i]) {      const Dtype sign = (i == 0) ? 1 : -1;      const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();      caffe_gpu_axpby(          bottom[i]->count(),              // count          alpha,                              // alpha          diff_.gpu_data(),                   // a          Dtype(0),                           // beta          bottom[i]->mutable_gpu_diff());  // b    }  }}INSTANTIATE_LAYER_GPU_FUNCS(MyLossLayer);}  // namespace caffe

需要注意的除了编写代码时的错误,还有就是最后一句话不要忘记加上:

INSTANTIATE_LAYER_GPU_FUNCS(MyLossLayer);

3.比较容易凌乱的是第三步,添加参数。
打开caffe_root/src/caffe/proto/caffe.proto
里面记录了caffe中各个类的参数,我们需要做的,是找到message LayerParameter,并在里面添加上你新增的参数。什么是参数,注意,你的新增层的名字(类型)不是参数,也就是说如果你只是新增了一个层,那么不需要在这里做任何改变。为了举例子,我在MyLayer层中添加一个参数key。

// NOTE// Update the next available ID when you add a new LayerParameter field.//// LayerParameter next available layer-specific ID: 148 (last added: all_pass_param)![2017-09-15 17-15-09屏幕截图.png-7.1kB][1]message LayerParameter {  optional string name = 1; // the layer name  optional string type = 2; // the layer type  repeated string bottom = 3; // the name of each bottom blob  repeated string top = 4; // the name of each top blob  // The train / test phase for computation.  optional Phase phase = 10;

先放上最终的deploy.prototxt以说明key这个参数的具体意思.
2017-09-15 17-15-09屏幕截图.png-7.1kB
key是我们要添加的参数。而为了将key添加进来,我们也就需要定义一个my_loss_param参数,它可以理解成是MyLoss层的参数集合,所有要定义的参数都包含在my_loss_param下,只不过本例子只添加了一个参数key。所以,最终我们需要在caffe,proto中添加两个参数名称,即参数集合名称my_loss_param和添加的参数名称key。
如下代码所示,首先在LayerParameter中添加参数集合名称my_loss_param。注意编号不要重复,同一个message下的各个参数对应唯一的编号,我将my_loss_param编号为147。而optional MyLossParameter表示my_loss_param是MyLoss层的参数集合名称。

  optional SoftmaxParameter softmax_param = 125;  optional SPPParameter spp_param = 132;  optional SliceParameter slice_param = 126;  optional TanHParameter tanh_param = 127;  optional ThresholdParameter threshold_param = 128;  optional TileParameter tile_param = 138;  optional WindowDataParameter window_data_param = 129;  optional MyLossParameter my_loss_param = 147;  }

参数集合名称定义完后,就可以定义具体的参数了。在LayerParameter外面写具体的参数定义,如下:

message MyLossParameter {  optional float key = 1 [default = 0];}

即定义了MyLoss层的参数key,如果还想定义其他参数,也可以继续在message MyLossParameter中添加。
同理,如果针对MyLoss层,你想定义多个不同的参数集合,每个参数集合下又有不同的参数,那就可以先在LayerParameter中定义多个参数集合名称。然后再外面分别声明每个参数集合名称下的参数即可。

至此,添加新layer的三步全部完成了。
重新编译caffe后,就可以使用新添加的层了。

第三部分:可能出现的问题与bug

1.心细的同学可能会发现,caffe.proto文件下还除了有message LayerParameter,还有message V1LayerParametermessage V0LayerParameter,那么后面两个有没有用呢?

答案是有用,但对于新定义层没有用。我们只需要在message LayerParameter中添加参数集合名称,之后在外面写好具体参数即可,不需要在后面两个message中做任何改动(网上有些教程说需要改动这两个,是错误的).后两个是旧版本中的参数定义的方式,随着caffe版本的更新,采用了新的方式,但估计作者和维护者觉得用新方式把所有参数定义重新弄一遍比较麻烦,所以旧的方式也没有被删除,而是通过caffe_root/src/caffe/util/upgrade_proto.cpp中的函数,将旧版本转化成新版本。所以,结论是,我们不需要改动它,但也不能删除。

2.有的同学发现,严格按照3步流程做完,然而编译时会出现提示未定义my_loss_param这个参数的错误。

我也遇到了这个问题,解决方式是严格仔细查找你在caffe.proto中做的一切改动,是否名字写错了,时候有大小写不对,时候有汉字格式的空格,是否标号重复等等。

3.最后,即使编译成功,使用train命令正式训练时提示 Aborted at 1457505270 (unix time) try "date -d @1457505270" if you are using GNU date 这种错误。

解决方法是将caffe_root/build/include/caffe/proto目录下的三个文件复制到caffe_root/include/caffe/proto目录下覆盖粘贴,之后重新编译caffe即可。