1. 程式人生 > >Caffe原始碼導讀

Caffe原始碼導讀

轉自:https://ymgd.github.io/codereader/2016/10/20/caffe_sourcecode_analysis/

1.前言

目前的影象和自然語言處理很多地方用到了神經網路/深度學習相關的知識,神奇的效果讓廣大身處IT一線的程式猿GG們躍躍欲試,不過看到深度學習相關一大串公式之後頭皮發麻,又大有放棄的想法。

從工業使用的角度來說,不打算做最前沿的研究,只是用已有的方法或者成型的框架來完成一些任務,也不用一上來就死啃理論,倒不如先把神經網路看得簡單一點,視作一個搭積木的過程,所謂的卷積神經網路(CNN)或者迴圈神經網路(RNN)等無非是積木塊不一樣(層次和功能不同)以及搭建的方式不一樣,再者會有一套完整的理論幫助我們把搭建的積木模型最好地和需要完成的任務匹配上。

大量的數學背景知識可能大家都忘了,但是每天敲程式碼的習慣並沒有落下,所以說不定以優秀的深度學習開源框架程式碼學習入手,也是一個很好的神經網路學習切入點。

這裡給大家整理和分享的是使用非常廣泛的深度學習框架caffe,這是一套最早起源於Berkeley的深度學習框架,廣泛應用於神經網路的任務當中,大量paper的實驗都是用它完成的,而國內電商等網際網路公司的大量計算機視覺應用也是基於它完成的。程式碼結構清晰,適合學習。

2.Caffe程式碼結構

2.1 總體概述

典型的神經網路是層次結構,每一層會完成不同的運算(可以簡單理解為有不同的功能),運算的層疊完成前向傳播運算,“比對標準答案”之後得到“差距(loss)”,還需要通過反向傳播來求得修正“積木塊結構(引數)”所需的元件,繼而完成引數調整。

所以caffe也定義了環環相扣的類,來更好地完成上述的過程。我們看到這裡一定涉及資料網路層網路結構最優化網路幾個部分,在caffe中同樣是這樣一個想法,caffe的原始碼目錄結構如下。

在很多地方都可以看到介紹說caffe種貫穿始終的是Blob,Layer,Net,Solver這幾個大類。這四個大類分別負責資料傳輸、網路層次、網路骨架與引數求解策略,呈現一個自下而上,環環相扣的狀態。在原始碼中可以找到對應這些名稱的實現,詳細說來,這4個部分分別負責:

  • Blob:是資料傳輸的媒介,神經網路涉及到的輸入輸出資料,網路權重引數等等,其實都是轉化為Blob資料結構來儲存的。
  • Layer:是神經網路的基礎單元,層與層間的資料節點、前後傳遞都在該資料結構中被實現,因神經網路網路中設計到多種層,這裡layers下實現了卷積層、激勵層,池化層,全連線層等等“積木元件”,豐富度很高。
  • Net:是網路的整體搭建骨架,整合Layer中的層級機構組成網路。
  • Solver:是網路的求解優化策略,讓你用各種“積木”搭建的網路能最適應當前的場景下的樣本,如果做深度學習優化研究的話,可能會修改這個模組。

2.2 程式碼閱讀順序建議

在對整體的結構有一個大致的印象以後,就可以開始閱讀原始碼了,一個參考的閱讀順序大概是:

Step 1caffe.proto:對應目錄 caffe-master\src\caffe\proto\caffe.proto

Step 2Hpp檔案:包括

  • solver.hpp — caffe-master\include\caffe\net.hpp
  • net.hpp — caffe-master\include\caffe\net.hpp
  • layer.hpp — caffe-master\include\caffe\layer.hpp
  • blob.hpp — caffe-master\include\caffe\blob.hpp

上面d,c,b,a這4個部分實際上是自底向上的結構。

Step 3Cpp/cu檔案:對應上面提到的blob、net、solver的具體實現,所以你會看到blob.cpp,net.cpp,solver.cpp,但是注意,沒有layer.cpp,而是可以看到\src\caffe\layers下有派生出的各種對應各種神經網路層的類,比如\src\caffe\layers\data_layer.cpp, conv_layer.cpp, relu_layer.cpp, pooling_layer.cpp, inner_product_layer.cpp等。(通常說來,caffe框架已經實現了很通用的網路結構,如果有自己的需求,新增一些新的層次即可)

Step 4tools檔案caffe提供的工具,目錄在caffe-master\tools,例如計算影象均值,調優網路,視覺化等。

2.3 原始碼主線結構圖

caffe程式碼的一個精簡原始碼主線結構圖如下:

2.4 程式碼細節

2.4.1 caffe.proto

caffe.proto是建議第一個閱讀的部分,它位於…\src\caffe\proto目錄下。首先要說明的是Google Protocol Buffer(簡稱 Protobuf) 是Google 公司內部的混合語言資料標準,是一種輕便高效的結構化資料儲存格式,可以用於結構化資料序列化,或者說序列化。用來做資料儲存或 RPC 資料交換格式。caffe.proto執行後會生成caffe.pb.cc和caffe.pb.h兩個檔案,包含了很多結構化資料。

caffe.proto的一個message定義了一個需要傳輸的引數結構體,Package caffe可以把caffe.proto裡面的所有檔案打包存在caffe類裡面。大致的程式碼框架如下:

package caffe;
message BlobProto {...} 
message BlobProtoVector {...} 
message Datum {...}
...
message V0LayerParameter {...}

一個message定義了一個需要傳輸的引數結構體,Required是必須有值的, optional是可選項,repeated表示後面單元為相同型別的一組向量。比如:

message NetParameter {
  optional string name = 1; // consider giving the network a name
  // DEPRECATED. See InputParameter. The input blobs to the network.
  repeated string input = 3;
  // DEPRECATED. See InputParameter. The shape of the input blobs.
  repeated BlobShape input_shape = 8;

  // 4D input dimensions -- deprecated.  Use "input_shape" instead.
  // If specified, for each input blob there should be four
  // values specifying the num, channels, height and width of the input blob.
  // Thus, there should be a total of (4 * #input) numbers.
  repeated int32 input_dim = 4;

  // Whether the network will force every layer to carry out backward operation.
  // If set False, then whether to carry out backward is determined
  // automatically according to the net structure and learning rates.
  optional bool force_backward = 5 [default = false];
  // The current "state" of the network, including the phase, level, and stage.
  // Some layers may be included/excluded depending on this state and the states
  // specified in the layers' include and exclude fields.
  optional NetState state = 6;

  // Print debugging information about results while running Net::Forward,
  // Net::Backward, and Net::Update.
  optional bool debug_info = 7 [default = false];

  // The layers that make up the net.  Each of their configurations, including
  // connectivity and behavior, is specified as a LayerParameter.
  repeated LayerParameter layer = 100;  // ID 100 so layers are printed last.

  // DEPRECATED: use 'layer' instead.
  repeated V1LayerParameter layers = 2;
}

Caffe.proto每個message在編譯後都會自動生成一些函式,大概是這樣一個命名規範:Set_+field 設定值的函式命名,has_ 檢查field是否已經被設定, clear_用於清理field,mutable_用於設定string的值,_size用於獲取 重複的個數。

大概有這麼些Message類別:
屬於blob的BlobProtoBlobProtoVectorDatum
屬於layer的FillerParameterLayerParameterArgMaxParameterTransformationParameterLossParameterAccuracyParameterConcatParameterContrastiveLossParameterConvolutionParameterDataParameterDropoutParameterDummyDataParameterEltwiseParameterExpParameterHDF5DataParameterHDF5OutputParameterHingeLossParameterImageDataParameterInfogainLossParameterInnerProductParameter,LRNParameterMemoryDataParameterMVNParameterPoolingParameterPowerParameterPythonParameterReLUParameterSigmoidParameterSliceParameterSoftmaxParameterTanHParameterThresholdParameter等。
屬於net的NetParameterSolverParameterSolverStateNetStateNetStateRuleParamSpec

2.4.2 Blob

前面說到了Blob是最基礎的資料結構,用來儲存網路傳輸 過程中產生的資料和學習到的一些引數。比如它的上一層Layer中會用下面的形式表示學習到的引數:vector<shared_ptr<Blob<Dtype> > > blobs_;裡面的blob就是這裡定義的類。 部分程式碼如下:

template <typename Dtype>
class Blob {
 public:
  Blob()
       : data_(), diff_(), count_(0), capacity_(0) {}

  /// @brief Deprecated; use <code>Blob(const vector<int>& shape)</code>.
  explicit Blob(const int num, const int channels, const int height, const int width);
  explicit Blob(const vector<int>& shape);

  /// @brief Deprecated; use <code>Reshape(const vector<int>& shape)</code>.
  void Reshape(const int num, const int channels, const int height,
      const int width);
...

其中template <typename Dtype>表示函式模板,Dtype可以表示int,double等資料型別。Blob是四維連續陣列(4-D contiguous array, type = float32), 如果使用(n, k, h, w)表示的話,那麼每一維的意思分別是:

  • n: number. 輸入資料量,比如進行sgd時候的mini-batch大小。
  • c: channel. 如果是影象資料的話可以認為是通道數量。
  • h,w: height, width. 如果是影象資料的話可以認為是圖片的高度和寬度。

實際Blob在(n, k, h, w)位置的值物理位置為((n * K + k) * H + h) * W + w。

Blob內部有兩個欄位data和diff。Data表示流動資料(輸出資料),而diff則儲存BP的梯度。

關於blob引入的標頭檔案可以參考下面說明做理解: #include “caffe/common.hpp”單例化caffe類,並且封裝了boost和cuda隨
機數生成的函式,提供了統一介面。
#include “caffe/proto/caffe.pb.h”上一節提到的標頭檔案
#include “caffe/syncedmem.hpp”主要是分配記憶體和釋放記憶體的。而class SyncedMemory定義了記憶體分配管理和CPU與GPU之間同步的函式。Blob會使用SyncedMem自動決定什麼時候去copy data以提高執行效率,通常情況是僅當gnu或cpu修改後有copy操作。
#include “caffe/util/math_functions.hpp”封裝了很多cblas矩陣運算,基本是矩陣和向量的處理函式。

關於Blob裡定義的函式的簡單說明如下:

  • Reshape()可以改變一個blob的大小;
  • ReshapeLike()為data和diff重新分配一塊空間,大小和另一個blob的一樣;
  • Num_axes()返回的是blob的大小;
  • Count()計算得到count=numchannelsheight*width。
  • Offset()可得到輸入blob資料(n,c,h,w)的偏移量位置;
  • CopyFrom()從source拷貝資料,copy_diff來作為標誌區分是拷貝data還是 diff。
  • FromProto()從proto讀資料進來,其實就是反序列化。 
  • ToProto()把blob資料儲存到proto中。 ShareDate()/ShareDiff()從other的blob複製data和diff的值;

2.4.3 Layer

Layer是網路的基本單元(“積木”),由此派生出了各種層類。如果做資料特徵表達相關的研究,需要修改這部分。Layer類派生出來的層類通過這 實現兩個虛擬函式Forward()Backward(),產生了各種功能的 層類。Forward是從根據bottom計算top的過程,Backward則剛好相反。 在網路結構定義檔案(*.proto)中每一層的引數bottom和top數目 就決定了vector中元素數目。

一起來看看Layer.hpp

#include <algorithm>
#include <string>
#include <vector>

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/layer_factory.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/math_functions.hpp"

/**
 Forward declare boost::thread instead of including boost/thread.hpp
 to avoid a boost/NVCC issues (#1009, #1010) on OSX.
 */
namespace boost { class mutex; }

namespace caffe {
...
template <typename Dtype>
class Layer {
 public:
  /**
   * You should not implement your own constructor. Any set up code should go
   * to SetUp(), where the dimensions of the bottom blobs are provided to the
   * layer.
   */
  explicit Layer(const LayerParameter& param)
    : layer_param_(param), is_shared_(false) {
      // Set phase and copy blobs (if there are any).
      phase_ = param.phase();
      if (layer_param_.blobs_size() > 0) {
        blobs_.resize(layer_param_.blobs_size());
        for (int i = 0; i < layer_param_.blobs_size(); ++i) {
          blobs_[i].reset(new Blob<Dtype>());
          blobs_[i]->FromProto(layer_param_.blobs(i));
        }
      }
    }
  virtual ~Layer() {}
...

Layer中三個重要引數:
LayerParameter layer_param_;這個是protobuf檔案中儲存的layer引數。
vector<share_ptr<Blob<Dtype>>> blobs_;這個儲存的是layer學習到的引數。
vector<bool> param_propagate_down_;這個bool表示是否計算各個 blob引數的diff,即傳播誤差。

包含了一些基本函式:
Layer()嘗試從protobuf讀取引數; SetUp()根據實際的引數設定進行實現,對各種型別的引數初始化;
Forward()和Backward()對應前向計算和反向更新,輸入統一都是 bottom,輸出為top,其中Backward裡面有個propagate_down引數, 用來表示該Layer是否反向傳播引數。
Caffe::mode()具體選擇使用CPU或GPU操作。

2.4.4 Net

Net是網路的搭建部分,將Layer所派生出層類組合成網路。 Net用容器的形式將多個Layer有序地放在一起,它自己的基本功能主要 是對逐層Layer進行初始化,以及提供Update( )的介面用於更新網路引數, 本身不能對引數進行有效地學習過程。

 vector<shared_ptr<Layer<Dtype> > > layers_;

Net也有它自己的Forward()Backward(),他們是對整個網路的前向和反向傳導,呼叫可以計算出網路的loss。 Net由一系列的Layer組成(無迴路有向圖DAG),Layer之間的連線由一個文字檔案描述。模型初始化Net::Init()會產生blob和layer並呼叫Layer::SetUp。 在此過程中Net會報告初始化程序。這裡的初始化與裝置無關,在初始化之後通過Caffe::set_mode()設定Caffe::mode()來選擇執行平臺CPU或 GPU,結果是相同的。

裡面比較重要的函式的簡單說明如下:

Init()初始化函式,用於建立blobs和layers,用於呼叫layers的setup函式來初始化layers。 
ForwardPrefilled()用於前饋預先填滿,即預先進行一次前饋。
Forward()把網路輸入層的blob讀到net_input_blobs_,然後進行前饋,計 算出loss。Forward的過載,只是輸入層的blob以string的格式傳入。
Backward()對整個網路進行反向傳播。 
Reshape()用於改變每層的尺寸。 Update()更新params_中blob的值。 
ShareTrainedLayersWith(Net* other)從Other網路複製某些層。
CopyTrainedLayersFrom()呼叫FromProto函式把源層的blob賦給目標 層的blob。
ToProto()把網路的引數存入prototxt中。 
bottom_vecs_存每一層的輸入blob指標 
bottom_id_vecs_存每一層輸入(bottom)的id 
top_vecs_存每一層輸出(top)的blob 
params_lr()params_weight_decay()學習速率和權重衰減; 
blob_by_name()判斷是否存在名字為blob_name的blob;
FilterNet()給定當前phase/level/stage,移除指定層。 
StateMeetsRule()中net的state是否滿足NetStaterule。 
AppendTop()在網路中附加新的輸入或top的blob。 
AppendBottom()在網路中附加新的輸入或bottom的blob。 
AppendParam()在網路中附加新的引數blob。
GetLearningRateAndWeightDecay()收集學習速率和權重衰減,即更新params_、params_lr_和params_weight_decay_ ;

更多細節可以參考這篇部落格

2.4.5 Solver

Solver是Net的求解部分,研究深度學習求解與最優化的同學會修改這部分內容。Solver類中包含一個Net指標,主要實現了訓練模型引數所採用的優化演算法,它所派生的類完成對整個網路進行訓練。

shared_ptr<Net<Dtype> > net_;

不同的模型訓練方法通過過載函式ComputeUpdateValue( )實現計算update引數的核心功能。 最後當進行整個網路訓練過程(即執行Caffe訓練模型)的時 候,會執行caffe.cpp中的train( )函式,而這個train函式實際上是實 例化一個Solver物件,初始化後呼叫了Solver中的Solve( )方法。

標頭檔案主要包括solver.hpp、sgd_solvers.hpp、solver_factory.hpp

class Solver {...}
class SGDSolver : public Solver<Dtype>{...}
class NesterovSolver : public SGDSolver<Dtype>{...}
class AdaGradSolver : public SGDSolver<Dtype> {...}
class RMSPropSolver : public SGDSolver<Dtype> {...}
class AdaDeltaSolver : public SGDSolver<Dtype> {...}
Solver<Dtype>* GetSolver(const SolverParameter& param) {...} 

包含的主要函式和介紹如下:

Solver()建構函式,初始化net和test_net兩個net類,並呼叫init函式初始化網路,解釋詳見官方文件頁; Solve()訓練網路有如下步驟:

  1. 設定Caffe的mode(GPU還是CPU)
  2. 如果是GPU且有GPU晶片的ID,則設定GPU
  3. 設定當前階段(TRAIN還是TEST)
  4. 呼叫PreSolve函式:PreSolve()
  5. 呼叫Restore函式:Restore(resume_file)
  6. 呼叫一遍Test(),判斷記憶體是否夠
  7. 對於每一次訓練時的迭代(遍歷整個網路)

對於第7步,訓練時的迭代,有如下的過程:

while (iter_++ < param_.max_iter())
    1.計算loss:loss = net_->ForwardBackward(bottom_vec)     
    2.呼叫ComputeUpdateValue函式;
    3.輸出loss;
    4.達到test_interval時呼叫Test() 
    5.達到snapshot時呼叫snapshot()

關於Test() 測試網路。有如下過程:

1. 設定當前階段(TRAIN還是TEST)
2. 將test_net_指向net_,即對同一個網路操作
3. 對於每一次測試時的迭代:for (int i = 0; i < param_.test_iter(); ++i)
    3.1.用下面語句給result賦值net_output_blobs_
result = test_net_->Forward(bottom_vec, &iter_loss);
    3.2.第一次測試時: 取每一個輸出層的blob result_vec = result[j]->cpu_data(),把每一個blob的資料(降為一維)存入一個vector–“test_score”
不是第一次測試: 用 test_score[idx++] += result_vec[k] 而不是 test_score.push_back(result_vec[k])
把輸出層對應位置的blob值累加 test_score[idx++] += result_vec[k]。
    3.3.是否要輸出Test loss,是否要輸出test_score;     
    3.4.設定當前階段(TRAIN還是TEST)

基本函式的一個簡易介紹如下:
Snapshot()輸出當前網路狀態到一個檔案中; 
Restore()從一個檔案中讀入網路狀態,並可以從那個狀態恢復; 
GetLearningRate()得到學習率; PreSolve()提前訓練,詳見網頁連結; 
ComputeUpdateValue()用隨機梯度下降法計算更新值;

未完待續…


相關推薦

caffe原始碼導讀(二)關於Blob資料結構

開啟proto/caffe.proto中,剛開始就是介紹Blob資料結構,這個資料結構是其他大部分資料結構的重要依賴. caffe中進行網路層計算時,每一層的輸入輸出都是以Blob物件為緩衝,是cagge的基本儲存單元. 一、先看Blob的資料結構描述 // Specifies th

caffe原始碼導讀(四)Blob.cpp解析

本系列參考<深度學習21天實戰caffe>這本書所做的筆記,如果錯誤歡迎指導 前篇 caffe 原始碼導讀(一) 瞭解protobuf caffe 原始碼導讀(二) Blob資料結構介紹 caffe 原始碼導讀(三) Blob.hpp標頭檔案解析 d=========

Caffe原始碼導讀

轉自:https://ymgd.github.io/codereader/2016/10/20/caffe_sourcecode_analysis/ 1.前言 目前的影象和自然語言處理很多地方用到了神經網路/深度學習相關的知識,神奇的效果讓廣大身處IT一線的程式猿G

caffe原始碼 池化層

1、標題圖示池化層(前向傳播) 池化層其實和卷積層有點相似,有個類似卷積核的視窗按照固定的步長在移動,每個視窗做一定的操作,按照這個操作的型別可以分為兩種池化層: 輸入引數如下: 輸入: 1 * 3 * 4 * 4 池化核: 2 * 2 pad: 0 步長:2 輸出引數如下

caffe原始碼理解之inner_product_layer

原文地址:https://www.cnblogs.com/dupuleng/articles/4312149.html 在caffe中所謂的Inner_Product(IP) 層即fully_connected (fc)layer,為什麼叫ip呢,可能是為了看起來比較優雅吧。。 從CAF

Caffe原始碼(四):math_functions 分析

轉自:https://blog.csdn.net/seven_first/article/details/47378697#1-caffecpugemm-%E5%87%BD%E6%95%B0 主要函式 math_function 定義了caffe 中用到的一些矩陣操作和數值計算的一些函式,這

caffe 原始碼分析【三】:Euclidean loss layer

以下是Euclidean loss layer的程式碼分析,轉自: https://blog.csdn.net/seashell_9/article/details/68064294 一. 前向函式 template <typename Dtype> void Euclide

caffe 原始碼分析【二】:Layer基類

建構函式 //標頭檔案 include/caffe/layer.hpp //實現檔案 src/caffe/layer.cpp // src/caffe/layer.cu /* * 建構函式 * 子類中修改建構函式,自定義設定在SetUp()中設定

caffe 原始碼分析【一】: Blob類

Blob類的:     //標頭檔案: include\caffe\blob.hpp //cpp檔案: src\caffe\blob.cpp //cu檔案: src/caffe/blob.cu //定義某layer的輸入blobs const ve

caffe原始碼深入學習6:超級詳細的im2col繪圖解析,分析caffe卷積操作的底層實現

在先前的兩篇部落格中,筆者詳細解析了caffe卷積層的定義與實現,可是在conv_layer.cpp與base_conv_layer.cpp中,卷積操作的實現仍然被隱藏,通過im2col_cpu函式和caffe_cpu_gemm函式(後者實現矩陣乘法)實現,在此篇部落格中,筆者旨在向大家展示,caf

caffe 原始碼 閱讀 指導意見

Caffe Source Code Analysis https://buptldy.github.io/2016/10/09/2016-10-09-Caffe_Code/ ./build/tools/caffe train --solver=examples/mnist/lenet_sol

Caffe原始碼理解2:SyncedMemory CPU和GPU間的資料同步

目錄 寫在前面 成員變數的含義及作用 構造與析構 記憶體同步管理 參考 部落格:blog.shinelee.me | 部落格園 | CSDN 寫在前面 在Caffe原始碼理解1中介紹了Blob類,其中的資料成員有 shared_ptr<SyncedMemory>

caffe原始碼分析-Blob

本文主要分析caffe原始碼分析-Blob,主要如下幾個方面: overview整體上了解caffe的Blob Blob 成員變數 Blob主要函式,核心在於Blob的使用例項以及其與opencv Mat的操作的相互轉化(附帶執行結果基於CLion) o

caffe原始碼解析:層(layer)的註冊與管理

caffe中所有的layer都是類的結構,它們的構造相關的函式都註冊在一個全域性變數g_registry_ 中。 首先這個變數的型別 CreatorRegistry是一個map定義, public: typedef shared_ptr<Layer<Dt

caffe原始碼分析-ReLULayer

啟用函式如:ReLu,Sigmoid等layer相對較為簡單,所以在分析InnerProductLayer前,我們先看下啟用函式層。 常見啟用層ReLU的使用示例如下: layer { name: "relu1" type: "ReLU" bott

caffe原始碼分析-layer_factory

caffe中有許多的layer,在net中建立連線layer是通過工廠模式的方式建立,而不是每一個new然後連線。在net.cpp中建立layer方式如下: layers_.push_back(Laye

caffe原始碼分析-InputLayer

對於輸入層,我們首先分析最簡單的InputLayer層,其常作為網路inference時的輸入,簡單的mnist使用示例如下: layer { name: "data" type: "Input

caffe原始碼分析-BlockingQueue

BlockingQueue執行緒安全的佇列, 作為caffe訓練時資料同步的重要資料結構,本文做簡要分析。 template<typename T> class BlockingQueue

caffe原始碼分析-DataTransformer

本文主要分析caffe中DataTransformer這個類, 主要作用是: 將Datum型別或者cv::Mat, 轉化為caffe的Blob<Dtype>,並按照Transformation``Parameter引數對影象做處理,例如scale

caffe原始碼解析:insertSplits對 top輸出到多個 Layer的情況進行分割

作用:對 top輸出到多個 Layer的情況進行分割,建立完整的網路結構 重要的引數說明舉例: layer_idx_to_layer_name[i] 記錄各層的名稱,如 [0x00000000] "input" blob_name_to_last_top_idx[“c