【深夜福利】Caffe 增加自定义 Layer 及其 ProtoBuffer 参数

博客内容基于新书《深度学习:21 天实战 Caffe》,书中课后习题答案欢迎读者留言讨论。以下进入正文。

在使用 Caffe 过程中经常会有这样的需求:已有 Layer 不符合我的应用场景;我需要这样这样的功能,原版代码没有实现;或者已经实现但效率太低,我有更好的实现。

方案一:简单粗暴的解法——偷天换日

如果你对ConvolutionLayer 的实现不满意,那就直接改这两个文件:$CAFFE_ROOT/include/caffe/layers/conv_layer.hpp 和 $CAFFE_ROOT/src/caffe/layers/conv_layer.cpp 或 conv_layer.cu ,将 im2col + gemm 替换为你自己的实现(比如基于 winograd 算法的实现)。

优点:快速迭代,不需要对 Caffe 框架有过多了解,糙快狠准。

缺点:代码难维护,不能 merge 到 caffe master branch,容易给使用代码的人带来困惑(效果和 #define TRUE false 差不多)。

方案二:稍微温柔的解法——千人千面

和方案一类似,只是通过预编译宏来确定使用哪种实现。例如可以保留ConvolutionLayer 默认实现,同时在代码中增加如下段:

#ifdef SWITCH_MY_IMPLEMENTATION// 你的实现代码#else// 默认代码#endif这样可以在需要使用该 Layer 的代码中,增加宏定义:

#define SWITCH_MY_IMPLEMENTATION

就可以使用你的实现。而未定义该宏的代码,仍然使用原版实现。

优点:可以在新旧实现代码之间灵活切换;

缺点:每次切换需要重新编译;

方案三:优雅转身——山路十八弯

同一个功能的 Layer 有不同实现,希望能灵活切换又不需要重新编译代码,该如何实现?

这时不得不使用 ProtoBuffer 工具了。

首先,要把你的实现,要像正常的 Layer 类一样,分解为声明部分和实现部分,分别放在 .hpp 与 .cpp、.cu 中。Layer 名称要起一个能区别于原版实现的新名称。.hpp 文件置于$CAFFE_ROOT/include/caffe/layers/,而 .cpp 和 .cu 置于$CAFFE_ROOT/src/caffe/layers/,这样你在 $CAFFE_ROOT 下执行 make 编译时,会自动将这些文件加入构建过程,省去了手动设置编译选项的繁琐流程。

其次,在$CAFFE_ROOT/src/caffe/proto/caffe.proto 中,增加新 LayerParameter 选项,这样你在编写 train.prototxt 或者 test.prototxt 或者 deploy.prototxt 时就能把新 Layer 的描述写进去,便于修改网络结构和替换其他相同功能的 Layer 了。

最后也是最容易忽视的一点,在 Layer 工厂注册新 Layer 加工函数,不然在你运行过程中可能会报如下错误:

F1002 01:51:22.656038 1954701312 layer_factory.hpp:81] Check failed: registry.count(type) == 1 (0 vs. 1) Unknown layer type: AllPass (known types: AbsVal, Accuracy, ArgMax, BNLL, BatchNorm, BatchReindex, Bias, Concat, ContrastiveLoss, Convolution, Crop, Data, Deconvolution, Dropout, DummyData, ELU, Eltwise, Embed, EuclideanLoss, Exp, Filter, Flatten, HDF5Data, HDF5Output, HingeLoss, Im2col, ImageData, InfogainLoss, InnerProduct, Input, LRN, Log, MVN, MemoryData, MultinomialLogisticLoss, PReLU, Pooling, Power, ReLU, Reduction, Reshape, SPP, Scale, Sigmoid, SigmoidCrossEntropyLoss, Silence, Slice, Softmax, SoftmaxWithLoss, Split, TanH, Threshold, Tile, WindowData)*** Check failure stack trace: ***@0x10243154e google::LogMessage::Fail()@0x102430c53 google::LogMessage::SendToLog()@0x1024311a9 google::LogMessage::Flush()@0x1024344d7 google::LogMessageFatal::~LogMessageFatal()@0x10243183b google::LogMessageFatal::~LogMessageFatal()@0x102215356 caffe::LayerRegistry<>::CreateLayer()@0x102233ccf caffe::Net<>::Init()@0x102235996 caffe::Net<>::Net()@0x102118d8b time()@0x102119c9a main@0x7fff851285ad start@0x4 (unknown)Abort trap: 6

下面给出一个实际案例,走一遍方案三的流程。

这里我们实现一个新 Layer,名称为 AllPassLayer,顾名思义就是全通 Layer,“全通”借鉴于信号处理中的全通滤波器,将信号无失真地从输入转到输出。

虽然这个 Layer 并没有什么卵用,但是在这个基础上增加你的处理是非常简单的事情。另外也是出于实验考虑,全通层的 Forward/Backward 函数非常简单不需要读者有任何高等数学和求导的背景知识。读者使用该层时可以插入到任何已有网络中,而不会影响训练、预测的准确性。

首先看头文件:

#ifndef CAFFE_ALL_PASS_LAYER_HPP_#define CAFFE_ALL_PASS_LAYER_HPP_#include <vector>#include "caffe/blob.hpp"#include "caffe/layer.hpp"#include "caffe/proto/caffe.pb.h"#include "caffe/layers/neuron_layer.hpp"namespace caffe {template <typename Dtype>class AllPassLayer : public NeuronLayer<Dtype> { public: explicit AllPassLayer(const LayerParameter& param): NeuronLayer<Dtype>(param) {} virtual inline const char* type() const { return "AllPass"; } protected: 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); 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);};} // namespace caffe#endif // CAFFE_ALL_PASS_LAYER_HPP_

再看源文件:

#include <algorithm>#include <vector>#include "caffe/layers/all_pass_layer.hpp"#include <iostream>using namespace std;#define DEBUG_AP(str) cout<<str<<endlnamespace caffe {template <typename Dtype>void AllPassLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) { const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); const int count = bottom[0]->count(); for (int i = 0; i < count; ++i) {top_data[i] = bottom_data[i]; } DEBUG_AP("Here is All Pass Layer, forwarding."); DEBUG_AP(this->layer_param_.all_pass_param().key());}template <typename Dtype>void AllPassLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom) { if (propagate_down[0]) {const Dtype* bottom_data = bottom[0]->cpu_data();const Dtype* top_diff = top[0]->cpu_diff();Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();const int count = bottom[0]->count();for (int i = 0; i < count; ++i) {bottom_diff[i] = top_diff[i];} } DEBUG_AP("Here is All Pass Layer, backwarding."); DEBUG_AP(this->layer_param_.all_pass_param().key());}#ifdef CPU_ONLYSTUB_GPU(AllPassLayer);#endifINSTANTIATE_CLASS(AllPassLayer);REGISTER_LAYER_CLASS(AllPass);} // namespace caffe

时间考虑,我没有实现 GPU 模式的 forward、backward,故本文例程仅支持 CPU_ONLY 模式。

编辑 caffe.proto,找到 LayerParameter 描述,增加一项:

每个人心中,都会有一个古镇情怀,流水江南,烟笼人家。

【深夜福利】Caffe 增加自定义 Layer 及其 ProtoBuffer 参数

相关文章:

你感兴趣的文章:

标签云: