caffe代碼淺析

Caffe參數及存儲方式

參數類型
● 可學習參數,即權重,其值由模型初始化參數、誤差方向傳播過程決定。存放在.caffemodel文件中。
● 結構參數,包括網絡層數、層類型、卷積核數等,一旦設定好,訓練階段不能更改,注意:caffe中可以設置訓練階段結構參數和預測階段結構參數不同。存放在train.prototxt和test.prototxt中。
● 訓練超參數,包括訓練次數等,預測階段不需要該參數。存放在Solver.prototxt中

prototxt文本
protocol buffers用於序列化和檢索數據結構。caffe使用prototxt文本存儲輸入輸出參數描述文件。
protocol buffers的序列化數據及讀取數據的步驟:
1. 在一個.proto文件中定義Message格式。
2. 使用protoc編譯.proto文件,編譯器會生成一個類,以便後續的計算單元中包含該類
3. 使用python/C++/java protocol buffer API 讀寫Message
caffe模型結構參數定義在prototxt文件中,prototxt文件的後綴是.prototxt。學習好的模型會被序列化地存儲在二進制 protocol buffer (binaryproto) .caffemodel 文件中。

輸入輸出文件
輸入:
(1)slover.prototxt。描述網絡訓練時的各種超參數文件,如訓練的策略,學習率的變化率,模型保存的頻率等參數
(2)train.prototxt。描述訓練網絡的拓撲結構參數文件,如卷積層/池化層個數,卷積核大小,卷積核數目。
(3)test.prototxt。描述測試網絡的拓撲網絡結構參數文件。
輸出:
VGG16.caffemodel:保存的訓練好的網絡可學習參數文件,如權重,偏置。

Caffe代碼組成

一個Caffe由四個主要的大類構成,這些類分別是Blob類,Layer類,Net類,Solver類。
● Blob:是存放數據的結構,是用來保存學習到的參數以及網絡傳輸過程中產生數據的類,本質上是一個N維數組。
● Layer:是網絡的基本單元,由此派生出了各種層類。修改這部分的人主要是研究特徵表達方向的。
● Net:是網絡的搭建,將Layer所派生出層類組合成網絡。
● Solver:是Net的求解方法,管理並執行Net對象的訓練和測試。

Blob

Blob類原型(只截取關鍵數據和函數)

//Blob是一個模板類
template <typename Dtype>
class Blob{
protected:
    shared_ptr<SyncedMemory> data_;
    shared_ptr<SyncedMemory> diff_;
    vector<int> shape_;
    int count_;
    int capacity_;
public:
    Blob():data_(),diff_(),count_(0),capacity_(0){}//默認構造函數
//...
};

聲明一個Blob對象如下:

Blob<float> input;

blob 表示一個4維數組,常規的維數爲圖像數量 N*通道數 C*圖像高度 H *圖像寬度W。 Blob 按行爲主( row-major) 進行存儲,所以一個 4 維 blob 中,索引爲(n, c, h, w)的blob值轉換爲一維數組的索引值爲((n* C+c) *H+h)*W+w。

Blob成員變量
Blob類中有兩個SyncedMemory類型數據對象指針——data_(數據或權值)和diff_(梯度),一個表示形狀的vector<int>,還有兩個用於指示大小和總容量的count_和capacity_變量。由於數據既可存儲在CPU上,也可存儲在GPU上。數據訪問有兩種方式:靜態方式,不改變數據;動態方式,改變數據。

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

blob 使用了一個 SyncedMem 類來同步 CPU 和 GPU 上的數值, 以隱藏同步的細節和最小化傳送數據。一個經驗準則是,如果不想改變數值,就一直使用常量調用,而且絕不要在自定義類中存儲指針。 每次操作 blob 時, 調用相應的函數來獲取它的指針,因爲 SyncedMem 需要用這種方式來確定何時需要複製數據。

Blob成員函數

Blob<float> input;//創建一個Blob對象
vector<int> a;
input.Reshape(1,2,3,4);//它通過整型數組改變Blob的各個維度的長度
a=input.shape();//它返回vector<int>類型的shape
cout<<input.shape_string();//它返回string類型的shape
cout<<input.num();
cout<<input.channels();
cout<<input.height();
cout<<input.width();

//反序列化函數原型,從BlobProto中恢復一個對象
void FromProto(const BlobProto& proto,bool reshape = true);
//序列化函數原型,將內存中的Blob對象存在BlobProto中,以便於存儲在磁盤中
void ToProto(BlobProto* proto,bool write_diff = false)const;

Layer

Layer類原型(只截取關鍵數據和函數)

//Lyaer本身是一個抽象模板類
template <typename Dtype>
class Layer{
protected:
    LayerParameter layer_param_;//它是一個Protocol buffer對象,包含層內結構參數
    Phase phase_;//階段,TRAIN或TEST
    vector<shared_ptr<Blob<Dtype>>> blobs_;//層的權值和偏置
    vector<Dtype> loss_;//當前層的loss值
    vector<bool> param_propagate_down_;//是否計算誤差梯度的標誌
public:
    //顯式構造函數
    explicit Layer(const LayerParameter& param):layer_param(param),is_shared(false){
    phase_ = param.phase();
    blobs_.resize(layer_param.blobs_size());按照layer_param_設置Blob對象個數
    for(int i = 0;i<layer_param.blobs_size();i++)
    {   
        //對blobs_中每個Blob對象調整尺寸,與layer_param_Blob對象尺寸一致
        blobs_[i].reset(new Blob<Dtype>());
    blobs_i]->FroProto(layer_param_.blobs(i));
    }
    }
//...
};

5大Layer派生類
● 神經元類,定義於neuron_layers.hpp中,其派生類主要是元素級別的運算(比如Dropout運算,激活函數ReLu,Sigmoid等),運算均爲同址計算(in-place computation,返回值覆蓋原值而佔用新的內存)。
● LossLayer類,定義於loss_layers.hpp中,其派生類會產生loss,只有這些層能夠產生loss。
● 數據層類,定義於data_layer.hpp中,作爲網絡的最底層,主要實現數據格式的轉換。
● 視覺層類,定義於vision_layers.hpp,實現抽取特徵功能,具體地說包含卷積操作,Pooling操作,他們基本都會產生新的內存佔用(Pooling相對較小)。
● 通用層類,定義於common_layers.hpp,Caffe提供了單個層與多個層的連接,並在這個頭文件中聲明。包括了常用的全連接層InnerProductLayer類。

Layer成員數據
1、loss
每一層又有一個loss值,大多數Layer都是0,只有LossLayer纔可能產生非0的loss。計算loss是會把所有層的loss_相加。
2、權值和偏置
放在vector<shared_ptr<Blob<Dtype>>>類型的blobs_中,每層可能不止有一個Blob對象,對於大多數Layer來說輸入和輸出都各連接只有一個Blob,但是對於某些層,如下圖所示,LossLayer有兩個輸入Blob。在網路結構定義文件(*.proto)中每一層的參數bottom和top數目就決定了vector中Blob對象的數目。

Layer成員函數
Layer是Caffe模型的本質內容和執行計算的基本單元。每一個 layer 都定義了 3 種重要的運算: setup(初始化設置), forward(前向傳播),backward(反向傳播)。CPU模式下,前向和反向傳播函數名分別爲Forward_cpu()和Backward_cpu;GPU模式下,分別爲Forward_gpu()和Backward_gpu,這四個函數都是虛函數,在不同Layer的派生類中需要重寫。
● Setup: 在模型初始化時重置 layers 及其相互之間的連接 ;
● Forward: 從bottom blob中接收數據,進行計算後將輸出送入到top blob中;
● Backward: 給定top blob的diff,計算其相對於輸入的diff,並傳遞到bottom blob。一個有參數的layer需要計算相對於各個參數的梯度值並存儲在內部。
Layer類派生出來的層類通過這實現這兩個虛函數,產生了各式各樣功能的層類。Forward是從根據bottom計算top的過程,Backward則相反(根據top計算bottom)。

Net

Net類原型(只截取關鍵數據和函數)

template <typename Dtype>
class Net{
protected:
    string name_;//網絡名稱
    Phase phase_;//當前階段

    vector<shared_ptr<Layer<Dtype>>> layers_;//網絡中間的層
    vector<string> layer_names_;

    vector<shared_ptr<Blob<Dtype>>> blobs_;//層之間的輸入輸出blobs
    vector<string> blob_names_;

    vector<shared_ptr<Blob<Dtype>>> params_;//網絡權值

    vector<float> params_lr;//學習率
public:
    //顯式構造函數
    //NetParameter對象初始化Net
    explicit Net(const NetParameter& param,const Net* root_net = NULL);
    //用網絡描述文件初始化Net
    explicit Net(const string& param_file,Phase phase, const Net* root_net= NULL);
    //以輸入Blob爲參數進行前向傳播,返回輸出Blob
    const vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>*>& bottom,Dtype* loss = NULL);
    void Backward();
    void Update();
//...
};

Net成員數據
一個Net對象包含的三種對象,分別是層數組(layers_)、Blob數組(blobs_)和權重數組(params_)。layers_是Layer的vector容器,其自身實現的功能主要是對逐層Layer進行初始化,以及提供Update( )的接口(更新網絡參數)。
Net 是由一系列層組成的有向無環( DAG)計算圖, Caffe 保留了計算圖中所有的中間值以確保前向和反向迭代的準確性。一個典型的 Net 開始於 data layer——從磁盤中加載數據,終止於 loss layer——計算如分類和重構這些任務的目標函數。

Net成員函數
Forward(…)和Backward()這兩個函數對整個網絡的前向和方向傳播,各調用一次就可以計算出網絡的loss了。
Net::Init()進行模型的初始化。初始化主要實現兩個操作:創建 blobs 和 layers 以搭建整 個網絡 DAG 圖,以及調用layers 的 SetUp()函數。
網絡構建完之後,通過設置 Caffe::mode()函數中的 Caffe::set_mode(), 即可實現在 CPU 或 GPU 上的運行。

Solver

Solver負責整個模型的優化,讓全局loss達到最小。
Solver的主要工作:
1、調用Net::Forward=>調用Net::Backward=>調用Net::Update
2、週期性測試網絡。

Solver類原型

//Solver是一個抽象模板類
template <typename Dtype>
class Slover{
protected:
    SolverParameter param_;//用於從prototxt中提取參數
    int iter_;//迭代次數
    shared_ptr<Net<Dtype> > net_;//指向一個Net對象的指針,用於訓練
    vector<shared_ptr<Net<Dtype>>> test_nets;//Net對象數組,用於測試
    virtual void ApplyUpdate() = 0;//更新權值
    void TestAll();//每隔一定週期對訓練的網絡進行一次評估
public:
    //顯示構造函數
    //以SolverParameter對象構造Solver
    explicit Slover(const SolverParameter& param,const Solver* root_solver =NULL);
    //以Solver描述文件構造Solver
    explicit Solver(const string& param_file,const Solver* root_solver =NULL);
    void Init(const SolverParameter& param);//初始化
    virtual void Solve(const char* resume_file =NULL);//從一個resume_file恢復訓練,默認是從iter 0開始訓練
    void Step(int iters);//進行第iter次迭代
//...
};

Solver成員數據
Solver包含一個訓練網絡net_對象和若干個預測網絡test_nets_對象。由Solver管理它們的初始化和執行。

Solver成員函數
根據訓練方法的不同定義不同的Solver的派生類,不同的派生類都有一個ComputeUpdateValue( )實現計算update參數的核心功能。
最後當進行整個網絡訓練過程(也就是你運行Caffe訓練某個模型)的時候,實際上是在運行caffe.cpp中的train( )函數,而這個函數實際上是實例化一個Solve的派生類對象,初始化後調用Solve( )函數。而這個Solve( )函數又調用派生類中的ApplyUpdate()函數,ApplyUpdate()函數就包含了ComputeUpdateValue()和net_->Update()兩個函數的調用。

至此,以對象的角度分析了caffe深度學習框架源碼的組成,希望給初學者一個整體的認識。如有錯誤,請聯繫博主~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章