【caffe源碼研究】第三章:源碼篇(8) :Layer代碼

簡介

Layer是Caffe模型的本質內容和執行計算的基本單元。Layer可以進行很多運算,如:convolve(卷積)、pool(池化)、inner product(內積),rectified-linear和sigmoid等非線性運算,元素級的數據變換,normalize(歸一化)、load data(數據加載)、softmax和hinge等losses(損失計算)。可在Caffe的layer catalogue(層目錄)中查看所有操作,其囊括了絕大部分目前最前沿的深度學習任務所需要的層類型。

這裏寫圖片描述

一個layer通過bottom(底部)連接層接收數據,通過top(頂部)連接層輸出數據。
每一個layer都定義了3種重要的運算:setup(初始化設置),forward(前向傳播),backward(反向傳播)。

  • Setup: 在模型初始化時重置layers及其相互之間的連接 ;
  • Forward: 從bottom層中接收數據,進行計算後將輸出送入到top層中;
  • Backward: 給定相對於top層輸出的梯度,計算其相對於輸入的梯度,並傳遞到bottom層。一個有參數的layer需要計算相對於各個參數的梯度值並存儲在內部。

特別地,Forward和Backward函數分別有CPU和GPU兩種實現方式。如果沒有實現GPU版本,那麼layer將轉向作爲備用選項的CPU方式。儘管這樣會增加額外的數據傳送成本(輸入數據由GPU上覆制到CPU,之後輸出數據從CPU又複製回到GPU),但是對於做一些快速實驗這樣操作還是很方便的。

總的來說,Layer承擔了網絡的兩個核心操作:forward pass(前向傳播)——接收輸入並計算輸出;backward pass(反向傳播)——接收關於輸出的梯度,計算相對於參數和輸入的梯度並反向傳播給在它前面的層。由此組成了每個layer的前向和反向通道。

由於Caffe網絡的組合性和其代碼的模塊化,自定義layer是很容易的。只要定義好layer的setup(初始化設置)、forward(前向通道)和backward(反向通道),就可將layer納入到網絡中。

構造函數

首先來看layer類的構造部分,以及Public部分的函數

template <typename Dtype>
class Layer {
 public:
  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() {}

首先獲得當前網絡的Phase,是train還是test,在初始化列表初始化LayerParameter,之後blobs_這裏存放的是一個指向blob類的shared_ptr指針的一個vector,在這裏是申請空間,然後將傳入的layer_param中的blob拷貝過來。

SetUp

void SetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
    InitMutex();
    CheckBlobCounts(bottom, top);
    LayerSetUp(bottom, top);
    Reshape(bottom, top);
    SetLossWeights(top);
  }

這裏是Setup函數,首先check 這個bottom和top的blob是否正確,再調用Layersetup對每一具體的層做進一步設置,之後再做reshape來設置top blobs和internal buffer。最後再設置loss weight multiplier 的blob對每一個非零的loss和weight,一般這個方法被繼承之後是不會被重寫的。

Forward

Forward.這其實是一個裝飾器,繼承之後在調用的調用其相應的forward_cpu或者forward_gpu,根據輸入的input data blob計算相應的output data blob,同時會反應這一層layer的total loss.
抽取CPU部分如下:

inline Dtype Forward(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top){
        Lock();
        Dtype loss = 0;
        Reshape(bottom, top);
        Forward_cpu(bottom, top);
        for (int top_id = 0; top_id < top.size(); ++top_id) {
            if (!this->loss(top_id)) { continue; }
            const int count = top[top_id]->count();
            const Dtype* data = top[top_id]->cpu_data();
            const Dtype* loss_weights = top[top_id]->cpu_diff();
            loss += caffe_cpu_dot(count, data, loss_weights); //內積
        }
        Unlock();
        return loss;
}

這裏是BackWard,實現的是反向傳播,也就是給定top blob額error gradient 計算得到bottom的error gradient。其輸入時 output blobs ,在Ouput blobs裏面的diff存儲的就是其相應的error gradients。其中propagate_down這個參數跟Bottom的長度是一樣的,每一個Index用來指定是否需要反向傳播error gradients 到對應的bottom blob。而bottom 這裏面的diff 區域存放的就是BackWard計算出來相應的gradient error.

其中的loss部分,需要說明。
Caffe中,默認每一層都可以作爲LossLayer,不過層的名字中不帶Loss的層其”Loss_weight”屬性默認爲0,即對最後的總的Loss貢獻爲0。名字中帶Loss的層可以在網絡的任意位置使用(不一定非在網絡的輸出最後部分),且可以有不止一個LossLayer,最後的訓練所使用的Loss爲所有LossLayer損失的加權和
具體體現在函數SetLossWeights中。

inline void SetLossWeights(const vector<Blob<Dtype>*>& top) {
    const int num_loss_weights = layer_param_.loss_weight_size();
    if (num_loss_weights) {
        CHECK_EQ(top.size(), num_loss_weights) << "loss_weight must be "
            "unspecified or specified once per top blob.";
        for (int top_id = 0; top_id < top.size(); ++top_id) {
            const Dtype loss_weight = layer_param_.loss_weight(top_id);
            if (loss_weight == Dtype(0)) { continue; }
            this->set_loss(top_id, loss_weight);
            const int count = top[top_id]->count();
            Dtype* loss_multiplier = top[top_id]->mutable_cpu_diff();
            caffe_set(count, loss_weight, loss_multiplier);
        }
    }
}

Backward

template <typename Dtype>
inline void Layer<Dtype>::Backward(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down,
    const vector<Blob<Dtype>*>& bottom) {
    switch (Caffe::mode()) {
    case Caffe::CPU:
        Backward_cpu(top, propagate_down, bottom);
        break;
    case Caffe::GPU:
        Backward_gpu(top, propagate_down, bottom);
        break;
    default:
        LOG(FATAL) << "Unknown caffe mode.";
    }
}

如果自己你要實現一個Layer的話,那麼Forward_cpu和Backward_cpu 以及gpu(可選),應該要有自己的實現。

下面這幾個函數,分別是計算cpu和gpu模式下的正向和反向傳播

virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top)
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top)
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom) = 0;
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom)

其他

virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top)
virtual inline bool ShareInParallel() 
inline bool IsShared() const
inline void SetShared(bool is_shared)

LayerSetup就是對具體某一個layer的setup,被上面的那個函數所調用,ShareInParallel和IsShared和SetShared分別是用來返回並行狀態和獲得這一layer是否被多個nets所共享,默認是除了data layer都是關閉的。在多個GPU下的Train階段以及share是true的情況下,is_shared將會被置成true。

virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) = 0;

這個reshape主要是layer用來根據輸入的blob調節Internal buffer以及輸出的Blob的.

vector<shared_ptr<Blob<Dtype> > >& blobs()\\返回blobs
const LayerParameter& layer_param() \\返回layer 的參數parameter
virtual void ToProto(LayerParameter* param, bool write_diff = false)\\將層參數寫到Protobuffer裏
inline Dtype loss(const int top_index) const \\給定index返回相應的scalar loss
inline void set_loss(const int top_index, const Dtype value)\\給定Index設置loss
virtual inline const char* type()\\返回layer的type

以下幾個函數主要獲得bottom或者top blob的數量狀態,比較簡單,看名字即可

virtual inline int ExactNumBottomBlobs() 
virtual inline int MinBottomBlobs() 
virtual inline int MaxBottomBlobs() 
virtual inline int ExactNumTopBlobs() 
virtual inline int MinTopBlobs() 
virtual inline int MaxTopBlobs() 
virtual inline bool EqualNumBottomTopBlobs()
virtual inline bool AutoTopBlobs() 

AllowforceBackward用來設置是否強制梯度返回,因爲有些層其實不需要梯度信息 ,後面兩個函數分別查看以及設置是是否需要計算梯度。

virtual inline bool AllowForceBackward(const int bottom_index)
inline bool param_propagate_down(const int param_id)
inline void set_param_propagate_down(const int param_id, const bool value)

好,再往下面看,幾個變量和函數都是保護變量

LayerParameter layer_param_; \\保存layer的參數 parameter
Phase phase_; \\標定階段是train還是test
vector<shared_ptr<Blob<Dtype> > > blobs_; \\是Blob的一個集合,保存了learnbale參數
vector<bool> param_propagate_down_;\\標誌位是否要計算param blob的梯度
vector<Dtype> loss_;\\用來表明那個top blob 有非零的權重

這個函數被setup調用,主要是check bottom和top 的blob是否match,這裏面用了上面提到的ExactBottomBlobs()等函數

virtual void CheckBlobCounts(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top)

SetLoss是非常重要的一個步驟,是被SetUp調用來初始化top bottom的weights,並且存儲非零的loss weights 在diff blob裏面

inline void SetLossWeights(const vector<Blob<Dtype>*>& top)

私有變量和函數如下,東西比較少,主要是對並行中的鎖進行控制

bool is_shared_; //標記該layer是否被其他nets所共享
shared_ptr<boost::mutex> forward_mutex_;//若該layer被shared,則需要這個mutex序列保持forward過程的正常運行
void InitMutex();//初始化forward 的 mutex
void Lock();//locak mutex
void Unlock();//unlock mutex這一看就明白了
發佈了319 篇原創文章 · 獲贊 77 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章