如何在Caffe中加入新的layer并利用Matlab进行调试
Caffe当中已经实现了很多常用的layer,但是在有些时候,通过单独使用或者组合这些已有的layer并没法实现我们想要的功能,这时便只能自己添加一个新的layer了,最新版Caffe添加一个layer主要步骤有四个:
- 在proto中添加layer相应的参数
- 编写实现layer的函数:hpp、cpp、cu
- 有些layer需要在layer_factory中进行装配
- layer的调试
下面以实现一个激活函数层为例,来完整地进行这样一个步骤,并用Matlab来进行调试。
我们要实现如下的激活函数:
很简单,由于要实现的是一个激活函数,因此可以参照caffe里面sigmoid激活函数的实现。
在proto中添加layer相应的参数
打开caffe/src/proto/caffe.proto,306行,可以看到如下:
// NOTE
// Update the next available ID when you add a new LayerParameter field.
//
// LayerParameter next available layer-specific ID: 139 (last added: batchnorm_param)
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
...
我们添加的是一个layer,因此便要在LayerParameter中添加我们写的Layer的Parameter,由于LayerParameter经常要改,因此前面标注了一个NOTE,来记录LayerParameter的更新情况,
// LayerParameter next available layer-specific ID: 139 (last added: batchnorm_param)
这句话的意思是,现在最新添加的layerparameter是batchnorm_param,ID是139,这是用来实现Batchnormlize用的。那我们要新添加layer的话,ID就应该是140,或者其他比139更大的数,防止与之前一定的Layer的ID冲突。
在往下,在大概355行左右,
// The default for the engine is set by the ENGINE switch at compile-time.
optional AccuracyParameter accuracy_param = 102;
optional ArgMaxParameter argmax_param = 103;
optional BatchNormParameter batch_norm_param = 139;
optional ConcatParameter concat_param = 104;
optional ContrastiveLossParameter contrastive_loss_param = 105;
optional ConvolutionParameter convolution_param = 106;
optional DataParameter data_param = 107;
optional DropoutParameter dropout_param = 108;
optional DummyDataParameter dummy_data_param = 109;
optional EltwiseParameter eltwise_param = 110;
optional EmbedParameter embed_param = 137;
optional ExpParameter exp_param = 111;
optional FlattenParameter flatten_param = 135;
optional HDF5DataParameter hdf5_data_param = 112;
optional HDF5OutputParameter hdf5_output_param = 113;
optional HingeLossParameter hinge_loss_param = 114;
optional ImageDataParameter image_data_param = 115;
optional InfogainLossParameter infogain_loss_param = 116;
optional InnerProductParameter inner_product_param = 117;
optional LogParameter log_param = 134;
optional LRNParameter lrn_param = 118;
optional MemoryDataParameter memory_data_param = 119;
optional MVNParameter mvn_param = 120;
optional PoolingParameter pooling_param = 121;
optional PowerParameter power_param = 122;
optional PReLUParameter prelu_param = 131;
optional PythonParameter python_param = 130;
optional ReductionParameter reduction_param = 136;
optional ReLUParameter relu_param = 123;
optional ReshapeParameter reshape_param = 133;
optional SigmoidParameter sigmoid_param = 124;
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;
这些便是Caffe之前所定义的一些Layer,可以看到sigmoid层是这么定义的,
optional SigmoidParameter sigmoid_param = 124;
那么在此,我们仿照这个定义方法,定义一个Shlu激活函数:
optional ShluParameter shlu_param = 140;
之前的NOTE表明,最新添加的Layer ID是139,因此我们添加Shlu_Layer,令其ID为140.
然后再往下,要定义Shlu_Layer所需要的参数,类似于Convolution_Layer需要设定kernel_size等,我们新添加的Layer也要设定所需要的参数,仿照SigmoidParameter的写法
message SigmoidParameter {
enum Engine {
DEFAULT = 0;
CAFFE = 1;
CUDNN = 2;
}
optional Engine engine = 1 [default = DEFAULT];
}
照抄一个ShluParameter,把CUDNN=2删掉,因为不编写CUDNN下的激活函数代码(封装好了的,无法修改),也就用不到CUDNN Engine。
message ShluParameter {
enum Engine {
DEFAULT = 0;
CAFFE = 1;
}
optional Engine engine = 1 [default = DEFAULT];
}
好了,到这一步,就基本完成了proto的修改,重新make一下即可。
编写实现layer的函数:hpp与cpp
修改完proto之后,便要实现我们要新加入的Shlu_Layer了,与Sigmoid类似,首先在caffe/include/layer/中加入shlu_layer.hpp,仿照sigmoid_layer.hpp即可,然后再caffe/src/layer/中加入shlu_layer.cpp,也是仿照sigmoid_layer.cpp,进行一些修改。
再次make,可能会有一些错误,按照错误信息调试修改一下就好了。
修改layer_factory.cpp
仿照layer_factory.cpp中sigmoid_layer的配置,进行shlu_layer的配置,记得添加对shlu_layer.hpp头文件的引用,并且删除其中CUDNN的部分。
make一下,如果通过,那shlu_layer就基本实现了,但还要具体测试一下。
利用MATLAB进行调试
Caffe的MATLAB接口可是个好东西,用MATLAB调试可以很方便地看到各种数据的形式以及结果。
我们这边需要调试自己实现的激活函数,shlu_layer。
第一步便是编写一个测试网络,具体如下:
name: "SHLUTEST"
input: "data"
input_dim: 1
input_dim: 1
input_dim: 100
input_dim: 100
# 测试后向过程必须加,不然回传的梯度都会是0
force_backward: true
layer {
name: "shlu1"
type: "Shlu" #这里的名字应该跟你之前定义的一致,要注意大小写
bottom: "data"
top: "shlu1"
}
编写的这个网络实现的便是输入数据维数1*1*100*100,通过shlu_layer。
接下来,打开Matlab,
cd caffe
matlab
编写代码如下:
addpath ./matlab
model = './shlu_test.prototxt';
caffe.set_mode_cpu();
# 测试gpu代码时请用GPU模式
#caffe.set_mode_gpu();
#caffe.set_device(gpu_id);
net = caffe.Net(model, 'test');
# 生成1*1*100*100维度的正态分布随机数,并填入'data'层的blobs
net.blobs('data').set_data(randn(net.blobs('data').shape));
# 前向过程
net.forward_prefilled();
# 检查生成的"res"是否是期望的结果
res = net.blobs('shlu1').get_data();
# 后向过程
# diff为自己设置的梯度值,保证维度一致
net.blobs('shlu1').set_diff(diff);
net.backward_prefilled();
# 检查生成的"data_diff"是否是期望的结果
data_diff = net.blobs('data').get_diff();
分别在cpu模式与gpu模式下都调试一遍,保证没有错误,再进行自己所需要的网络的整体配置。