【用Python學習Caffe】4. 設計自己的網路結構
4. 設計自己的網路結構
通過前文的例子,我們都知道了Caffe的網路都是一個prototxt的網路結構配置檔案定義的,該檔案可以用文字工具開啟,開啟後,我們可以看到如下結構:
layer { name: "data" type: "Data" top: "data" top: "label" transform_param { scale: 0.00390625 } data_param { source: "../../data/mnist/mnist_train_lmdb" batch_size: 64 backend: LMDB } } layer { name: "conv1" type: "Convolution" bottom: "data" top: "conv1" param { lr_mult: 1 decay_mult: 1 } param { lr_mult: 2 decay_mult: 0 } convolution_param { num_output: 20 pad: 0 kernel_size: 5 stride: 1 weight_filler { type: "xavier" } bias_filler { type: "constant" value: 0 } } } layer { name: "pool1" type: "Pooling" bottom: "conv1" top: "pool1" pooling_param { pool: MAX kernel_size: 2 stride: 2 } } ...
在prototxt檔案中,層都是用layer{}的結構表示,而裡面包含的層的引數可以在caffe.proto檔案中找到,比如說Data型別的結構由message DataParameter
所定義,Convolution
型別的結構由message ConvolutionParameter
所定義。
具體說明下:
name
表示該層的名稱type
表示該層的型別,如Data
或者Convolution
型別top
表示該層後接的Blob
結構的名稱bottom
表示該層前接的Blob
資料結構的名稱*_param
表示該層的引數,比如對於某卷積層conv1
來說,convolution_param
中
num_output
pad
表示卷積pad
kernel_size
表示卷積核的大小
對於如何使用層結構,一方面我們可以從model_libs.py
及caffenet.py
等檔案找到如何使用層結構的例子。另外可能參考這個教程,裡面給出caffe的所有層的Python函式使用例子。
另一方面我們可以參照caffe的C++類定義。在我們知道各層的描述後,可以將這些層結構拼接成我們自己的網路,手動在文字工具中直接編寫prototxt檔案。
但對於一些非常複雜的網路,很多情況下可能有成百上千的層。這樣的情況下,我們能難人工一層接一層的編寫prototxt檔案,而且還很容易出錯,因此可以通過python來直接自動生成結構檔案。
在caffe目錄下的example/pycaffe資料夾的caffenet.py給出瞭如何直接生成一個多層線性分類器的網路配置檔案prototxt檔案,而本節還將介紹一個直接生成mnist LeNet5網路的訓練、測試及實施的三個網路配置檔案prototxt。而我們將以後章節中介紹通過三個檔案完成LeNet5網路的訓練及測試任務。
Tips:可以將一些常用的網路結構打包成基礎體,比如在model_lib.py檔案中將VGG網路、AlexNet等常用網路打包成基礎網路。另外訓練、測試及實施的三個網路的中間結構是一致的,只是輸入層和最終輸出層是不一樣的,因此將可以中間相同層都用一個固定函式生成。
生成網路配置主要分為如下幾個部分:
4.1 配置基礎網路結構
這裡生成訓練、測試及實施的三個網路中都相同的網路中間部件
# 此函式生成LeNet5的主體結構 def lenet5_body(net, from_layer): # 網路引數 kwargs = { # param定義學習率,這裡是指基礎學習率step的情況,lt_mult乘以基礎學習率為實際學習率,為0表示權重不更新,decay_mult同權重衰減相關 'param': [dict(lr_mult=1, decay_mult=1), dict(lr_mult=2, decay_mult=0)], 'weight_filler': dict(type='xavier'), # 權重初始化模式 'bias_filler': dict(type='constant', value=0)} # 權重偏差初始化模式 # 判斷是否存在from_layer層 assert from_layer in net.keys() # conv1 net.conv1 = L.Convolution(net[from_layer], kernel_size=5, stride=1, num_output=20, pad=0, **kwargs) net.pool1 = L.Pooling(net.conv1, pool=P.Pooling.MAX, kernel_size=2, stride=2) net.conv2 = L.Convolution(net.pool1, kernel_size=5, stride=1, num_output=50, pad=0, **kwargs) net.pool2 = L.Pooling(net.conv2, pool=P.Pooling.MAX, kernel_size=2, stride=2) net.ip1 = L.InnerProduct(net.pool2, num_output=500, **kwargs) net.relu1 = L.ReLU(net.ip1, in_place=True) net.ip2 = L.InnerProduct(net.relu1, name='ip2', num_output=10, **kwargs)
4.2 構建整體網路
訓練、測試及實施的三個網路的結構有些許不同,主要集中在輸入層和輸出層。
# 訓練網路 train_net = caffe.NetSpec() # 基礎網路 # 帶標籤的資料輸入層 train_net.data, train_net.label = L.Data(source=train_data,backend=P.Data.LMDB, batch_size=64,ntop=2,transform_param=dict(scale=0.00390625)) # 生成LeNet5的主體結構 lenet5_body(train_net, 'data') # 生成誤差損失層 train_net.loss = L.SoftmaxWithLoss(train_net.ip2, train_net.label) # 測試網路 test_net = caffe.NetSpec() # 基礎網路 # 帶標籤的資料輸入層 test_net.data, test_net.label = L.Data(source=test_data, batch_size=100, backend=P.Data.LMDB, ntop=2,transform_param=dict(scale=0.00390625)) # 生成LeNet5的主體結構 lenet5_body(test_net, 'data') # 生成誤差損失層 test_net.loss = L.SoftmaxWithLoss(test_net.ip2, test_net.label) # 新增一個精確層 test_net.accuracy = L.Accuracy(test_net.ip2, test_net.label) # 實施網路 deploy_net = caffe.NetSpec() # 基礎網路 # 帶標籤的資料輸入層 deploy_net.data = L.Input(input_param=dict(shape=dict(dim=[64,1,28,28]))) # 生成LeNet5的主體結構 lenet5_body(deploy_net, 'data') deploy_net.prob = L.Softmax(deploy_net.ip2)
4.3 prototxt檔案的儲存
通過to_proto將網路儲存成prototxt檔案
# 儲存訓練檔案 with open(model_root+'train.prototxt', 'w') as f: print('name: "LenNet5_train"', file=f) print(train_net.to_proto(), file=f) with open(model_root+'test.prototxt', 'w') as f: print('name: "LenNet5_test"', file=f) print(test_net.to_proto(), file=f) with open(model_root+'deploy.prototxt', 'w') as f: print('name: "LenNet5_test"', file=f) print(deploy_net.to_proto(), file=f)