【caffe源碼研究】第三章:源碼篇(5) :Net

簡介

通過合成和自動微分,網絡同時定義了一個函數和其對應的梯度。通過合成各層的輸出來計算這個函數,來執行給定的任務,並通過合成各層的後向傳播過程來計算來自損失函數的梯度,從而學習任務。Caffe模型是端到端的機器學習引擎。

準確的說,Net是由一系列層組成的有向無環(DAG)計算圖,Caffe保留了計算圖中所有的中間值以確保前向和反向迭代的準確性。一個典型的Net開始於data layer——從磁盤中加載數據,終止於loss layer——計算如分類和重構這些任務的目標函數。 Net由一系列層和它們之間的相互連接構成,用的是一種文本建模語言。
例如

name: "LeNet"
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 64 dim: 1 dim: 28 dim: 28 } }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
......

結構

這裏寫圖片描述

一個Net由多個Layer組成。一個典型的網絡從data layer(從磁盤中載入數據)出發到loss layer結束。

/**
 * @brief Connects Layer%s together into a directed acyclic graph (DAG)
 *        specified by a NetParameter.
 *
 * TODO(dox): more thorough description.
 */
template <typename Dtype>
class Net {
 public:
...
  /// @brief Initialize a network with a NetParameter.
  void Init(const NetParameter& param);
...

  const vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,
      Dtype* loss = NULL);
...
  /**
   * The network backward should take no input and output, since it solely
   * computes the gradient w.r.t the parameters, and the data has already been
   * provided during the forward pass.
   */
  void Backward();
  ...
  Dtype ForwardBackward(const vector<Blob<Dtype>* > & bottom) {
    Dtype loss;
    Forward(bottom, &loss);
    Backward();
    return loss;
  }
...

 protected:
  ...
  /// @brief The network name
  string name_;
  /// @brief The phase: TRAIN or TEST
  Phase phase_;
  /// @brief Individual layers in the net
  vector<shared_ptr<Layer<Dtype> > > layers_;
  /// @brief the blobs storing intermediate results between the layer.
  vector<shared_ptr<Blob<Dtype> > > blobs_;
  vector<vector<Blob<Dtype>*> > bottom_vecs_;
  vector<vector<Blob<Dtype>*> > top_vecs_;
  ...
  /// The root net that actually holds the shared layers in data parallelism
  const Net* const root_net_;
};
}  // namespace caffe

說明:

Init中,通過創建blob和layer搭建了整個網絡框架,以及調用各層的SetUp函數。
blobs_存放這每一層產生的blobs的中間結果,bottom_vecs_存放每一層的bottom blobs,top_vecs_存放每一層的top blobs.

Net的類中的關鍵函數簡單剖析:

  • ForwardBackward:按順序調用了Forward和Backward。
  • ForwardFromTo(int start, int end):執行從start層到end層的前向傳遞,採用簡單的for循環調用。
  • BackwardFromTo(int start, int end):和前面的ForwardFromTo函數類似,調用從start層到end層的反向傳遞。
  • ToProto函數完成網絡的序列化到文件,循環調用了每個層的ToProto函數。

下面介紹幾個比較關鍵的函數的運行過程。

Init

Net的初始化函數。Init主要包含的步驟有

(1). FilterNet

此函數的作用就是模型參數文件(*.prototxt)中的不符合規則的層去掉。例如:在caffe的examples/mnist中的lenet網絡中,如果只是用於網絡的前向,則需要將包含train的數據層去掉.
例如一個Net網絡結構部分如下

name: "LeNet"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "number_train_lmdb"
    batch_size: 64
    backend: LMDB
  }
}
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "number_test_lmdb"
    batch_size: 100
    backend: LMDB
  }
}
...

在NetParameter的定義中有一項

  // 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;

其中

message NetState {
  optional Phase phase = 1 [default = TEST];
  optional int32 level = 2 [default = 0];
  repeated string stage = 3;
}

簡單的理解就是,每一層可以設定一個條件,如果是include,則滿足條件的層就包含進去,也可以設定爲exclude,則滿足條件的層就會剔除。如LeNet中,

include {
    phase: TRAIN
  }

這句話的意思就是如果phase是TRAIN,則這一層符合條件需要保留。如果phase是TEST,則這一層去掉。
FilterNet這個函數就是把不符合條件的層去掉。

(2). InsertSplits

此函數作用是,對於底層一個輸出blob對應多個上層的情況,則要在加入分裂層,形成新的網絡。這麼做的主要原因是多個層反傳給該blob的梯度需要累加。

例如:LeNet網絡中的數據層的top label blob對應兩個輸入層,分別是accuracy層和loss層,那麼需要在數據層在插入一層。如下圖:

這裏寫圖片描述

數據層之上插入了一個新的層,label_mnist_1_split層,爲該層的創建兩個top blob分別爲,Label_mnist_1_split_0和Label_mnist_1_split_1。

新增的這一層的屬性可以從代碼裏看出來,增加了一個Split層。

void ConfigureSplitLayer(const string& layer_name, const string& blob_name,
    const int blob_idx, const int split_count, const float loss_weight,
    LayerParameter* split_layer_param) {
  split_layer_param->Clear();
  split_layer_param->add_bottom(blob_name);
  split_layer_param->set_name(SplitLayerName(layer_name, blob_name, blob_idx));
  split_layer_param->set_type("Split");
  for (int k = 0; k < split_count; ++k) {
    split_layer_param->add_top(
        SplitBlobName(layer_name, blob_name, blob_idx, k));
    if (loss_weight) {
      if (k == 0) {
        split_layer_param->add_loss_weight(loss_weight);
      } else {
        split_layer_param->add_loss_weight(0);
      }
    }
  }
}

(3). 其他

步驟:

1. 調用InsertSplits()函數從in_param讀入新網絡到param 
2. 定義name_,blob_name_to_idx,available_blobs,num_layers 
3. param.input_size()返回輸入層blob的個數; 
    param.input(i)表示第i個blob的名字; 
    param.layers_size()返回網絡的層數。 
4. 對每一個輸入層的blob: 
    產生一塊和當前blob一樣大的空間 e.g. imput_dim=[12 55 66 39 20 24 48 64]表示第一個blob的四個維數爲 12 55 66 39,第二個爲 20 24 48 64 接着blob_pointer指向這塊空間 
    blob_pointer壓到blobs_中 vector<shared_ptr<Blob<Dtype>>> blobs_ 
    blob_name壓到blob_names_中 vector<string> blob_names_ 
    param.force_backward()壓到blob_need_backward_中vector<bool> blob_need_backward_ 
    i 壓到 net_input_blob_indices_中 net_input_blob_indices_ -> vector 
    blob_pointer.get() 壓到 net_input_blobs_中 
    注意與blobs_的區別 
    vector<shared_ptr<Blob<Dtype>>> blobs_ 
    vector<Blob<Dtype>*> net_input_blobs_ 
    shared_ptr類型的參數調用.get()則得到Blob*類型 
    map<string, int> blob_name_to_idx 
    初始化爲輸入層的每個blob的名字 set<string> available_blobs 
    計算所需內存 memory_used += blob_pointer->count() 

5. 存每一層的輸入blob指針 vector<vector<Blob<Dtype>*> > bottom_vecs_ 
    存每一層輸入(bottom)的id vector<vector<int> > bottom_id_vecs_ 
    存每一層輸出(top)的blob vector<vector<Blob<Dtype>*> > top_vecs_ 
    用網絡的層數param.layers_size()去初始化上面四個變量 
    vector<vector<int> > top_id_vecs_ 
6. 對第i層(很大的一個for循環): 
    param.layers(i)返回的是關於第當前層的參數: 
    layer_param = param.layers(i) 
    把當前層的參數轉換爲shared_ptr<Layer<Dtype>>,並壓入到layers_中 
    把當前層的名字壓入到layer_names_:vector<string> layer_names_ 
    判斷當前層是否需要反饋 need_backward = param.force_backward() 

    下面開始產生當前層:分爲處理bottom的blob和top的blob兩個步驟 
    對第j個bottom的blob: 
        layer_param.bottom_size()存的是當前層的輸入blob數量 
        layer_param.bottom(j)存的是第j個輸入blob的名字 
        讀取當前blob的id,其中blob_name_to_idx在輸入層初始化過了 
        blob_name_to_idx[blob_name] = i 
        輸出當前blob的名字 
        存入第j個輸入blob的指針bottom_vecs_[i].push_back(blobs_[blob_id].get()) 
        存入第j個輸入blob的id bottom_id_vecs_[i].push_back(blob_id) 
        更新need_backward 
        從available_blobs中刪除第j個blob的名字 

    對第j個top的blob: 
        layer_param.top_size()存的是當前層的輸出blob數量 
        layer_param.top(j)存的是第j個輸出blob的名字 
        判斷是否進行同址計算 
        輸出當前blob的名字 
        定義一塊新的blob空間,用blob_pointer指向這塊空間 
        把這個指針存入到blobs_中 
        把blob_name、force_backward、idx存入對應的容器中 
        向available_blobs插入當前blob的名字 
        top_vecs_[i]對於第i層,插入當前blob的指針 
        top_id_vecs_[i]對於第i層,插入當前blob的id 
    輸出當前層位於top的blob的信息 
    計算所需內存 
    判斷當前層i是否需要backward 

7. 所有名字在available_blobs中的blob爲當前層的輸出blob,存入net_output_blobs_中 
8. 建立每個blob的name和index的對應關係map:blob_names_index_ 
9. 建立每個層的name和index的對應關係map:layer_names_index_ 
10. 調用GetLearningRateAndWeightDecay函數

ForwardFromTo

前向過程,就是把每一層的loss加起來。

Dtype Net<Dtype>::ForwardFromTo(int start, int end) {
  CHECK_GE(start, 0);
  CHECK_LT(end, layers_.size());
  Dtype loss = 0;
  for (int i = start; i <= end; ++i) {
    // LOG(ERROR) << "Forwarding " << layer_names_[i];
    Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
    loss += layer_loss;
    if (debug_info_) { ForwardDebugInfo(i); }
  }
  return loss;
}

BackwardFromTo

反向過程,把具有反向傳播的層進行反向傳播運算。

template <typename Dtype>
void Net<Dtype>::BackwardFromTo(int start, int end) {
  CHECK_GE(end, 0);
  CHECK_LT(start, layers_.size());
  for (int i = start; i >= end; --i) {
    if (layer_need_backward_[i]) {
      layers_[i]->Backward(
          top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
      if (debug_info_) { BackwardDebugInfo(i); }
    }
  }
}

Update

更新,每一層的權重分別更新。

template <typename Dtype>
void Net<Dtype>::Update() {
  for (int i = 0; i < learnable_params_.size(); ++i) {
    learnable_params_[i]->Update();
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章