caffe源碼閱讀(1)_整體框架和簡介(摘錄)


原文鏈接:https://www.zhihu.com/question/27982282

1.Caffe代碼層次。
回答里面有人說熟悉Blob,Layer,Net,Solver這樣的幾大類,我比較贊同。我基本是從這個順序開始學習的,這四個類復雜性從低到高,貫穿了整個Caffe。把他們分為三個層次介紹。

  • Blob:作為數據傳輸的媒介,無論是網絡權重參數,還是輸入數據,都是轉化為Blob數據結構來存儲
  • Layer:作為網絡的基礎單元,神經網絡中層與層間的數據節點、前后傳遞都在該數據結構中被實現,層類種類豐富,比如常用的卷積層、全連接層、pooling層等等,大大地增加了網絡的多樣性
  • Net:作為網絡的整體骨架,決定了網絡中的層次數目以及各個層的類別等信息
  • Solver:作為網絡的求解策略,涉及到求解優化問題的策略選擇以及參數確定方面,修改這個模塊的話一般都會是研究DL的優化求解的方向。
1. Blob:
1.1. Blob的類型描述
Caffe內部采用的數據類型主要是對protocol buffer所定義的數據結構的繼承,因此可以在盡可能小的內存占用下獲得很高的效率,雖然追求性能的同時Caffe也會犧牲了一些代碼可讀性。
直觀來說,可以把Blob看成一個有4維的結構體(包含數據和梯度),而實際上,它們只是一維的指針而已,其4維結構通過shape屬性得以計算出來。
1.2. Blob的重要成員函數和變量
shared_ptr<SyncedMemory> data_ //數據
shared_ptr<SyncedMemory> diff_ //梯度
void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
const int width)
重新修改Blob的形狀(4維),並根據形狀來申請動態內存存儲數據和梯度。
inline int count(int start_axis, int end_axis) const
計算Blob所需要的基本數據單元的數量。
在更高一級的Layer中Blob用下面的形式表示學習到的參數:
vector<shared_ptr<Blob<Dtype> > > blobs_; 
這里使用的是一個Blob的容器是因為某些Layer包含多組學習參數,比如多個卷積核的卷積層。
以及Layer所傳遞的數據形式,后面還會涉及到這里:
vector<Blob<Dtype>*> &bottom;
vector<Blob<Dtype>*> *top

2.2. Layer:
2.2.1. 5大Layer派生類型
Caffe十分強調網絡的層次性,也就是說卷積操作,非線性變換(ReLU等),Pooling,權值連接等全部都由某一種Layer來表示。具體來說分為5大類Layer
  • NeuronLayer類 定義於neuron_layers.hpp中,其派生類主要是元素級別的運算(比如Dropout運算,激活函數ReLu,Sigmoid等),運算均為同址計算(in-place computation,返回值覆蓋原值而占用新的內存)。
  • LossLayer類 定義於loss_layers.hpp中,其派生類會產生loss,只有這些層能夠產生loss
  • 數據層 定義於data_layer.hpp中,作為網絡的最底層,主要實現數據格式的轉換。
  • 特征表達層(我自己分的類)定義於vision_layers.hpp(為什么叫vision這個名字,我目前還不清楚),實現特征表達功能,更具體地說包含卷積操作,Pooling操作,他們基本都會產生新的內存占用(Pooling相對較小)。
  • 網絡連接層和激活函數(我自己分的類)定義於common_layers.hpp,Caffe提供了單個層與多個層的連接,並在這個頭文件中聲明。這里還包括了常用的全連接層InnerProductLayer類。
2.2.2. Layer的重要成員函數
在Layer內部,數據主要有兩種傳遞方式, 正向傳導(Forward)反向傳導(Backward)。Forward和Backward有CPU和GPU(部分有)兩種實現。Caffe中所有的Layer都要用這兩種方法傳遞數據。
virtual void Forward(const vector<Blob<Dtype>*> &bottom, vector<Blob<Dtype>*> *top) = 0; virtual void Backward(const vector<Blob<Dtype>*> &top, const vector<bool> &propagate_down, vector<Blob<Dtype>*> *bottom) = 0; 
Layer類派生出來的層類通過這實現這兩個虛函數,產生了各式各樣功能的層類。Forward是從根據bottom計算top的過程,Backward則相反(根據top計算bottom)。注意這里為什么用了一個包含Blob的容器(vector),對於大多數Layer來說輸入和輸出都各連接只有一個Layer,然而對於某些Layer存在一對多的情況,比如LossLayer和某些連接層。在網路結構定義文件(*.proto)中每一層的參數bottom和top數目就決定了vector中元素數目。
layers { bottom: "decode1neuron" // 該層底下連接的第一個Layer bottom: "flatdata" // 該層底下連接的第二個Layer top: "l2_error" // 該層頂上連接的一個Layer name: "loss" // 該層的名字 type: EUCLIDEAN_LOSS // 該層的類型 loss_weight: 0 } 
2.2.3. Layer的重要成員變量
loss
vector<Dtype> loss_; 
每一層又有一個loss_值,只不多大多數Layer都是0,只有LossLayer才可能產生非0的loss_。計算loss是會把所有層的loss_相加。
learnable parameters
vector<shared_ptr<Blob<Dtype> > > blobs_; 
前面提到過的,Layer學習到的參數。
 

2.3. Net:
Net用容器的形式將多個Layer有序地放在一起,其自身實現的功能主要是對逐層Layer進行初始化,以及提供Update( )的接口(更新網絡參數),本身不能對參數進行有效地學習過程。
vector<shared_ptr<Layer<Dtype> > > layers_;
同樣Net也有它自己的
vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom, Dtype* loss = NULL); void Net<Dtype>::Backward(); 
他們是對整個網絡的前向和方向傳導,各調用一次就可以計算出網絡的loss了。
2.4. Solver
這個類中包含一個Net的指針,主要是實現了訓練模型參數所采用的優化算法,它所派生的類就可以對整個網絡進行訓練了。
shared_ptr<Net<Dtype> > net_;
不同的模型訓練方法通過重載函數ComputeUpdateValue( )實現計算update參數的核心功能
virtual void ComputeUpdateValue() = 0;
最后當進行整個網絡訓練過程(也就是你運行Caffe訓練某個模型)的時候,實際上是在運行caffe.cpp中的train( )函數,而這個函數實際上是實例化一個Solver對象,初始化后調用了Solver中的Solve( )方法。而這個Solve( )函數主要就是在迭代運行下面這兩個函數,就是剛才介紹的哪幾個函數。
ComputeUpdateValue(); net_->Update(); 

==============================================================

至此,從底層到頂層對Caffe的主要結構都應該有了大致的概念。為了集中重點介紹Caffe的代碼結構,文中略去了大量Caffe相關的實現細節和技巧,比如Layer和Net的參數如何初始化,proto文件的定義,基於cblas的卷積等操作的實現(cblas實現卷積這一點我的個人主頁GanYuFei中的《Caffe學習筆記5-BLAS與boost::thread加速》有介紹)等等就不一一列舉了。

整體來看Layer部分代碼最多,也反映出Caffe比較重視豐富網絡單元的類型,然而由於Caffe的代碼結構高度層次化,使得某些研究以及應用(比如研究類似非逐層連接的神經網絡這種復雜的網絡連接方式)難以在該平台實現。這也就是一開始說的一個不足。

另外,Caffe基本數據單元都用Blob,使得數據在內存中的存儲變得十分高效,緊湊,從而提升了整體訓練能力,而同時帶來的問題是我們看見的一些可讀性上的不便,比如forward的參數也是直接用Blob而不是設計一個新類以增強可讀性。所以說性能的提升是以可讀性為代價的。
最后一點也是最重要的一點,我從Caffe學到了很多。第一次看的C++項目就看到這么好的代碼,實在是受益匪淺,在這里也感謝作者賈揚清等人的貢獻。

 
Net中有必要認識的幾個名字:
 
這里再補充幾點:
1.使用Linux 的grep指令來快速追蹤某個關鍵字:在caffe根目錄下輸入: grep -n -H -R "REGISTER_LAYER_CREATOR"(舉例),其中-n 顯示行號 -H顯示文件名 -R遞歸查找每個子目錄

    


免責聲明!

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



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