前面一篇講述瞭如何添加自定義的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[:]