Caffe官方教程翻译(1):LeNet MNIST Tutorial
前言
最近打算重新跟著官方教程學習一下caffe,順便也自己翻譯了一下官方的文檔。自己也做了一些標注,都用斜體標記出來了。中間可能額外還加了自己遇到的問題或是運行結果之類的。歡迎交流指正,拒絕噴子!
官方教程的原文鏈接:http://caffe.berkeleyvision.org/gathered/examples/mnist.html
Training LeNet on MNIST with Caffe
事先聲明,我們默認你已經成功地編譯了Caffe源碼。如果沒有,請參考Installation Page。在這篇教程中,我們也默認認為你的caffe安裝在CAFFE_ROOT。
準備數據集
首先,你需要從MNIST網頁上下載數據并轉換數據集的格式(直接下載的數據都是壓縮了的)。為了做到這個,我只需要簡單地在終端運行如下命令:
cd $CAFFE_ROOT ./data/mnist/get_mnist.sh ./examples/mnist/create_mnist.sh如果終端反饋報錯說wget或是gunzip沒有安裝,你還需要分別安裝他們。在運行了這些腳本之后,會生成兩個數據集:mnist_train_lmdb和mnist_test_lmdb。
LeNet:MNIST分類模型
在我們實際運行訓練程序之前,讓我們先解釋一下究竟會發生什么。我們將會使用LeNet網絡,眾所周知,這個網絡在手寫數字分類任務上表現得非常好。我們將會跑一個與原始的LeNet相比,稍微有些不同的版本的網絡。在這個網絡中,神經元的sigmoid激活函數被替換為ReLu激活函數了。(補充:線性修正單元激活函數,簡稱ReLu函數,嘛,一般來說ReLuctant函數可以使得網絡的訓練更快,提升訓練效果,除了最后輸出層的激活函數有時不得不使用sigmoid函數外,隱含層中激活函數一律使用ReLu函數會比使用sigmoid函數取得更好的效果)
LeNet網絡的設計包含了卷積神經網絡(CNN)的本質,在一些大型模型諸如用于ImageNet的模型中也依然通用。一般來說,它包含了一個卷積層后面跟著一個池化層,另外一個卷積層再跟一個池化層,加上兩個全連接層(近似與卷積多層感知機),我們把網絡定義在下面的文件中:
$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt
定義MNIST網絡
這個部分主要講解lenet_train_test.prototxt,其中詳細定義了LeNet模型,用于手寫數字分類。我們默認你已經很熟悉Google Protobuf,并且已經閱讀過caffe中使用的protobuf定義(定義在$CAFFE_ROOT/src/caffe/proto/caffe.proto)。
接下來我們要具體根據protobuf寫下caffe的神經網絡,caffe::NetParameter(或是基于python,caffe.proto.caffe_pb2.NetParameter)。我們首先要給網絡定義一個名字:
name: "LeNet"編寫數據層
現在,我們要從我們之前創建的lmdb數據庫文件讀取MNIST的數據了。下面是定義的一個數據層:
layer {name: "mnist"type: "Data"transform_param {scale:0.00390625}data_param {source: "mnist_train_lmdb"backend: LMDBbatch_size: 64}top: "data"top: "label" }很明確地,我們可以看到,這個層的名字是mnist,類型是Data,并且他會從給定的lmdb文件中讀取數據。每次抽取的batch_size為64,并且限制讀入的像素的灰度范圍為[0,1)[0,1)。這里可能會有疑問了,為什么是0.003906250.00390625這個數?因為它等于12561256。(補充:因為通常讀入的每個像素的灰度范圍都是從0到255,這里意思就是做一個歸一化,目的就是可以讓后面的數據也可以更好處理)最后呢,這個層會產生兩個blob(補充:親切點,叫它泡泡吧。好了不開玩笑了,就理解為一個單元就行了),一個是data的blob,一個是label的blob。(補充:訓練時,data的blob負責讀入訓練數據,label的blob負責讀入標簽,很好理解)
編寫卷積層
讓我們來定義第一個卷積層:
layer {name: "conv1"type: "Convolution"param { lr_mult: 1 }param { lr_mult: 2 }convolution_param {num_output: 20kernel_size: 5stride: 1weight_filler {type: "xavier"}bias_filler {type: "constant"}}bottom: "data"top: "conv1" }這一層接收了了前面的數據層送來的數據,并生成了conv1層。上面定義了,生成的conv1層的輸出num_output有20個通道,卷積核的大小kernel_size為5(5×55×5的卷積核),步長stride為1(1×11×1 的步長)。
上面定義了兩個filler,一個是weight_filler,一個是bias_filler。這些filler讓我們可以初始化權重和偏置參數。對于weight_filler來說,我們使用xavier算法來自動根據輸入參數數量和輸出神經元個數決定初始化參數。對于bias_filler來說,我們很簡單地將其初始化為常數,默認的初始化的值是0。
還有幾個參數。lr_mult是當前層的可學習參數的學習率的調整參數。這么理解可能比較繞,舉個例子說明會好一些。在這個例子中,我們設置第一個可學習參數,即權重參數,其學習率與程序運行時給的學習率相同(1倍);然后是第二個可學習參數,即偏置參數,其學習率是程序運行時給的學習率的2倍。這么做通常能讓神經網絡更好地收斂。(補充:學習率會在后面定義,這里只要知道訓練時caffe會送進來網絡的學習率,在前面定義的lr_mult中,權重參數與那個學習率相同吧,而偏置參數會是那個學習率的2倍)
編寫池化層
呸呸呸(文檔上就是這么寫的,皮這一下很開心?),池化層更加容易定義:
layer {name: "pool1"type: "Pooling"pooling_param {kernel_size: 2stride: 2pool: MAX}bottom: "conv1"top: "pool1" }上面這一段定義了:我們對數據進行最大化池化(max pooling),池化核的大小kernel_size為2(2×22×2 的池化核),步長stride為2(2×22×2的步長)。(不難看出相鄰池化區域之間沒有重疊部分)
相似地也可以照葫蘆畫瓢,編寫出第二個卷積層和池化層。可以到$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt中查看詳細的定義。
編寫全連接層
編寫全連接層也沒什么難的了:
layer {name: "ip1"type: "InnerProduct"param { lr_mult: 1 }param { lr_mult: 2 }inner_product_param {num_output: 500weight_filler {type: "xavier"}bias_filler {type: "constant"}}bottom: "pool2"top: "ip1" }這里定義了一個全連接層(要知道在Caffe中全連接層用InnerProduct層表示,直譯就是內積),輸出有500個(補充:這里不表示通道數了,表示輸出神經元個數)。其他所有的行都很眼熟,對吧?
編寫ReLu層
一個ReLu層也很簡單的:
layer {name: "relu1"type: "ReLu"bottom: "ip1"top: "ip1" }既然ReLu是一個對元素的操作。那么我們可以就地對它處理,以節省一些內存。這里是通過直接把相同的名字給到bottom和top的blob**實現的(補充:嘛,就是直接自己連自己)。不過,當然不能直接復制這個**blob的名字給其他類型的層。
在ReLu層之后,我們再編寫另外一個內積層(全連接層):
layer {name: "ip2"type: "InnerProduct"param { lr_mult: 1 }param { lr_mult: 2 }inner_product_param {num_output: 10weight_filler {type: "xavier"}bias_filler {type: "constant"}}bottom: "ip1"top: "ip2" }(補充:基本都和前面一樣,但是輸出num_output是10個,為什么?因為手寫數字是0-9,每一個輸出對應一個數字,如果識別的是某個數字,那就輸出1就行了,這就是我們常說的one-hot編碼)
編寫損失層
最后我們還要寫一下損失層:
layer {name: "loss"type: "SoftmaxWithLoss"bottom: "ip2"bottom: "label" }這個softmax_loss層聲明了softmax和多項logistic損失(這樣可以解決時間并提高數值的穩定性)。它需要輸入兩個blob,第一個會給出預測的結果,第二個是由2數據層提供的標簽(還記得不?就在最開始的那個層定義的)。它并不會產生任何輸出值,它所做的就只是計算損耗函數的值,當反向傳播開始時報告它(補充:命令行上可以看到),并且根據ip2層初始化梯度。這就是魔術真正開始實施的地方。
額外的提示:編寫層的規則
每一層的定義可以包含是否和何時他們會被網絡包含進去的規則,就像下面這個:
layer {// ...layer definition...include: { phase: TRAIN } }這是一項規則,基于當前神經網絡的狀態,會判斷這一層是否應該包含在網絡中。你也可以查看$CAFFE_ROOT/src/caffe/proto/caffe.proto來獲取更多有關層定義規則和模型架構的信息。
在上面這個例子中,這一層只會在訓練(TRAIN phase)階段被包含進網絡。如果我們把TRAIN換成TEST,那么這一層將只會在測試階段被包含進網絡。默認來說,那里也不會有層級規則,一個層總是會被包含進網絡。另外,lenet_train_test.prototxt中定義了兩個數據層(也有不同的batch_size),一個是用于訓練階段的,而另一個是用于測試階段的。就如在lenet_solver.prototxt中定義的一樣,還有一個Accuracy層,僅僅是在測試階段(TEST phase)中被引入,以反饋模型在每100步迭代過程中的準確率。
定義MNIST的解決方案
在下面的prototxt 文件中查看定義的一些信息:
$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt
# The train/test net protocol buffer definition net: "examples/mnist/lenet_train_test.prototxt" # test_iter specifies how many forward passes the test should carry out. # In the case of MNIST, we have test batch size 100 and 100 test iterations, # covering the full 10,000 testing images. test_iter: 100 # Carry out testing every 500 training iterations. test_interval: 500 # The base learning rate, momentum and the weight decay of the network. base_lr: 0.01 momentum: 0.9 weight_decay: 0.0005 # The learning rate policy lr_policy: "inv" gamma: 0.0001 power: 0.75 # Display every 100 iterations display: 100 # The maximum number of iterations max_iter: 10000 # snapshot intermediate results snapshot: 5000 snapshot_prefix: "examples/mnist/lenet" # solver mode: CPU or GPU solver_mode: GPU補充:
上面也就是定義了一些訓練的設置,比如學習率,迭代次數,snapshot(意思是每迭代多少次保存一次模型)等等,不做贅述了。
為了省事,在我的筆記本上只是裝了CPU版本的caffe,所以跑的時候用的是CPU版本的跑。
下面這個東西自己看著改改就行了,后面會提到詳細信息:
# solver mode: CPU or GPU solver_mode: CPU訓練和測試我們的模型
在你編寫了網絡定義(network definition protobuf)和解決方案(solver protobuf)文件之后,訓練模型對你來說就很簡單了。直接運行train_lenet.sh腳本文件,或者說自己直接用命令行來訓練也行:
cd $CAFFE_ROOT ./examples/mnist/train_lenet.sh當你運行代碼時,你會看到很多像這樣的信息一閃而過:
I1203 net.cpp:66] Creating Layer conv1 I1203 net.cpp:76] conv1 <- data I1203 net.cpp:101] conv1 -> conv1 I1203 net.cpp:116] Top shape: 20 24 24 I1203 net.cpp:127] conv1 needs backward computation.這些信息告訴了你每一層的細節信息:它的連接方式和輸出的大小,這些東西都很方便你調試。在初始化網絡結束之后,就會開始進行訓練:
I1203 net.cpp:142] Network initialization done. I1203 solver.cpp:36] Solver scaffolding done. I1203 solver.cpp:44] Solving LeNet基于前面對解決方案的設置,我們會打印訓練過程中每100次迭代的loss函數的輸出值,并且每過500次迭代測試一次網絡。你會看到類似下面的信息:
I1203 solver.cpp:204] Iteration 100, lr = 0.00992565 I1203 solver.cpp:66] Iteration 100, loss = 0.26044 ... I1203 solver.cpp:84] Testing net I1203 solver.cpp:111] Test score #0: 0.9785 I1203 solver.cpp:111] Test score #1: 0.0606671對于訓練中每一次迭代,lr是那次迭代的學習率,并且loss就是訓練中損失函數輸出的值。對于測試階段的輸出來說,score 0輸出的是準確率,score 1輸出的是損失函數。
過了幾分鐘,訓練就完成了。(補充:GPU哈,CPU不可能的)
I1203 solver.cpp:84] Testing net I1203 solver.cpp:111] Test score #0: 0.9897 I1203 solver.cpp:111] Test score #1: 0.0324599 I1203 solver.cpp:126] Snapshotting to lenet_iter_10000 I1203 solver.cpp:133] Snapshotting solver state to lenet_iter_10000.solverstate I1203 solver.cpp:78] Optimization Done.補充:可以看到這里的準確率是0.9897,我在訓練時得到的是0.991左右的準確率,這個結果可以說比較理想了。
最后的模型,會存儲為二進制protobuf文件,存為:
lenet_iter_10000如果你在某一個現實應用的數據集中訓練的話,你可以在你的應用中將它作為一個訓練好的模型直接調用。
額…怎么用GPU訓練?
你剛剛就是用GPU訓練的!所有的訓練都是運行在GPU上的。事實上,如果你想在CPU上訓練,你可以很簡單地改變lenet_solver.prototxt中的一行:
# solver mode: CPU or GPU solver_mode: CPU然后你就可以在CPU上進行訓練了。這不是很簡單嗎?
MNIST是一個很小的數據集,所以就通信開銷來說使用GPU訓練并不顯得有多大有時。在更大的數據集上訓練那些更大更復雜的模型時,比如ImageNet,CPU和GPU計算速度的差距會變得十分明顯。
如何在特定的步驟中減小學習率?
請查閱 lenet_multistep_solver.prototxt文件。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Caffe官方教程翻译(1):LeNet MNIST Tutorial的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三维重建学习(5):简单地从数学原理层面
- 下一篇: Caffe官方教程翻译(2):Web d