Caffe 議事(二):從零開始搭建 ResNet 之 網絡的搭建(上)

3.搭建網絡:

  搭建網絡以前,要確保以前編譯 caffe 時已經 make pycaffe 了。python

  步驟1:導入 Caffe

  咱們首先在 ResNet 文件夾中創建一個 mydemo.py 的文件,本參考資料咱們用 spyder 打開。要導入 Caffe 的話直接 import caffe 是不能夠的,由於系統找不到 caffe module,這時候要告訴系統 caffe 在哪裏能夠導入,所以須要添加 caffe 的路徑,準確地說是 caffe-master/python 路徑。爲了之後的方便,咱們在 ResNet 中再創建一個 init_path.py,在這個文件中寫入如下代碼並保存:git

import os.path as osp
import sys

# 添加路徑到系統路徑
def add_path(path):
    if path not in sys.path:
        sys.path.insert(0,path)

# 返回當前文件所在目錄
this_dir = osp.dirname(__file__)
# 組合成caffe的路徑
pycaffe_path = osp.join(this_dir, 'caffe-master', 'python')
# 添加路徑
add_path(pycaffe_path)

   由於 init_path.py 是在 …/ResNet 下,因此 this_dir 這個返回的就是 …/ResNet 目錄,那麼 pycaffe_path = …/ResNet/caffe-master/python,這個路徑添加進系統路徑後,咱們在 mydemo.py 中鍵入以下代碼,而後運行,不報錯就說明已經導入 Caffe 了。github

import init_path
import caffe
import numpy as np
from caffe import layers as L, params as P

Fig 10 成功導入 Caffe網絡

  步驟2:建立網絡的 prototxt 文件

  Caffe 裏面跑網絡只須要 solver.prototxt 就能夠了,solver 裏面含有網絡的模型(包括訓練和測試的網絡),模型也是 prototxt 文件。所以咱們須要生成 solver 的 prototxt 和網絡的 prototxt 文件。咱們先生成網絡的 prototxt 文件,在 ResNet 文件夾中再新建一個文件夾叫 res_net_model,用來存儲網絡模型文件。咱們補充 mydemo.py 以下:ide

# -*- coding: utf-8 -*-
import init_path
import caffe
import numpy as np
import os.path as osp
from caffe import layers as L, params as P, to_proto

this_dir = osp.dirname(__file__)


def ResNet(split):
    pass

# 生成 ResNet 網絡的 prototxt 文件
def make_net():
    
    # 建立 train.prototxt 並將 ResNet 函數返回的值寫入 train.prototxt
    with open(this_dir + '/res_net_model/train.prototxt', 'w') as f:
        f.write(str(ResNet('train')))
        
    # 建立 test.prototxt 並將 ResNet 函數返回的值寫入 test.prototxt
    with open(this_dir + '/res_net_model/test.prototxt', 'w') as f:
        f.write(str(ResNet('test')))

if __name__ == '__main__':
    
    make_net()

   每次執行 mydemo.py 時,首先運行 make_net(),而後在 make_net 函數中建立 prototxt 文件,將 ResNet 返回的內容寫入 prototxt,那麼最關鍵的就是在 ResNet 返回的值。咱們先給出在 ResNet 數據層的例子:函數

def ResNet(split):
    
    # 寫入數據的路徑
    train_file = this_dir + '/caffe-master/examples/cifar10/cifar10_train_lmdb'
    test_file = this_dir + '/caffe-master/examples/cifar10/cifar10_test_lmdb'
    mean_file = this_dir + '/caffe-master/examples/cifar10/mean.binaryproto'
    
    # source: 導入的訓練數據路徑; 
    # backend: 訓練數據的格式; 
    # ntop: 有多少個輸出,這裏是 2 個,分別是 n.data 和 n.labels,即訓練數據和標籤數據,
    # 對於 caffe 來講 bottom 是輸入,top 是輸出
    # mirror: 定義是否水平翻轉,這裏選是
    
    # 若是寫是訓練網絡的 prototext 文件    
    if split == 'train':
        data, labels = L.Data(source = train_file, backend = P.Data.LMDB, 
                              batch_size = 128, ntop = 2, 
                              transform_param = dict(mean_file = mean_file, 
                                                      crop_size =28, 
                                                      mirror = True))

    
    # 若是寫的是測試網絡的 prototext 文件
    # 測試數據不須要水平翻轉,你僅僅是用來測試
    else:
        
        data, labels = L.Data(source = test_file, backend = P.Data.LMDB, 
                              batch_size = 128, ntop = 2, 
                              transform_param = dict(mean_file = mean_file, 
                                                      crop_size =28))

  有人或許有疑問,爲何會有 L.data?L.Data 裏面有這麼多參數怎麼來的?在 spyder 上面即便打了 L. 也不會提示 L 有哪些具體的函數(只顯示系統固有函數),那麼如何知道的呢?在 caffe-master/src/caffe/proto/caffe.proto 裏面有這些函數的介紹,這是個混合編譯的文件,固然讀裏面的內容並不難。下面是咱們詳細來講明:學習

 

Fig 11 caffe.proto 數據層截圖測試

  在 caffe.proto 搜索 DataParameter,咱們就能找到這些參數,那麼數據層的名字叫什麼呢?很簡單,把 Paramter 去掉就是了,也就是 L.Data,數據層有哪些參數,參數的類型都是什麼,上面寫得都很清楚,咱們的例子用到了 source 和 batch_size(這 2 個必須指定),其餘的參數都有default 選項,source 類型是 string,咱們就知道是字符串類型,那就是存數據的路徑了;batch_size 是 uint32,就是數字了;backend 有點特別,是 DB 類型的,咱們看上面 DB 裏面有 LEVELDB 和 LMDB,那麼咱們寫的時候這樣寫 backend = P.Data.LMDB 或者 P.Data.LEVELDB,由於這裏 default 是 LEVELDB 格式,而咱們是數據類型是 LMDB,因此要賦值 backend,其餘的依次類推了。ui

  由於 caffe 裏面訓練基本都是用 SGD(隨機梯度降低)的方法,所以都要取樣本塊,一次迭代只拿一個 batch 來訓練,這裏 batch_size 咱們就設置爲 128 (固然你也能夠是 100 或者其餘什麼,不過建議不要太大)。爲何要設置 mean_file 路徑?設置這個路徑是爲了讓數據減去它的均值,這樣網絡收斂會更快,效果也每每會更好,至關於一個簡單的 preprocessing 的過程。爲何要設置 crop_size?設置 crop_size 爲 28 意味着將原來的 3 X 32 X 32 大小的圖像隨機剪裁成 3 X 28 X 28 大小的圖像塊做爲輸入數據,雖然論文中做者是在原來 3 X 32 X 32 大小的圖像的上下左右加上 4 層 pad,pad 的值均爲 0,變成了 3 X 40 X 40 的圖像,而後在這個圖像上隨機剪裁成 3 X 32 X 32 大小圖像做爲輸入數據,但這裏爲了快速實現 ResNet 所以採用了一個折中的辦法,因爲輸入數據大小變成了 3 X 28 X 28,因此測試數據要進行剪裁成一樣大小,這種剪裁的方法是 data augmentation的一種,能夠增長樣本的多樣性。爲何要設置 mirror?mirror 設置爲 True 意味將剪裁後的圖像進行隨機水平翻轉,既要麼翻轉要麼不翻轉。跟上面的 data augmentation 同樣,也是一種增長樣本多樣性的方法,咱們認爲圖像通過水平翻轉以後裏面的物體仍然是那個物體。this

  數據層咱們定義好了之後,接下來咱們定義 ResNet Block,由於 ResNet Block 是有規律的,全部咱們再額外寫一些函數,補充代碼以下:

def conv_BN_scale_relu(split, bottom, nout, ks, stride, pad):
    
    conv = L.Convolution(bottom, kernel_size = ks, stride = stride, 
                         num_output = nout, pad = pad, bias_term = True, 
                         weight_filler = dict(type = 'xvaier'), 
                         bias_filler = dict(type = 'constant'), 
                         param = [dict(lr_mult = 1, decay_mult = 1), 
                                  dict(lr_mult = 2, decay_mult = 0)])
    if split == 'train':
        
        # 訓練的時候咱們對 BN 的參數取滑動平均
        BN = L.BatchNorm(
            conv, batch_norm_param = dict(use_global_stats = False), 
                in_place = True, param = [dict(lr_mult = 0, decay_mult = 0), 
                                          dict(lr_mult = 0, decay_mult = 0), 
                                          dict(lr_mult = 0, decay_mult = 0)])
        
    else:
        
        # 測試的時候咱們直接是有輸入的參數,BN 的學習率懲罰設置爲 0,由 scale 學習 
        BN = L.BatchNorm(
            conv, batch_norm_param = dict(use_global_stats = True), 
                in_place = True, param = [dict(lr_mult = 0, decay_mult = 0), 
                                          dict(lr_mult = 0, decay_mult = 0), 
                                          dict(lr_mult = 0, decay_mult = 0)])
    
    scale = L.Scale(BN, scale_param = dict(bias_term = True, in_place = True))
    relu = L.ReLu(scale, in_place = True)
    
    return scale, relu

Fig 12 conv_BN_scale_relu 函數輸入到輸出結構

  對 conv_BN_scale_relu 函數的解釋:輸入的數據爲 bottom,nout 是卷積核的個數,也等於輸出數據的通道數,ks 是卷積核的大小,3 的意思是 3 X 3 大小的卷積核,stride 意思是步長,pad 的意思是在輸入數據上下左右補多少層 0,卷積以後咱們還對數據進行 BN(BatchNormalization)操做,爲何要進行 BN,《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》這篇論文講到過會加速網絡的訓練速度,具體這裏就不講了,然而 caffe 中 BN 層並不能學習到 α 和 β 參數,所以要加上 scale 層學習,這是做者在 ResNet code主頁上  https://github.com/KaimingHe/deep-residual-networks 提到的:

  

 

  通過scale層以後,咱們再通過一個激活函數ReLU,咱們返回的值是scale層的輸出和ReLU的輸出,這樣能夠供咱們選擇。下面講解另外的一個函數:

def ResNet_block(split, bottom, nout, ks, stride, projection_stride, pad):
    
    # 1 表明不須要 1 X 1 的映射
    if projection_stride == 1:
        
        scale0 = bottom
    
    # 不然通過 1 X 1,stride = 2 的映射    
    else:
        
        scale0, relu0 = conv_BN_scale_relu(split, bottom, nout, 1, 
                                           projection_stride, 0)
                                           
    scale1, relu1 = conv_BN_scale_relu(split, bottom, nout, ks, 
                                       projection_stride, pad)
    scale2, relu2 = conv_BN_scale_relu(split, bottom, nout, ks, stride, pad)
    
    wise = L.Eltwise(scale2, scale0, operation = P.Eltwise.SUM)
    wise_relu = L.ReLu(wise, in_place = True)
    
    return wise_relu

   咱們在 ResNe t結構介紹部分中提到了網絡的結構,發現輸入數據通過 2 次卷積操做後再與輸入數據相加即爲 ResNet 的基本結構,所以這個 ResNet_block 就定義了這個部分。

Fig 13 ResNet_bloc k函數輸入到輸出的結構

相關文章
相關標籤/搜索