前面一篇讲述了如何添加自定义的Caffe C++层,本篇讲解如何添加自定义的Python层,依然以mnist example为例子,在caffe-master\examples\mnist中的 lenet_train_test.ptototxt文件中,conv1层前添加以下测试层,这个层对网络不做任何修改,仅用于测试。
layer {
name: 'test'
type: 'Python'
bottom: 'data'
top:'data'
python_param {
module: 'test_layer' #与python文件名相同
layer: 'MyLayer' #与python文件中的类名相同
param_str: "'num_classes': 10"
}
}
这个层的类型为python,我们需要编写python文件来实现这个层的内容。在caffe-master\examples\mnist目录下创建一个文件夹python_layers文件夹,在python_layers文件夹中创建test_layer.py, 类名为MyLayer,如下,包含setup,reshape, forward, backward 4个函数,每个函数的输入输出都是bottom及top,对应prototxt文件的bottom及top,bottom与top均是Blob数据类型。
import caffe
class MyLayer(caffe.Layer):
#初始化时调用,检查输入的参数是否异常
def setup(self, bottom, top):
print 'setup'
#初始化时、前向传播时调用,用于设定参数的siaze
def reshape(self, bottom, top):
print 'reshape'
#前向传播时调用
def forward(self, bottom, top):
print 'forward'
#反向传播,如果本层不需要反向传播,则不调用
def backward(self, bootom, top):
print 'backward'
以上是最简单的写法,各个函数仅打印了函数的名。我们先不详细实现各个函数的功能,我们先运行这个Python层,先看能否运行成功。
在Caffe根目录下,即caffe-master\下创建train_mnist.py文件,注意这里要在caff-master目录下创建,因为mnist的*.prototxt文件中设置的路径是在examples开始。
import os.path as osp
import sys
import caffe
this_dir = osp.dirname(__file__)
layerpath = osp.join(this_dir, 'examples/mnist/python_layers')
sys.path.insert(0, layerpath) #将python文件的位置添加到环境变量
caffe.set_mode_gpu()
caffe.set_device(0)
solver = caffe.SGDSolver('./examples/mnist/lenet_solver.prototxt')
solver.solve()
运行以上python文件,通过打印的log可以看到在初始化时,'setup'和'reshape' 各打印了2次,然后在训练过程中,有多少次向前计算则‘reshape’与‘forward’就被调用多少次,而‘forward没有被调用’。通过实验测试,前向计算的次数为:
表示验证测试前向计算的次数,因为第0次时要做一次前向验证测试,因此要加1,后面的表示当max_iter大于等于test_interval时式子为1,否则为0。例如当max_iter为10000,test_interval为500,test_iter为100时,前向计算的次数为12101次。
下面逐步来实现各层。
setup 函数
def setup(self, bottom, top):
layer_params = yaml.load(self.param_str)
self._num_classes = layer_params['num_classes']
print 'setup, bottom len:', len(bottom), ', top len:', len(top), ', class num:', self._num_classes
注意这里要在py文件头部import yaml,这里获取了.prototxt文件设置的参数param_str。另外这里len(top)与len(bottom)表示输入输出的参数个数。如果层文件设置为:
layer {
name: 'test'
type: 'Python'
bottom: 'data'
bottom: 'label'
top:'data'
top:'label'
python_param {
module: 'test_layer'
layer: 'MyLayer'
param_str: "'num_classes': 10"
}
}
这里bottom和top的参数都有多个,通过序号的方式分别获取与设置bottom与top的参数,如下:
top[0].reshape(*bottom[0].data.shape) #data
top[1].reshape(*bottom[1].data.shape) # label
reshape 函数
这里reshape直接写作如下:
def reshape(self, bottom, top):
top[0].reshape(*bottom[0].data.shape) #data
forward函数
forward函数如下:
def forward(self, bottom, top):
top[0].data[...] = bottom[0].data[:]
backward函数
def backward(self, bootom, top):
for i in range(len(propagate_down)):
if not propagate_down[i]:
continue
bottom[i].diff[...] = top[i].diff[:]