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文件,如果錯誤,會顯示出錯的地方。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章