【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
下載到四個檔案,從左至右依次是測試集影象、測試集標籤、訓練集影象、訓練集標籤:
轉換資料格式:
./examples/mnist/create_mnist.sh
- 1
在 examples/mnist下出現兩個新的資料夾:
create_mnist.sh這個指令碼是將訓練集和測試集分別轉換成了lmdb格式。
2.LeNet: MNIST分類模型
本實驗用的網路模型是LeNet,它是公認在數字分類任務上效果很好的網路。實驗中在原始 LeNet基礎上做了一點改動,對於神經元的啟用,用ReLU替換了sigmoid。
LeNet的設計包括一個卷積層,後隨一個pooling層,再一個卷積層,後隨一個pooling層,再兩個全連線層,類似於傳統的多層感知器。經典LeNet如圖:
這些層的定義在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
會在終端看到這樣的訊息,這些訊息顯示了每一層的細節,即連線關係與輸出的形狀,在除錯的時候是很有用的:
初始化以後就開始訓練了:
solver中設定每100次迭代打印出訓練的loss,每1000次迭代打印出測試的loss:
迭代完結果就出來了:
最後的模型儲存在一個二進位制的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