转载链接:http://withwsf.github.io/2016/04/14/Caffe-with-Python-Layer/
Caffe通过Boost中的Boost.Python模块来支持使用Python定义Layer:
- 使用C++增加新的Layer繁琐、耗时而且很容易出错
- 开发速度与执行速度之间的trade-off
编译支持Python Layer的Caffe
如果是首次编译,修改Caffe根目录下的Makefile.cinfig,uncomment
如果已经编译过
1 2
| make clean WITH_PYTHON_LAYER=1 make&& make pycaffe
|
使用Python Layer
在网络的prototxt文件中添加一个Python定义的loss层如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| layer{ type: ’Python' name: 'loss' top: 'loss' bottom: ‘ipx’ bottom: 'ipy' python_param{ #module的名字,通常是定义Layer的.py文件的文件名,需要在$PYTHONPATH下 module: 'pyloss' #layer的名字---module中的类名 layer: 'EuclideanLossLayer' } loss_weight: 1 }
|
定义Python Layer
根据上面的要求,我们在$PYTHONPAT在创建pyloss.py,并在其中定义EuclideanLossLayer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import caffe import numpy as np class EuclideadLossLayer(caffe.Layer):#EuclideadLossLayer没有权值,反向传播过程中不需要进行权值的更新。如果需要定义需要更新自身权值的层,最好还是使用C++ def setup(self,bottom,top): #在网络运行之前根据相关参数参数进行layer的初始化 if len(bottom) !=2: raise exception("Need two inputs to compute distance") def reshape(self,bottom,top): #在forward之前调用,根据bottom blob的尺寸调整中间变量和top blob的尺寸 if bottom[0].count !=bottom[1].count: raise exception("Inputs must have the same dimension.") self.diff=np.zeros_like(bottom[0].date,dtype=np.float32) top[0].reshape(1) def forward(self,bottom,top): #网络的前向传播 self.diff[...]=bottom[0].data-bottom[1].data top[0].data[...]=np.sum(self.diff**2)/bottom[0].num/2. def backward(self,top,propagate_down,bootm): #网络的前向传播 for i in range(2): if not propagate_down[i]: continue if i==0: sign=1 else: sign=-1 bottom[i].diff[...]=sign*self.diff/bottom[i].num
|
原理浅析
阅读caffe源码pythonlayer.hpp可以知道,类PythonLayer继承自Layer,并且新增私有变量boost::python::object self来表示我们自己定义的python layer的内存对象。
类PythonLayer类的成员函数LayerSetUP, Reshape, Forward_cpu和Backward_cpu分别是对我们自己定义的python layer中成员函数setup, reshape, forward和backward的封装调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class PythonLayer : public Layer<Dtype> { public: PythonLayer(PyObject* self, const LayerParameter& param) : Layer<Dtype>(param), self_(bp::handle<>(bp::borrowed(self))) { } virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { // Disallow PythonLayer in MultiGPU training stage, due to GIL issues // Details: https://github.com/BVLC/caffe/issues/2936 if (this->phase_ == TRAIN && Caffe::solver_count() > 1 && !ShareInParallel()) { LOG(FATAL) << "PythonLayer is not implemented in Multi-GPU training"; } self_.attr("param_str") = bp::str( this->layer_param_.python_param().param_str()); self_.attr("setup")(bottom, top); } virtual void Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { self_.attr("reshape")(bottom, top); } virtual inline bool ShareInParallel() const { return this->layer_param_.python_param().share_in_parallel(); } virtual inline const char* type() const { return "Python"; } protected: virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { self_.attr("forward")(bottom, top); } virtual void Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { self_.attr("backward")(top, propagate_down, bottom); } private: bp::object self_; }; |
整体流程大致为:首先从文件中读入solver并生成一个solver,然后根据solver的net路径生成一
个net,net调用layer_factory循环生成每个层,最后根据读入model或是filler来初始化参数。从上面的流程可以知道layer_factory是循环生成每个层,我看.cpp文件也的确写了#if
WITH_PYTHON_LAYER,然后有什么什么操作,比如储存python
layer的python_param,并调用setup,这里实际上已经是利用boost进行C++
Python混编了。这些操作的定义就在python_layer.hpp文件中。
layer_factory中python
layer的setup相关具体操作是,先根据param找到module的位置,再加载module,再根据层名加载层,然后前向计算反向计算什么的。
这些就已经算是达到目的了。不过只是知道相对路径,怎么可能加载成功呢?然后又继续找啊找,终于在faster
rcnn的tools文件中找到。_init_paths里有写一些预操作,比如将lib路径写入PYTHONPATH中,当然如果写入的话,这样就可以
直接加载了。