caffe 學習(2)——基本原理


參考 http://caffe.berkeleyvision.org/tutorial/

  • 表達:models和optimizations使用純文本文檔形式定義,不是用代碼定義;
  • 速度:適用於工業和科研中的模型和大數據
  • 模塊性:新任務和設置可以靈活擴展
  • 開源、社區

開始學習!

Blobs, Layers, and Nets: 一個Caffe模型的基本組成

Blobs: 標准數組和統一內存接口,用於存儲、通信和操作信息(數據和偏導流)

Layers:模型和計算

Nets:連接層

 

Blobs:

blob實際上是caffe中處理和傳輸的數據的包裝器,並同時兼具CPU和GPU之間的數據同步的能力。

數學上看,一個blob實際上是一個類似C中的N維數組。

 

caffe使用blob存儲和通信數據。blob提供一個統一的內存接口保存數據。caffe中常用的數據有image batches, model parameters and derivatives for optimization。

 

由於我們通常對於blobs的數值和差分感興趣,所以一個blob存儲兩塊內存:data and diff。前者是我們傳輸的正常數據,后者是網絡計算的梯度。

實際數據可以被存儲在CPU或GPU上,我們有兩種方式獲得它們:const方式(不改變數值),mutable方式(改變數值)。兩種方式如下定義:

const Dtype* cpu_data() const;
Dtype* mutable_cpu_data();

 在GPU上和對差分的操作是類似的。

這樣設計的原因在於:blob使用SyncedMem類同步CPU和GPU的數據,為了隱藏同步的細節和最小化數據傳輸使用兩種調用方式。

經驗方法是如果不想改變數值就一直使用const方式調用,不在自己定義的object上存儲指針。

 

Layers:

layer是模型的關鍵,計算的基礎單元。layer的操作有卷積、pool、內積、應用非線性elementwise變換(ReLU,sigmoid等),normalize,加載數據,計算損失(softmax和hinge)等。

每個layer從底層連接獲得輸入,輸出到頂層連接。

A layer with bottom and top blob.

每個layer定義三種關鍵計算:setup,forward和backward。

setup:模型初始化時,初始化這個layer和它的連接;

forward:從底層取出輸入,計算輸出,傳遞到頂層;

backward:從頂層獲得輸入,計算梯度,發送到底層。有參數的層計算關於它的參數的梯度,並在內部存儲。

特別地,每個layer有兩種forward和backward實現,分別是基於CPU和GPU。如果沒有實現GPU版本,layer默認使用CPU函數,這給快速實驗帶來了便利。

 

Nets:

net聯合定義一個函數和它的梯度通過合成和自動差分。每個layer的輸出的合成計算一個給定任務的函數,每個layer的backward的合成計算學習任務的loss的梯度。

caffe model是端到端的機器學習引擎。

net是layers的集合,layers之間使用有向無環圖(Directed acyclic graph, DAG)連接。

net使用純文本文檔定義layers和他們指尖的連接。一個簡單的logistic regression分類器如下定義:

Softmax Regression

name: "LogReg"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  data_param {
    source: "input_leveldb"
    batch_size: 64
  }
}
layer {
  name: "ip"
  type: "InnerProduct"
  bottom: "data"
  top: "ip"
  inner_product_param {
    num_output: 2
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip"
  bottom: "label"
  top: "loss"
}

 模型初始化使用Net::Init().這個初始化主要做兩件事:創建blobs和layers搭建整個有向無環圖(DAG),調用layers的Setup()函數。它也做一些統計工作,例如校驗整個網絡架構的正確性。

注意:網絡的構建是設備無關的。構建之后,網絡是運行在CPU或GPU上是通過一個單獨的定義實現的Caffe::mode(),設置Caffe::set_mode()。

 

模型是在純文本protocol buffer模式.prototxt中定義的,學習好的模型被序列化為binary protocol buffer,存儲在 .caffemodel文件中。

caffe使用Google Protocol Buffer出於以下幾個優點:

序列化時最小化binary string的size,有效序列化,文本格式兼容binary version,在多種語言中都有接口實現,例如C++和Python。這些優點使得在caffe建模靈活可拓展。

 

Forward and Backward: 前線傳播和后向傳播

前向傳播和后向傳播是一個網絡的關鍵計算。

Forward and Backward

以簡單的logistic regression為例:

前向傳播計算給定輸入的輸出,caffe依次排列每個layer定義的計算操作,計算模型表達的函數。前向傳播方向自底向上:

Forward pass

輸入數據x通過一個內積層輸出g(x),接着再通過一個softmax計算獲得h(g(x)),得到輸出softmax loss。

后向傳播計算給定的loss的梯度。后向傳播時,caffe逆向排列每層定義的梯度,通過自動差分計算整個模型的梯度。后向傳播方向自頂向下:

Backward pass

后向傳播從loss開始,計算loss關於輸出的梯度∂fw/∂h。關於模型剩余部分的梯度被一層層的根據鏈式法則求解。有參數的layer,像INNER_PRODUCT layer,計算關於layer參數的梯度∂fw/∂Wip,用於更新layer參數。

 

這些計算從定義model開始立即生效,caffe自動計划和執行前向和后向傳播:

Net::Forward()和Net::Backward()方法執行相關的傳播,而Layer::Forward()和Layer::Backward()在每個步驟中負責具體計算;

每個layer的計算類型有cpu和gpu兩種(forward_{cpu, gpu}(), backward_{cpu, gpu}())。處於便利或限制因素考慮,一個layer允許只實現CPU或GPU版本。

 

Solver最優化一個model通過首先調用前向傳播獲得output和loss,然后調用后向傳播生成model的梯度,更新weight和參數,以最小化loss。

Solver,Net,和Layer之間的分工使得caffe模塊化,易於開發使用。

 

Loss:損失函數

caffe中,像眾多機器學習技術一樣,learning過程有loss function驅動。loss function也常被稱為error,cost或objective function。損失函數指定了學習的目標,將參數設置(也就是當前網絡的權重)映射為一個標量數值,來表達這些參數設置的“badness”程度。因此,學習的目標就是找出一組參數設置最小化損失函數。

caffe里的loss通過網絡的前向傳播計算。每個layer取一組輸入(bottom)blobs,產生一組輸出(top)blobs。這些layer的輸出可能用於計算loss function。在one-verse-all分類任務中,loss function的一個典型選擇是SoftmaxWithLoss函數,網絡定義中如下定義:

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "pred"
  bottom: "label"
  top: "loss"
}

 在一個SoftmaxWithLoss函數中,top blobs是一個標量,取整個mini-batch的loss的平均值(mini-batch中每個樣本的預測label pred 與實際label label的誤差的平均值)。

 

Loss Weights:

對於有多個layer的net來說,每個layer產生一個loss,loss weights用來指定這些loss的相對重要性。

按照約定,caffe中類型type中帶有后綴Loss的layer對loss function有貢獻,而其他的layers被默認為純粹用於中間計算。然而,任何layer都可以被用作loss,通過在layer定義中添加一個field: loss_weight: <float>。每個帶有后綴Loss的layer默認loss_weight:1對於第一個top blob,其他的top blob的loss_weight:0;其它layer默認對所有top blob的loss_weight:0。所以上面的SoftmaxWithLosslayer等價於如下定義

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "pred"
  bottom: "label"
  top: "loss"
  loss_weight: 1
}

 然而,任何能夠后向傳播的layer都可能被給一個非零的loss_weight,例如允許正則化網絡中間層的激勵。對於非單個輸出的layer,如果被賦予一個非零loss_weight,loss通過對blob的所有元素求和簡單計算。

caffe中最終loss通過網絡的總體加權loss的求和被計算,如下偽碼所示:

loss := 0
for layer in layers:
  for top, loss_weight in layer.tops, layer.loss_weights:
    loss += loss_weight * sum(top)

 

Solver:

solver精心排列網絡的前向推斷和后向梯度構成參數更新過程,以保證loss最小化。學習過程被分為Solver監督管理最優化和生成參數更新過程,Net計算loss和梯度。

caffe的solver包括:

  • Stochastic Gradient Descent (type: "SGD"),
  • AdaDelta (type: "AdaDelta"),
  • Adaptive Gradient (type: "AdaGrad"),
  • Adam (type: "Adam"),
  • Nesterov’s Accelerated Gradient (type: "Nesterov") and
  • RMSprop (type: "RMSProp")

Solver構建最優化過程,創建學習的訓練網絡和評估的測試網絡;迭代地調用forward/backward函數和更新參數;周期性地評估測試網絡;在最優化過程中給model和solver狀態拍快照(緩存?)

在每個迭代步驟中,調用網絡前向傳播計算輸出和loss;調用網絡后向傳播計算梯度;根據solver方法,將所得梯度用於參數更新;根據學習率,history,和方法更新solver狀態。

像caffe的model一樣,caffe solver有CPU和GPU兩種模式。

 

solver方法針對於解決loss最小化的一般最優化問題。對數據集D,最優化目標是所有數據的平均loss,如下式所示:

其中fW(X(i))是數據項X(i)的loss,r(W)是正則化項。|D|在實際中可能非常大,所以在每個solver迭代過程中,我們使用目標的stochastic近似,使用一個mini-batch的數據N<<|D|,loss如下所示:

 

model計算前向傳播時計算fW,后向傳播時計算梯度

參數更新被solver構建,來源於error梯度,正則化梯度和其他特定項。

 

SGD:Stochastic gradient descent (type: "SGD")

更新權重W通過一個線性聯合負梯度和先前的權重更新。學習率α是負梯度的加權系數,momentum μ是前面權重更新的加權系數。

 一般情況下,我們使用下面公式根據以前權重更新Vt和當前權重Wt,計算迭代次數為t+1時的更新值Vt+1和更新權重Wt+1

學習過程的超參數("hyperparameters" α和μ)可能需要一些調整以獲得最后的結果。如果不確定從哪里開始調整,參考下面的經驗法則("Rules of thumb"),更多的信息可以參見Leon Bottou's Stochastic Gradient Descent Tricks(L. Bottou.     Stochastic Gradient Descent Tricks.     Neural Networks: Tricks of the Trade: Springer, 2012.)。

 

設置學習率α和momentum μ的經驗法則:

使用SGD方法的deep learning,初始化學習率的一個好的策略是設置一個值約,然后在訓練過程中,每當loss達到一個明顯的谷點時,使它降低一個常數因子(例如10),這個過程重復幾次。通常情況下momentum μ = 0.9或相似的值。通過在迭代中平滑權重更新,momentum使得基於SGD的deep learning過程更平穩更快。

這是Krizhevsky et al.[1]中使用的策略。caffe使得這個策略在SolverParameter中很容易實現,見./example/imagenet/alexnet_solver.prototxt。

如要使用這樣的超參數調整策略,你可以將下面幾行放入你的solver的prototxt中的某位置:

base_lr: 0.01     # begin training at a learning rate of 0.01 = 1e-2

lr_policy: "step" # learning rate policy: drop the learning rate in "steps"
                  # by a factor of gamma every stepsize iterations

gamma: 0.1        # drop the learning rate by a factor of 10
                  # (i.e., multiply it by a factor of gamma = 0.1)

stepsize: 100000  # drop the learning rate every 100K iterations

max_iter: 350000  # train for 350K iterations total

momentum: 0.9

 上面的設置中,momentum μ始終等於0.9。訓練開始時的前100,000次迭代,學習率base_lr ,然后乘以gamma,以學習率訓練第100K-200K次迭代,然后第200K至300K迭代使用學習率,最后直到350K次迭代,學習率為

注意到在很多次迭代后,momentum μ有效地使用一個因子乘以更新的size,所以如果增加μ,也應該相應地降低學習率α。反之亦然。

例如μ = 0.9時,我們有一個有效地更新size乘子;如果我們增加momentum到μ = 0.99時,我們已經增加更新size乘子到100,所以應該降低學習率以一個因子10(乘以0.1)。

注意上面的設置僅僅是指導作用,並不保證在每種情況下最優,甚至某些情況下根本就無效。如果學習過程中,例如初夏非常大的或者NaN或者inf的loss值或輸出,嘗試降低初始學習率base_lr,重新訓練,直到你發現base_lr值有效。

[1] A. Krizhevsky, I. Sutskever, and G. Hinton.     ImageNet Classification with Deep Convolutional Neural Networks.     Advances in Neural Information Processing Systems, 2012.

 

AdaDelta: (type: "AdaDelta")

這個方法是一個"穩定學習率的方法“,它像SGD一樣基於梯度進行最優化,更新形式如下:

[1] M. Zeiler     ADADELTA: AN ADAPTIVE LEARNING RATE METHOD.     arXiv preprint, 2012.

RMS:均方根值,Root-Mean-Square

 

AdaGrad: adaptive gradient (type: "AdaGrad")

是一個類似於SGD的基於梯度的優化算法,企圖以非常的predictive但幾乎看不見的特征的形式進行大海撈針("find needles in haystacks in the form of very predictive but rarely seen features", in Duchi et al.'s words)。給定所有前面迭代時的更新信息,更新公式如下所示:

實際上,對於權重W ,AdaGrad實現使用僅僅O(d)的額外存儲空間為歷史gradient信息。

[1] J. Duchi, E. Hazan, and Y. Singer.     Adaptive Subgradient Methods for Online Learning and Stochastic Optimization.     The Journal of Machine Learning Research, 2011.

 

Adam:(type: "Adam")

也是基於梯度的最優化方法,包含一個自適應矩估計(mt,vt),能夠被視為AdaGrad的推廣,更新形式如下:

Kingma等人提出使用作為默認值,caffe使用momentum, momentum2, delta分別表示

[1] D. Kingma, J. Ba.     Adam: A Method for Stochastic Optimization.     International Conference for Learning Representations, 2015.

 

NAG: Nesterov's accelerated gradient (type: "Nesterov")

是一種最優的凸優化方法,實現了收斂率,而不是。盡管實現收斂率所要求的假設典型地不支持使用caffe訓練的神經網絡,由於非平滑性和非凸性。實際中NAG是一種非常有效的方法,去最優化特定形式的deep learning架構,例如Sutskever等人生命的deep MNIST autoencoders。

權重更新看起來與SGD非常相似:

與SGD的區別在於SGD中使用梯度中的W不同,SGD中權重設置W基於誤差梯度,NAG中我們采用權重加上momentum

[1] Y. Nesterov.    A Method of Solving a Convex Programming Problem with Convergence Rate O(1/ √  O(1/k)
.     Soviet Mathematics Doklady, 1983.

[2] I. Sutskever, J. Martens, G. Dahl, and G. Hinton.     On the Importance of Initialization and Momentum in Deep Learning.     Proceedings of the 30th International Conference on Machine Learning, 2013

 

RMSprop: (type: "RMSprop")

是一個基於梯度的最優化方法,更新形式如下:

如果梯度更新導致震盪,梯度降低為(1 - δ)倍,否則增加δ。默認的δ值(rms_decay)為0.02。

[1] T. Tieleman, and G. Hinton.     RMSProp: Divide the gradient by a running average of its recent magnitude.     COURSERA: Neural Networks for Machine Learning.Technical report, 2012.

 

Scaffolding:拍快照

solver的scanffolding准備最優化方法和崔思華模型在Solver::Presolve()方法中。

> caffe train -solver examples/mnist/lenet_solver.prototxt
I0902 13:35:56.474978 16020 caffe.cpp:90] Starting Optimization
I0902 13:35:56.475190 16020 solver.cpp:32] Initializing solver from parameters:
test_iter: 100
test_interval: 500
base_lr: 0.01
display: 100
max_iter: 10000
lr_policy: "inv"
gamma: 0.0001
power: 0.75
momentum: 0.9
weight_decay: 0.0005
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
solver_mode: GPU
net: "examples/mnist/lenet_train_test.prototxt"

Net initialization

I0902 13:35:56.655681 16020 solver.cpp:72] Creating training net from net file: examples/mnist/lenet_train_test.prototxt
[...]
I0902 13:35:56.656740 16020 net.cpp:56] Memory required for data: 0
I0902 13:35:56.656791 16020 net.cpp:67] Creating Layer mnist
I0902 13:35:56.656811 16020 net.cpp:356] mnist -> data
I0902 13:35:56.656846 16020 net.cpp:356] mnist -> label
I0902 13:35:56.656874 16020 net.cpp:96] Setting up mnist
I0902 13:35:56.694052 16020 data_layer.cpp:135] Opening lmdb examples/mnist/mnist_train_lmdb
I0902 13:35:56.701062 16020 data_layer.cpp:195] output data size: 64,1,28,28
I0902 13:35:56.701146 16020 data_layer.cpp:236] Initializing prefetch
I0902 13:35:56.701196 16020 data_layer.cpp:238] Prefetch initialized.
I0902 13:35:56.701212 16020 net.cpp:103] Top shape: 64 1 28 28 (50176)
I0902 13:35:56.701230 16020 net.cpp:103] Top shape: 64 1 1 1 (64)
[...]
I0902 13:35:56.703737 16020 net.cpp:67] Creating Layer ip1
I0902 13:35:56.703753 16020 net.cpp:394] ip1 <- pool2
I0902 13:35:56.703778 16020 net.cpp:356] ip1 -> ip1
I0902 13:35:56.703797 16020 net.cpp:96] Setting up ip1
I0902 13:35:56.728127 16020 net.cpp:103] Top shape: 64 500 1 1 (32000)
I0902 13:35:56.728142 16020 net.cpp:113] Memory required for data: 5039360
I0902 13:35:56.728175 16020 net.cpp:67] Creating Layer relu1
I0902 13:35:56.728194 16020 net.cpp:394] relu1 <- ip1
I0902 13:35:56.728219 16020 net.cpp:345] relu1 -> ip1 (in-place)
I0902 13:35:56.728240 16020 net.cpp:96] Setting up relu1
I0902 13:35:56.728256 16020 net.cpp:103] Top shape: 64 500 1 1 (32000)
I0902 13:35:56.728270 16020 net.cpp:113] Memory required for data: 5167360
I0902 13:35:56.728287 16020 net.cpp:67] Creating Layer ip2
I0902 13:35:56.728304 16020 net.cpp:394] ip2 <- ip1
I0902 13:35:56.728333 16020 net.cpp:356] ip2 -> ip2
I0902 13:35:56.728356 16020 net.cpp:96] Setting up ip2
I0902 13:35:56.728690 16020 net.cpp:103] Top shape: 64 10 1 1 (640)
I0902 13:35:56.728705 16020 net.cpp:113] Memory required for data: 5169920
I0902 13:35:56.728734 16020 net.cpp:67] Creating Layer loss
I0902 13:35:56.728747 16020 net.cpp:394] loss <- ip2
I0902 13:35:56.728767 16020 net.cpp:394] loss <- label
I0902 13:35:56.728786 16020 net.cpp:356] loss -> loss
I0902 13:35:56.728811 16020 net.cpp:96] Setting up loss
I0902 13:35:56.728837 16020 net.cpp:103] Top shape: 1 1 1 1 (1)
I0902 13:35:56.728849 16020 net.cpp:109]     with loss weight 1
I0902 13:35:56.728878 16020 net.cpp:113] Memory required for data: 5169924

Loss

I0902 13:35:56.728893 16020 net.cpp:170] loss needs backward computation.
I0902 13:35:56.728909 16020 net.cpp:170] ip2 needs backward computation.
I0902 13:35:56.728924 16020 net.cpp:170] relu1 needs backward computation.
I0902 13:35:56.728938 16020 net.cpp:170] ip1 needs backward computation.
I0902 13:35:56.728953 16020 net.cpp:170] pool2 needs backward computation.
I0902 13:35:56.728970 16020 net.cpp:170] conv2 needs backward computation.
I0902 13:35:56.728984 16020 net.cpp:170] pool1 needs backward computation.
I0902 13:35:56.728998 16020 net.cpp:170] conv1 needs backward computation.
I0902 13:35:56.729014 16020 net.cpp:172] mnist does not need backward computation.
I0902 13:35:56.729027 16020 net.cpp:208] This network produces output loss
I0902 13:35:56.729053 16020 net.cpp:467] Collecting Learning Rate and Weight Decay.
I0902 13:35:56.729071 16020 net.cpp:219] Network initialization done.
I0902 13:35:56.729085 16020 net.cpp:220] Memory required for data: 5169924
I0902 13:35:56.729277 16020 solver.cpp:156] Creating test net (#0) specified by net file: examples/mnist/lenet_train_test.prototxt

Completion

I0902 13:35:56.806970 16020 solver.cpp:46] Solver scaffolding done.

I0902 13:35:56.806984 16020 solver.cpp:165] Solving LeNet
 
更新參數
實際的參數更新是通過solver完成,然后在Solver::ComputeUpdateValue()方法中應用到網絡參數。ComputeUpdateValue()方法插入任意權重衰減r(W)到權重梯度獲得最終梯度。然后這些梯度被學習率調整尺度,更新被存儲在每個參數blob的diff域中。最后每個參數blob的Blob::Update()方法被調用,進行最終的更新,也就是Blob的data域減去diff域。
 
Snapshotting and Resuming:拍快照和恢復
solver在訓練過程中對權重和本身狀態拍快照,方法為Solver::Snapshot()和Solver::SnapshotSolverState()。權重快照輸出當前學習得到的model,而且solver的snapshot允許訓練過程從某一給定點恢復,方法是Solver::Restore()和Solver::RestoreSolverState()。
權重被無擴展名地保存,solver的狀態被保存為.solverstate擴展名。這兩種文件都有一個_iter_N后綴表示快照的重復次數。
 
Snapshotting在solver定義的prototxt文件中如下構建
# The snapshot interval in iterations.
snapshot: 5000
# File path prefix for snapshotting model weights and solver state.
# Note: this is relative to the invocation of the `caffe` utility, not the
# solver definition file.
snapshot_prefix: "/path/to/model"
# Snapshot the diff along with the weights. This can help debugging training
# but takes more storage.
snapshot_diff: false
# A final snapshot is saved at the end of training unless
# this flag is set to false. The default is true.
snapshot_after_train: true


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM