Caffe官方教程翻译(1):LeNet MNIST Tutorial
前言
最近打算重新跟著官方教程學(xué)習(xí)一下caffe,順便也自己翻譯了一下官方的文檔。自己也做了一些標(biāo)注,都用斜體標(biāo)記出來了。中間可能額外還加了自己遇到的問題或是運(yùn)行結(jié)果之類的。歡迎交流指正,拒絕噴子!
官方教程的原文鏈接:http://caffe.berkeleyvision.org/gathered/examples/mnist.html
Training LeNet on MNIST with Caffe
事先聲明,我們默認(rèn)你已經(jīng)成功地編譯了Caffe源碼。如果沒有,請(qǐng)參考Installation Page。在這篇教程中,我們也默認(rèn)認(rèn)為你的caffe安裝在CAFFE_ROOT。
準(zhǔn)備數(shù)據(jù)集
首先,你需要從MNIST網(wǎng)頁上下載數(shù)據(jù)并轉(zhuǎn)換數(shù)據(jù)集的格式(直接下載的數(shù)據(jù)都是壓縮了的)。為了做到這個(gè),我只需要簡單地在終端運(yùn)行如下命令:
cd $CAFFE_ROOT ./data/mnist/get_mnist.sh ./examples/mnist/create_mnist.sh如果終端反饋報(bào)錯(cuò)說wget或是gunzip沒有安裝,你還需要分別安裝他們。在運(yùn)行了這些腳本之后,會(huì)生成兩個(gè)數(shù)據(jù)集:mnist_train_lmdb和mnist_test_lmdb。
LeNet:MNIST分類模型
在我們實(shí)際運(yùn)行訓(xùn)練程序之前,讓我們先解釋一下究竟會(huì)發(fā)生什么。我們將會(huì)使用LeNet網(wǎng)絡(luò),眾所周知,這個(gè)網(wǎng)絡(luò)在手寫數(shù)字分類任務(wù)上表現(xiàn)得非常好。我們將會(huì)跑一個(gè)與原始的LeNet相比,稍微有些不同的版本的網(wǎng)絡(luò)。在這個(gè)網(wǎng)絡(luò)中,神經(jīng)元的sigmoid激活函數(shù)被替換為ReLu激活函數(shù)了。(補(bǔ)充:線性修正單元激活函數(shù),簡稱ReLu函數(shù),嘛,一般來說ReLuctant函數(shù)可以使得網(wǎng)絡(luò)的訓(xùn)練更快,提升訓(xùn)練效果,除了最后輸出層的激活函數(shù)有時(shí)不得不使用sigmoid函數(shù)外,隱含層中激活函數(shù)一律使用ReLu函數(shù)會(huì)比使用sigmoid函數(shù)取得更好的效果)
LeNet網(wǎng)絡(luò)的設(shè)計(jì)包含了卷積神經(jīng)網(wǎng)絡(luò)(CNN)的本質(zhì),在一些大型模型諸如用于ImageNet的模型中也依然通用。一般來說,它包含了一個(gè)卷積層后面跟著一個(gè)池化層,另外一個(gè)卷積層再跟一個(gè)池化層,加上兩個(gè)全連接層(近似與卷積多層感知機(jī)),我們把網(wǎng)絡(luò)定義在下面的文件中:
$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt
定義MNIST網(wǎng)絡(luò)
這個(gè)部分主要講解lenet_train_test.prototxt,其中詳細(xì)定義了LeNet模型,用于手寫數(shù)字分類。我們默認(rèn)你已經(jīng)很熟悉Google Protobuf,并且已經(jīng)閱讀過caffe中使用的protobuf定義(定義在$CAFFE_ROOT/src/caffe/proto/caffe.proto)。
接下來我們要具體根據(jù)protobuf寫下caffe的神經(jīng)網(wǎng)絡(luò),caffe::NetParameter(或是基于python,caffe.proto.caffe_pb2.NetParameter)。我們首先要給網(wǎng)絡(luò)定義一個(gè)名字:
name: "LeNet"編寫數(shù)據(jù)層
現(xiàn)在,我們要從我們之前創(chuàng)建的lmdb數(shù)據(jù)庫文件讀取MNIST的數(shù)據(jù)了。下面是定義的一個(gè)數(shù)據(jù)層:
layer {name: "mnist"type: "Data"transform_param {scale:0.00390625}data_param {source: "mnist_train_lmdb"backend: LMDBbatch_size: 64}top: "data"top: "label" }很明確地,我們可以看到,這個(gè)層的名字是mnist,類型是Data,并且他會(huì)從給定的lmdb文件中讀取數(shù)據(jù)。每次抽取的batch_size為64,并且限制讀入的像素的灰度范圍為[0,1)[0,1)。這里可能會(huì)有疑問了,為什么是0.003906250.00390625這個(gè)數(shù)?因?yàn)樗扔?span id="ze8trgl8bvbq" class="MathJax_Preview" style="color: inherit; display: none;">12561256。(補(bǔ)充:因?yàn)橥ǔWx入的每個(gè)像素的灰度范圍都是從0到255,這里意思就是做一個(gè)歸一化,目的就是可以讓后面的數(shù)據(jù)也可以更好處理)最后呢,這個(gè)層會(huì)產(chǎn)生兩個(gè)blob(補(bǔ)充:親切點(diǎn),叫它泡泡吧。好了不開玩笑了,就理解為一個(gè)單元就行了),一個(gè)是data的blob,一個(gè)是label的blob。(補(bǔ)充:訓(xùn)練時(shí),data的blob負(fù)責(zé)讀入訓(xùn)練數(shù)據(jù),label的blob負(fù)責(zé)讀入標(biāo)簽,很好理解)
編寫卷積層
讓我們來定義第一個(gè)卷積層:
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" }這一層接收了了前面的數(shù)據(jù)層送來的數(shù)據(jù),并生成了conv1層。上面定義了,生成的conv1層的輸出num_output有20個(gè)通道,卷積核的大小kernel_size為5(5×55×5的卷積核),步長stride為1(1×11×1 的步長)。
上面定義了兩個(gè)filler,一個(gè)是weight_filler,一個(gè)是bias_filler。這些filler讓我們可以初始化權(quán)重和偏置參數(shù)。對(duì)于weight_filler來說,我們使用xavier算法來自動(dòng)根據(jù)輸入?yún)?shù)數(shù)量和輸出神經(jīng)元個(gè)數(shù)決定初始化參數(shù)。對(duì)于bias_filler來說,我們很簡單地將其初始化為常數(shù),默認(rèn)的初始化的值是0。
還有幾個(gè)參數(shù)。lr_mult是當(dāng)前層的可學(xué)習(xí)參數(shù)的學(xué)習(xí)率的調(diào)整參數(shù)。這么理解可能比較繞,舉個(gè)例子說明會(huì)好一些。在這個(gè)例子中,我們?cè)O(shè)置第一個(gè)可學(xué)習(xí)參數(shù),即權(quán)重參數(shù),其學(xué)習(xí)率與程序運(yùn)行時(shí)給的學(xué)習(xí)率相同(1倍);然后是第二個(gè)可學(xué)習(xí)參數(shù),即偏置參數(shù),其學(xué)習(xí)率是程序運(yùn)行時(shí)給的學(xué)習(xí)率的2倍。這么做通常能讓神經(jīng)網(wǎng)絡(luò)更好地收斂。(補(bǔ)充:學(xué)習(xí)率會(huì)在后面定義,這里只要知道訓(xùn)練時(shí)caffe會(huì)送進(jìn)來網(wǎng)絡(luò)的學(xué)習(xí)率,在前面定義的lr_mult中,權(quán)重參數(shù)與那個(gè)學(xué)習(xí)率相同吧,而偏置參數(shù)會(huì)是那個(gè)學(xué)習(xí)率的2倍)
編寫池化層
呸呸呸(文檔上就是這么寫的,皮這一下很開心?),池化層更加容易定義:
layer {name: "pool1"type: "Pooling"pooling_param {kernel_size: 2stride: 2pool: MAX}bottom: "conv1"top: "pool1" }上面這一段定義了:我們對(duì)數(shù)據(jù)進(jìn)行最大化池化(max pooling),池化核的大小kernel_size為2(2×22×2 的池化核),步長stride為2(2×22×2的步長)。(不難看出相鄰池化區(qū)域之間沒有重疊部分)
相似地也可以照葫蘆畫瓢,編寫出第二個(gè)卷積層和池化層。可以到$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt中查看詳細(xì)的定義。
編寫全連接層
編寫全連接層也沒什么難的了:
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" }這里定義了一個(gè)全連接層(要知道在Caffe中全連接層用InnerProduct層表示,直譯就是內(nèi)積),輸出有500個(gè)(補(bǔ)充:這里不表示通道數(shù)了,表示輸出神經(jīng)元個(gè)數(shù))。其他所有的行都很眼熟,對(duì)吧?
編寫ReLu層
一個(gè)ReLu層也很簡單的:
layer {name: "relu1"type: "ReLu"bottom: "ip1"top: "ip1" }既然ReLu是一個(gè)對(duì)元素的操作。那么我們可以就地對(duì)它處理,以節(jié)省一些內(nèi)存。這里是通過直接把相同的名字給到bottom和top的blob**實(shí)現(xiàn)的(補(bǔ)充:嘛,就是直接自己連自己)。不過,當(dāng)然不能直接復(fù)制這個(gè)**blob的名字給其他類型的層。
在ReLu層之后,我們?cè)倬帉懥硗庖粋€(gè)內(nèi)積層(全連接層):
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" }(補(bǔ)充:基本都和前面一樣,但是輸出num_output是10個(gè),為什么?因?yàn)槭謱憯?shù)字是0-9,每一個(gè)輸出對(duì)應(yīng)一個(gè)數(shù)字,如果識(shí)別的是某個(gè)數(shù)字,那就輸出1就行了,這就是我們常說的one-hot編碼)
編寫損失層
最后我們還要寫一下?lián)p失層:
layer {name: "loss"type: "SoftmaxWithLoss"bottom: "ip2"bottom: "label" }這個(gè)softmax_loss層聲明了softmax和多項(xiàng)logistic損失(這樣可以解決時(shí)間并提高數(shù)值的穩(wěn)定性)。它需要輸入兩個(gè)blob,第一個(gè)會(huì)給出預(yù)測的結(jié)果,第二個(gè)是由2數(shù)據(jù)層提供的標(biāo)簽(還記得不?就在最開始的那個(gè)層定義的)。它并不會(huì)產(chǎn)生任何輸出值,它所做的就只是計(jì)算損耗函數(shù)的值,當(dāng)反向傳播開始時(shí)報(bào)告它(補(bǔ)充:命令行上可以看到),并且根據(jù)ip2層初始化梯度。這就是魔術(shù)真正開始實(shí)施的地方。
額外的提示:編寫層的規(guī)則
每一層的定義可以包含是否和何時(shí)他們會(huì)被網(wǎng)絡(luò)包含進(jìn)去的規(guī)則,就像下面這個(gè):
layer {// ...layer definition...include: { phase: TRAIN } }這是一項(xiàng)規(guī)則,基于當(dāng)前神經(jīng)網(wǎng)絡(luò)的狀態(tài),會(huì)判斷這一層是否應(yīng)該包含在網(wǎng)絡(luò)中。你也可以查看$CAFFE_ROOT/src/caffe/proto/caffe.proto來獲取更多有關(guān)層定義規(guī)則和模型架構(gòu)的信息。
在上面這個(gè)例子中,這一層只會(huì)在訓(xùn)練(TRAIN phase)階段被包含進(jìn)網(wǎng)絡(luò)。如果我們把TRAIN換成TEST,那么這一層將只會(huì)在測試階段被包含進(jìn)網(wǎng)絡(luò)。默認(rèn)來說,那里也不會(huì)有層級(jí)規(guī)則,一個(gè)層總是會(huì)被包含進(jìn)網(wǎng)絡(luò)。另外,lenet_train_test.prototxt中定義了兩個(gè)數(shù)據(jù)層(也有不同的batch_size),一個(gè)是用于訓(xùn)練階段的,而另一個(gè)是用于測試階段的。就如在lenet_solver.prototxt中定義的一樣,還有一個(gè)Accuracy層,僅僅是在測試階段(TEST phase)中被引入,以反饋模型在每100步迭代過程中的準(zhǔn)確率。
定義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補(bǔ)充:
上面也就是定義了一些訓(xùn)練的設(shè)置,比如學(xué)習(xí)率,迭代次數(shù),snapshot(意思是每迭代多少次保存一次模型)等等,不做贅述了。
為了省事,在我的筆記本上只是裝了CPU版本的caffe,所以跑的時(shí)候用的是CPU版本的跑。
下面這個(gè)東西自己看著改改就行了,后面會(huì)提到詳細(xì)信息:
# solver mode: CPU or GPU solver_mode: CPU訓(xùn)練和測試我們的模型
在你編寫了網(wǎng)絡(luò)定義(network definition protobuf)和解決方案(solver protobuf)文件之后,訓(xùn)練模型對(duì)你來說就很簡單了。直接運(yùn)行train_lenet.sh腳本文件,或者說自己直接用命令行來訓(xùn)練也行:
cd $CAFFE_ROOT ./examples/mnist/train_lenet.sh當(dāng)你運(yùn)行代碼時(shí),你會(huì)看到很多像這樣的信息一閃而過:
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.這些信息告訴了你每一層的細(xì)節(jié)信息:它的連接方式和輸出的大小,這些東西都很方便你調(diào)試。在初始化網(wǎng)絡(luò)結(jié)束之后,就會(huì)開始進(jìn)行訓(xùn)練:
I1203 net.cpp:142] Network initialization done. I1203 solver.cpp:36] Solver scaffolding done. I1203 solver.cpp:44] Solving LeNet基于前面對(duì)解決方案的設(shè)置,我們會(huì)打印訓(xùn)練過程中每100次迭代的loss函數(shù)的輸出值,并且每過500次迭代測試一次網(wǎng)絡(luò)。你會(huì)看到類似下面的信息:
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對(duì)于訓(xùn)練中每一次迭代,lr是那次迭代的學(xué)習(xí)率,并且loss就是訓(xùn)練中損失函數(shù)輸出的值。對(duì)于測試階段的輸出來說,score 0輸出的是準(zhǔn)確率,score 1輸出的是損失函數(shù)。
過了幾分鐘,訓(xùn)練就完成了。(補(bǔ)充: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.補(bǔ)充:可以看到這里的準(zhǔn)確率是0.9897,我在訓(xùn)練時(shí)得到的是0.991左右的準(zhǔn)確率,這個(gè)結(jié)果可以說比較理想了。
最后的模型,會(huì)存儲(chǔ)為二進(jìn)制protobuf文件,存為:
lenet_iter_10000如果你在某一個(gè)現(xiàn)實(shí)應(yīng)用的數(shù)據(jù)集中訓(xùn)練的話,你可以在你的應(yīng)用中將它作為一個(gè)訓(xùn)練好的模型直接調(diào)用。
額…怎么用GPU訓(xùn)練?
你剛剛就是用GPU訓(xùn)練的!所有的訓(xùn)練都是運(yùn)行在GPU上的。事實(shí)上,如果你想在CPU上訓(xùn)練,你可以很簡單地改變lenet_solver.prototxt中的一行:
# solver mode: CPU or GPU solver_mode: CPU然后你就可以在CPU上進(jìn)行訓(xùn)練了。這不是很簡單嗎?
MNIST是一個(gè)很小的數(shù)據(jù)集,所以就通信開銷來說使用GPU訓(xùn)練并不顯得有多大有時(shí)。在更大的數(shù)據(jù)集上訓(xùn)練那些更大更復(fù)雜的模型時(shí),比如ImageNet,CPU和GPU計(jì)算速度的差距會(huì)變得十分明顯。
如何在特定的步驟中減小學(xué)習(xí)率?
請(qǐng)查閱 lenet_multistep_solver.prototxt文件。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Caffe官方教程翻译(1):LeNet MNIST Tutorial的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三维重建学习(5):简单地从数学原理层面
- 下一篇: Caffe官方教程翻译(2):Web d