caffe 網路結構幾個部分簡單介紹

查志強發表於2015-08-07

【原文:http://wenku.baidu.com/link?url=bk0n8T7rSeIceuVcn67XEubZMPyPlolirXhfc3PpyzolHJu6WhDqzN1mNxry7Fis4Ve5TJH1olqrBuyIv-akgFW0UpajJKsHhHt3m8sjU8i

下面這個 複製於一片知乎

首先應該瞭解下幾個大的類,比如blob,net,layer,solver,然後看怎麼從proto檔案中初始化建立網路,blob, layer, net這幾層關係中資料是如何傳遞的。至於不同的layer,用到那種layer的時候再看就ok。


1. 初識Caffe
1.1. Caffe相對與其他DL框架的優點和缺點:
優點: 

· 速度快。Google Protocol Buffer資料標準為Caffe提升了效率。

· 學術論文采用此模型較多。不確定是不是最多,但接觸到的不少論文都與Caffe有關(R-CNNDSN,最近還有人用Caffe實現LSTM

缺點:

· 曾更新過重要函式介面。有人反映,偶爾會出現介面變換的情況,自己很久前寫的程式碼可能過了一段時間就不能和新版本很好地相容了。(現在更新速度放緩,介面逐步趨於穩定,感謝 評論區王峰的建議)

· 對於某些研究方向來說的人並不適合。這個需要對Caffe的結構有一定了解,(後面提到)。

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

· Blob是基礎的資料結構,是用來儲存學習到的引數以及網路傳輸過程中產生資料的類。

· Layer是網路的基本單元,由此派生出了各種層類。修改這部分的人主要是研究特徵表達方向的。

· Net是網路的搭建,將Layer所派生出層類組合成網路。SolverNet的求解,修改這部分人主要會是研究DL求解方向的。


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

2. Caffe進階
2.1. Blob:
Caffe支援CUDA,在資料級別上也做了一些優化,這部分最重要的是知道它主要是對protocol buffer所定義的資料結構的繼承,Caffe也因此可以在儘可能小的記憶體佔用下獲得很高的效率。(追求效能的同時Caffe也犧牲了一些程式碼可讀性)
在更高一級的Layer中Blob用下面的形式表示學習到的引數:

vector<shared_ptr<Blob<Dtype> > > blobs_;

這裡使用的是一個Blob的容器是因為某些Layer包含多組學習引數,比如多個卷積核的卷積層。
以及Layer所傳遞的資料形式,後面還會涉及到這裡:

vector<Blob<Dtype>*> ⊥vector<Blob<Dtype>*> *top

2.2. Layer:
2.2.1. 5大Layer派生型別
Caffe十分強調網路的層次性,也就是說卷積操作,非線性變換(ReLU等),Pooling,權值連線等全部都由某一種Layer來表示。具體來說分為5大類Layer

· NeuronLayer類 定義於neuron_layers.hpp中,其派生類主要是元素級別的運算(比如Dropout運算,啟用函式ReLuSigmoid等),運算均為同址計算(in-place computation,返回值覆蓋原值而佔用新的記憶體)。

· LossLayer類 定義於loss_layers.hpp中,其派生類會產生loss只有這些層能夠產生loss

· 資料層 定義於data_layer.hpp中,作為網路的最底層,主要實現資料格式的轉換。

· 特徵表達層(我自己分的類)定義於vision_layers.hpp(為什麼叫vision這個名字,我目前還不清楚),實現特徵表達功能,更具體地說包含卷積操作,Pooling操作,他們基本都會產生新的記憶體佔用(Pooling相對較小)。

· 網路連線層和啟用函式(我自己分的類)定義於common_layers.hppCaffe提供了單個層與多個層的連線,並在這個標頭檔案中宣告。這裡還包括了常用的全連線層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++專案就看到這麼好的程式碼,實在是受益匪淺,在這裡也感謝作者賈揚清等人的貢獻。

 


相關文章