Caffe学习(六):Caffe 添加自定义层

这里我们以mnist example为例来说明,如何在网络中添加自定义的Caffe层(C++ 实现):

1. 不带参数的层

(1)网络文件添加自定义层

在caffe-master\examples\mnist\下拷贝lenet_train_test.prototxt,重命名为lenet_train_test_new.prototxt

在第1个卷积层,即conv1层前添加一个自定义层,层名为NewLayer,层类型为New,如下:

layer {
  name:"NewLayer"
  type:"New"
  bottom: "data"
  top:"data"
}

这里我们定义了一个最精简的层,该层没有配置参数,data从bottom进来,又从top出去,没做任何其他操作。由于这个层没有配置参数,因此仅在Caffe中添加hpp文件及cpp文件即可,不需要修改caffe.proto文件

(2)添加hpp文件

在Caffe的VS工程里, libCaffe——inclulde——layers下添加一个new_layer.hpp文件,文件的保存路径为:caffe-master\include\caffe\layers,代码如下:

#ifndef CAFFE_NEW_LAYER_HPP_
#define CAFFE_NEW_LAYER_HPP_

#include <vector>

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

namespace caffe {
	template <typename Dtype>
	class NewLayer : public Layer<Dtype> {
	public:
		explicit NewLayer(const LayerParameter& param)
			: Layer<Dtype>(param) {}
		virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
			const vector<Blob<Dtype>*>& top){};
		virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
			const vector<Blob<Dtype>*>& top){};

	protected:
		virtual void Forward_cpu(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 Forward_gpu(const vector<Blob<Dtype>*>& bottom,
			const vector<Blob<Dtype>*>& top){};
		virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
			const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom){};
	};

} 

#endif 

(3)添加cpp文件

在Caffe的VS工程里, libCaffe——src——layers下添加一个new_layer.cpp文件,文件的保存路径为:caffe-master\src\caffe\layers,代码如下:

#include <vector>
#include "caffe/layers/new_layer.hpp"

namespace caffe {

template <typename Dtype>
void NewLayer<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];
	}
}

template <typename Dtype>
void NewLayer<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];
		}
	}
}

#ifdef CPU_ONLY
	STUB_GPU(NewLayer);
#endif

INSTANTIATE_CLASS(NewLayer);  //类名,注:这个类名与prototxt文件中的层名不需一致
REGISTER_LAYER_CLASS(New); // 对应层的类型

}  // namespace caffe

以上即完成新层的添加,然后编译caffe项目(不用编译整个solution, 仅编译project, 编译caffe时会自动先编译libcaffe),在\caffe-master\Build\x64\Release下即生成新的caffe.lib及caffe.exe文件,然后对以上新的lenet_train_test_new.prototxt进行训练或测试(lenet_solver.prototxt文件中net指定的路径也需要修改),均可执行成功

2. 添加带参数的层

(1)添加带参数自定义层

这里我们添加带参数的层,即将上面的NewLayer层改写如下,增加了参数集合new_param,其中包含参数coeff1与coeff2:

layer {
  name:"NewLayer"
  type:"New"
  bottom: "data"
  top:"data"
    new_param {
    coeff1: 1.0
	coeff2: 2.0
  }
}

new_layer.cpp增加获取参数的语句,如下:

#include <vector>
#include "caffe/layers/new_layer.hpp"

namespace caffe {

template <typename Dtype>
void NewLayer<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();
	
	float coeff = this->layer_param_.new_param().coeff1(); // 获取参数
	LOG(INFO) << "NewLayer, Forward_cpu:" << coeff;

	for (int i = 0; i < count; ++i) {
			top_data[i] = bottom_data[i];
	}
}

template <typename Dtype>
void NewLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
	const vector<bool>& propagate_down,
	const vector<Blob<Dtype>*>& bottom) {

	float coeff = this->layer_param_.new_param().coeff2();	// 获取参数
	LOG(INFO) << "NewLayer, Backward_cpu:" << coeff;

	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];
		}
	}
}

#ifdef CPU_ONLY
	STUB_GPU(NewLayer);
#endif

INSTANTIATE_CLASS(NewLayer);  //类名,对应prototxt文件中的层名
REGISTER_LAYER_CLASS(New); // 对应层的类型

}  // namespace caffe

(2)修改caffe.proto

修改caffe-master\src\caffe\proto\caffe.proto,有两处修改点:

在message LayerParameter下添加:

  optional NewParameter new_param = 151;

注意设定的ID值不能与其他已设置的有相同,而message LayerParameter上面注明了可以设定的、没有冲突的ID值。

再在其他位置添加一个NewLayer层的message函数:

message NewParameter{
	optional float coeff1 = 1 [default = 1];
	optional float coeff2 = 2 [default = 2];
}

注意各个文件中参数的命名要匹配一致,如下:

修改好后,在VS中编译caffe.exe, 编译过程中caffe工程会根据caffe.proto生成新的caffe.pb.c与caffe.pb.h文件,分别位于caffe-master\src\caffe\proto\目录与caffe-master\include\caffe\proto\目录下,如果发现这两个文件没有更新,则可能是caffe.ptoto文件编写出错,例如,我一开始将optional float coeff1 = 1 [default = 1]中的 "default" 写成了 "defalut",导致一直不生成新的caffe.pb.c与caffe.pb.h文件,caffe也无法编译成功,检查很久后才发现问题所在。

在caffe-master目录下运行test_mnist.bat文件,文件内容:

.\Build\x64\Release\caffe.exe test -model=examples\mnist\lenet_train_test_new.prototxt -weights=examples\mnist\mnist_data\lenet_iter_10000.caffemodel -iterations=100
pause

输出如下,可以看到打印出了NewLayer层的LOG输出,即表示添加新层成功。

编译caffe.proto无法生成新的caffe.pb.c与caffe.pb.h的问题

如果无法生成caffe.pb.c与caffe.pb.h,那就是caffe.proto文件有错误,可以用protobuf工具检查文件哪里出错:

到以下路径下载protubuf,我这里下载 protoc-3.9.0-rc-1-win32.zip

https://github.com/protocolbuffers/protobuf/releases

下载解压,将bin\protoc.exe添加到系统环境变量PATH中

在caffe-master\src\caffe\proto\目录下创建bat文件,如下:

protoc caffe.proto --cpp_out=./
protoc caffe.proto --python_out=./
md ..\..\..\python\caffe\proto\
copy /y .\caffe_pb2.py ..\..\..\python\caffe\proto\
copy nul ..\..\..\python\caffe\proto\__init__.py
pause

双击运行,如果caffe.proto文件没有错误,会在caffe.proto的目录下生成caffe.pb.c及caffe.proto.h文件,如果错误,会显示出错的地方。

发布了40 篇原创文章 · 获赞 51 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/zh8706/article/details/95320367