1. 程式人生 > >【caffe學習筆記——mnist】mnist手寫資料集訓練和測試

【caffe學習筆記——mnist】mnist手寫資料集訓練和測試

http://blog.csdn.NET/liumaolincycle/article/details/47336921

本文主要來自Caffe作者Yangqing Jia網站給出的examples。

@article{jia2014caffe,
  Author = {Jia, Yangqing and Shelhamer, Evan and Donahue, Jeff and Karayev, Sergey and Long, Jonathan and Girshick, Ross and Guadarrama, Sergio and Darrell, Trevor},
  Journal = {arXiv preprint arXiv:
1408.5093}, Title = {Caffe: Convolutional Architecture for Fast Feature Embedding}, Year = {2014} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.準備資料集

首先從MNIST網站上下載資料集,執行:

cd $CAFFE_ROOT 
./data/mnist/get_mnist.sh 
  • 1
  • 2

下載到四個檔案,從左至右依次是測試集影象、測試集標籤、訓練集影象、訓練集標籤:

圖1

轉換資料格式:

./examples/mnist/create_mnist.sh
  • 1

在 examples/mnist下出現兩個新的資料夾:

圖2

create_mnist.sh這個指令碼是將訓練集和測試集分別轉換成了lmdb格式。

圖3

2.LeNet: MNIST分類模型

本實驗用的網路模型是LeNet,它是公認在數字分類任務上效果很好的網路。實驗中在原始 LeNet基礎上做了一點改動,對於神經元的啟用,用ReLU替換了sigmoid。 
LeNet的設計包括一個卷積層,後隨一個pooling層,再一個卷積層,後隨一個pooling層,再兩個全連線層,類似於傳統的多層感知器。經典LeNet如圖:

圖4

這些層的定義在examples/mnist/lenet_train_test.prototxt中。

3.定義MNIST網路

在定義自己的網路之前可以執行示例中給出的程式碼訓練網路:

sh examples/mnist/train_lenet.sh
  • 1

過程與CIFAR-10中的一樣,所用solver是examples/mnist/lenet_solver.prototxt,在這個solver中可以看到對訓練與測試的簡單設定,所用的網路定義就是examples/mnist/lenet_train_test.prototxt。 
下面詳細學習examples/mnist/lenet_train_test.prototxt的模型定義,學習的基礎是對Google Protobuf比較熟悉,可參考Google Protocol Buffer的使用和原理,還要讀過Caffe使用的protobuf定義,這個定義在src/caffe/proto/caffe.proto中。 
為了更深入地瞭解網路是怎麼定義的,我們自己寫一個caffe網路引數的protobuf。首先新建一個prototxt檔案,我這裡的命名是lenet_train_lml.prototxt。給網路取個名字:

name: "LeNet"
  • 1

3.1 寫資料層

現在要從之前建立的lmdb中讀取MNIST資料,定義如下的資料層:

layer { 
    name: "mnist"  #該層的名字
    type: "Data"    #輸入的型別
    data_param {    #資料引數
        source: "mnist_train_lmdb"  #資料來源,從 mnist_train_lmdb中讀入資料
        backend: LMDB   
        batch_size: 64  #批次大小,即一次處理64條資料
        scale: 0.00390625   #畫素灰度歸一化引數,1/256
    } 
    top: "data"     #該層生成兩個blob,分別是data和label
    top: "label" 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2 寫卷積層

定義第一個卷積層:

layer { 
    name: "conv1"   #該層的名字
    type: "Convolution"     #該層的型別
    param { lr_mult: 1 }    #學習率,權重的和solver的一樣
    param { lr_mult: 2 }    #偏移的是solver的兩倍
    convolution_param { 
        num_output: 20  #卷積核個數
        kernel_size: 5  #卷積核大小
            stride: 1   #步長
            weight_filler {  
            type: "xavier"  #用xavier演算法基於輸入輸出神經元數自動初始化權重
        } 
            bias_filler { 
            type: "constant"    #簡單初始化偏移為常數,預設為0 
        } 
    } 
    bottom: "data"  #該層的上一層是 data
    top: "conv1"    #該層生成conv1 blob
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.3 寫pooling層

pooling層就好定義多了:

layer { 
    name: "pool1"   #該層的名字
    type: "Pooling"     #該層的型別
    pooling_param {     
        kernel_size: 2  #pooling的核是2×2
        stride: 2   #步長是2,也就是說相鄰pooling區域之間沒有重疊
        pool: MAX   #pooling的方式
    } 
    bottom: "conv1"     #該層的上一層是conv1
    top: "pool1"    #該層生成pool1 blob
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然後就可以自己寫第二個卷積層和pooling層了,細節參考examples/mnist/lenet_train_test.prototxt。

layer { 
    name: "conv2" 
    type: "Convolution" 
    bottom: "pool1" 
    top: "conv2" 
    param {  lr_mult: 1 } 
    param {  lr_mult: 2 } 
    convolution_param { 
        num_output: 50 #卷積核變成了50個
        kernel_size: 5 
        stride: 1 
        weight_filler { 
            type: "xavier" 
        } 
        bias_filler { 
            type: "constant" 
        } 
    } 
} 
layer { 
    name: "pool2" 
    type: "Pooling" 
    bottom: "conv2" 
    top: "pool2" 
    pooling_param { 
        pool: MAX 
        kernel_size: 2 
        stride: 2 
    } 
}
  • 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

3.4 寫全連線層

全連線層也比較簡單:

layer { 
    name: "ip1" #全連線層的名字
    type: "InnerProduct"    #全連線層的型別
    param { lr_mult: 1 } 
    param { lr_mult: 2 } 
    inner_product_param { 
        num_output: 500     #輸出500個節點
        weight_filler { 
            type: "xavier" 
        } 
        bias_filler { 
            type: "constant" 
        } 
    } 
    bottom: "pool2" 
    top: "ip1" 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.5 寫ReLU層

layer { 
    name: "relu1" 
    type: "ReLU" 
    bottom: "ip1" 
    top: "ip1" 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

因為ReLU是元素級操作,所以可以用一種叫做in-place(猜測可以翻譯為在原位置,也就是不開闢新記憶體)的操作來節省記憶體,這是通過簡單地把bottom blob和top blob設成同樣的名字來實現,當然了,不要在其他型別的層中這麼幹。 
然後再寫一個全連線層:

layer { 
    name: "ip2" 
    type: "InnerProduct" 
    param { lr_mult: 1 } 
    param { lr_mult: 2 } 
    inner_product_param { 
        num_output: 10 
        weight_filler { 
            type: "xavier" 
        } 
        bias_filler { 
            type: "constant" 
        } 
    } 
    bottom: "ip1" 
    top: "ip2" 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.6 寫loss層

最後寫一個loss層:

layer { 
    name: "loss" 
    type: "SoftmaxWithLoss" 
    bottom: "ip2" 
    bottom: "label" 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

softmax_loss層實現了Softmax和多項Logistic損失(節省了時間,同時提高了資料穩定性)。它需要兩個blob,第一個是預測,第二個是資料層生成的label。該層不產生輸出,只是計算loss函式的值,在反向傳播的時候使用,並初始化關於ip2的梯度。

3.7 寫層次規則

層次定義包含的規則是這些層是否以及什麼時候包含在網路定義中,像這樣:

layer { 
    #...layer definition... 
    include: { phase: TRAIN } 
}
  • 1
  • 2
  • 3
  • 4

這個規則基於現有網路狀態,控制網路中的層次包含關係,可以檢視src/caffe/proto/caffe.proto來獲取層次規則及模型概要的更多資訊。 
在上面的例子中,這個層只會包含在TRAIN階段中,如果把TRAIN改為TEST,這個層就只會被用於TEST階段。如果不寫TRAIN或TEST的話,那麼這個層TRAIN階段和TEST階段都會被用到,所以lenet_train_test.prototxt中定義了兩個DATA層,我們參考它也分別定義兩個DATA層:

layer {
    name: "mnist"
    type: "Data"
    top: "data"
    top: "label"
    include {
        phase: TRAIN
    }
    transform_param {
        scale: 0.00390625
    }
    data_param {
        source: "examples/mnist/mnist_train_lmdb"
        batch_size: 64
        backend: LMDB
    }
}
layer {
    name: "mnist"
    type: "Data"
    top: "data"
    top: "label"
    include {
        phase: TEST
    }
    transform_param {
        scale: 0.00390625
    }
    data_param {
        source: "examples/mnist/mnist_test_lmdb"
        batch_size: 100
        backend: LMDB
    }
}
  • 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

另外,TEST階段有一個Accuracy層,它是用來每100次迭代報告一次模型準確率的,lenet_solver.prototxt中給出了定義,我們同樣也把它加上:

layer {
    name: "accuracy"
    type: "Accuracy"
    bottom: "ip2"
    bottom: "label"
    top: "accuracy"
    include {
        phase: TEST
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.定義MNIST的solver檔案

再看一下lenet_solver.prototxt中都定義了些什麼:

# 訓練/檢測網路的protobuf定義
net: "examples/mnist/lenet_train_lml.prototxt"
# test_iter指的是測試的迭代次數,這裡是100,測試批次大小也是100,這樣就覆蓋了10000個測試影象
test_iter: 100
# 每訓練迭代500次就測試一次
test_interval: 500
# 學習率,動量,權重下降
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# 學習率規則
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# 每迭代100次顯示一次
display: 100
# 最大迭代次數
max_iter: 10000
# 每5000次迭代儲存一次快照
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# 選擇CPU還是GPU模式
solver_mode: GPU
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5.訓練與測試模型

在寫完網路定義和solver之後,就可以訓練模型了,執行:

./examples/mnist/train_lenet.sh
  • 1

會在終端看到這樣的訊息,這些訊息顯示了每一層的細節,即連線關係與輸出的形狀,在除錯的時候是很有用的:

圖5

初始化以後就開始訓練了:

圖6

solver中設定每100次迭代打印出訓練的loss,每1000次迭代打印出測試的loss:

圖7

迭代完結果就出來了:

圖8

最後的模型儲存在一個二進位制的protobuf檔案lenet_iter_10000.caffemodel中,在訓練其他資料集的時候可以把它作為基礎模型。

6.其他

通過這個實驗,終於知道網路要怎麼設定了,還有其他不同的solver值得一試。例如autoencoder網路,執行命令:

./examples/mnist/train_mnist_autoencoder.sh
  • 1

或者:

./examples/mnist/train_mnist_autoencoder_adagrad.sh
  • 1

或者:

./examples/mnist/train_mnist_autoencoder_nesterov.sh