Layer的實現細節
原文連結:http://blog.csdn.net/xizero00/article/details/50914471
一、Layer的作用簡介
Layer實際上定義了Layer的基本操作,即初始化層、前向傳播和反向傳播。在前向傳播中根據bottom blob得到top blob,反向傳播則根據top反傳到bottom。而且在前傳的時候還可以計算loss,一般來說只有最後一層才會計算loss,雖然每個層都有計算loss的功能。Layer類在沒有實現GPU前傳和反傳的時候會自動使用CPU的實現。下面給出Layer類的具體介紹。
下面給出生成的一幅圖,感性地瞭解一下Layer的層次。
二、Layer類的詳細介紹
1)建構函式
建構函式初始化層的引數,並且設定當前層是否可以共享(如果是資料層則可以共享資料給多個網路)
這裡的blobs_的定義是 vector<shared_ptr<Blob<Dtype> > > blobs_;也就是說它是是blob指標型別的容器。
- explicit Layer(const LayerParameter& param)
- : layer_param_(param), is_shared_(false) {
- // Set phase and copy blobs (if there are any).
- // 訓練還是測試?phase
- phase_ = param.phase();
- if (layer_param_.blobs_size() > 0) {
- // 將blobs_的大小設定為引數中的大小
- blobs_.resize(layer_param_.blobs_size());
- for (int i = 0; i < layer_param_.blobs_size(); ++i) {
- // 新建若干個Blob
- blobs_[i].reset(new Blob<Dtype>());
- // 從blob檔案中獲取資料
- blobs_[i]->FromProto(layer_param_.blobs(i));
- }
- }
- }
2)成員變數
保護性的成員變數:
- /** The protobuf that stores the layer parameters */
- // 層的引數
- LayerParameter layer_param_;
- /** The phase: TRAIN or TEST */
- // 訓練還是測試
- Phase phase_;
- /** The vector that stores the learnable parameters as a set of blobs. */
- // blobs_的是blob指標容器
- vector<shared_ptr<Blob<Dtype> > > blobs_;
- /** Vector indicating whether to compute the diff of each param blob. */
- // 是否需要計算梯度,也即是否需要往下傳播
- vector<bool> param_propagate_down_;
- /** The vector that indicates whether each top blob has a non-zero weight in
- * the objective function. */
- // 每個top blob在目標函式中有非零的權重
- vector<Dtype> loss_;
私有的成員變數:
- /** Whether this layer is actually shared by other nets*/
- // 判斷該層是否被其他層所共享
- // 這個內部變數實際是判斷該層是不是資料層、資料層才可以被其他的網路共享
- bool is_shared_;
- /** The mutex for sequential forward if this layer is shared */
- // 前向傳播的時候所使用的互斥量的指標
- shared_ptr<boost::mutex> forward_mutex_;
3)成員函式
3-1非行內函數:
- /** Initialize forward_mutex_ */
- void InitMutex();
- /** Lock forward_mutex_ if this layer is shared */
- // 如果該層是共享的,則需要鎖住互斥量
- void Lock();
- /** Unlock forward_mutex_ if this layer is shared */
- // 如果該層是共享的,則需要解鎖互斥量
- void Unlock();
3-2行內函數:
- // 判斷該層是否開啟共享模式(即是否資料並行化了)
- inline bool IsShared() const { return is_shared_; }
- // 設定是否共享
- inline void SetShared(bool is_shared) {
- CHECK(ShareInParallel() || !is_shared)
- << type() << "Layer does not support sharing.";
- is_shared_ = is_shared;
- }
- // 前向傳播函式
- // 輸入bottom,計算出top
- inline Dtype Forward(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top);
- // 反向傳播函式
- // 輸入top和propagate_down
- // 輸出bottom
- inline void Backward(const vector<Blob<Dtype>*>& top,
- const vector<bool>& propagate_down,
- const vector<Blob<Dtype>*>& bottom);
- // 返回標量的損失(該損失與top blob相關聯,給定索引就可獲得該損失)
- inline Dtype loss(const int top_index) const {
- return (loss_.size() > top_index) ? loss_[top_index] : Dtype(0);
- }
- // 給定索引,設定top blob相關聯的損失
- inline void set_loss(const int top_index, const Dtype value) {
- if (loss_.size() <= top_index) {
- loss_.resize(top_index + 1, Dtype(0));
- }
- loss_[top_index] = value;
- }
- // 給定param_id返回是否應該計算梯度
- inline bool param_propagate_down(const int param_id) {
- return (param_propagate_down_.size() > param_id) ?
- param_propagate_down_[param_id] : false;
- }
- // 給定param_id設定是否應該計算梯度
- inline void set_param_propagate_down(const int param_id, const bool value) {
- if (param_propagate_down_.size() <= param_id) {
- param_propagate_down_.resize(param_id + 1, true);
- }
- param_propagate_down_[param_id] = value;
- }
- // 設定損失權重??暫時還不懂
- 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) {
- // the amount of weight to assign each top blob in the objective.
- // Each layer assigns a default value, usually of either 0 or 1,
- // to each top blob. loss_weight要麼為0,要麼為1
- const Dtype loss_weight = layer_param_.loss_weight(top_id);
- if (loss_weight == Dtype(0)) { continue; }// 為0則調過
- // loss_weigth為1則
- 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);
- }
- }
- }
3-3類內的函式:
- // SetUp設定層的互斥量、檢查BLOB的引數、呼叫LayerSetUp進行初始化
- // LayerSetUp是一個虛擬函式,使用者可以去過載它。
- // 然後再設定topblob的形狀以及設定損失權重。
- void SetUp(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {
- // 初始化互斥量
- InitMutex();
- // 檢查Blob
- CheckBlobCounts(bottom, top);
- // 層的初始化(虛擬函式,需使用者去實現如何初始化層)
- LayerSetUp(bottom, top);
- // 改變top的形狀(虛擬函式,需使用者去實現如何根據bottomblob改變topblob的形狀)
- Reshape(bottom, top);
- // 設定損失權重
- SetLossWeights(top);
- }
- // 返回blob指標的容器
- vector<shared_ptr<Blob<Dtype> > >& blobs() {
- return blobs_;
- }
- // 返回層的引數
- const LayerParameter& layer_param() const { return layer_param_; }
3-4虛擬函式(純虛擬函式是必須要實現的!!):
- // 虛擬函式,必須自己去實現
- virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {}
- // 在資料並行化的時候,層是否可以在多個網路之間共享
- // 預設是隻有資料層才能在多個網路之間共享,其他層則不行
- // 資料層應該在資料並行化的時候確保每個solver能夠順序地訪問資料
- virtual inline bool ShareInParallel() const { return false; }
- // 純虛擬函式(Reshape必須要實現)
- virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) = 0;
- // 把層引數寫入到proto檔案
- virtual void ToProto(LayerParameter* param, bool write_diff = false);
- // 虛擬函式,而且還是內聯的,返回層型別
- virtual inline const char* type() const { return ""; }
- // 虛擬函式,獲得bottom blob的精確個數
- virtual inline int ExactNumBottomBlobs() const { return -1; }
- // 虛擬函式,獲得bottom blob的最小個數
- virtual inline int MinBottomBlobs() const { return -1; }
- // 虛擬函式,獲得bottom blob的最大個數
- virtual inline int MaxBottomBlobs() const { return -1; }
- // 虛擬函式,獲得top blob的精確個數
- virtual inline int ExactNumTopBlobs() const { return -1; }
- // 虛擬函式,獲得top blob的最小個數
- virtual inline int MinTopBlobs() const { return -1; }
- // 虛擬函式,獲得top blob的最大個數
- virtual inline int MaxTopBlobs() const { return -1; }
- // 虛擬函式,bottom blob和top blob的個數是否一致
- virtual inline bool EqualNumBottomTopBlobs() const { return false; }
- // 返回當前層是否自動建立匿名top blobs
- // 如果返回true,表明網路初始化的時候建立了了足夠多的匿名top blobs
- // 來滿足ExactNumTopBlobs或者MinTopBlobs所要求的top blobs的個數
- virtual inline bool AutoTopBlobs() const { return false; }
- // 對於一個給定的bottom blob,返回是否允許強制反傳
- virtual inline bool AllowForceBackward(const int bottom_index) const {
- return true;
- }
- // 純虛擬函式,必須要實現前向的CPU計算,需要使用者去實現全向傳播CPU,也就是說必須要實現CPU的前向傳播
- virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) = 0;
- // 虛擬函式,需要使用者去實現全向傳播GPU,如果實現GPU則執行GPU的程式碼
- // 如果沒有實現則呼叫預設的CPU的程式碼
- virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {
- // LOG(WARNING) << "Using CPU code as backup.";
- return Forward_cpu(bottom, top);
- }
- // 純虛擬函式,反傳CPU ,必須要實現!!
- virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
- const vector<bool>& propagate_down,
- const vector<Blob<Dtype>*>& bottom) = 0;
- // 虛擬函式,反傳GPU,如果沒有則用CPU的反傳
- virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
- const vector<bool>& propagate_down,
- const vector<Blob<Dtype>*>& bottom) {
- // LOG(WARNING) << "Using CPU code as backup.";
- Backward_cpu(top, propagate_down, bottom);
- }
- // 該函式在SetUp中被呼叫
- // 檢查Blob的一些引數是否正確
- // 比如:
- // 精確的底層blob數目
- // 最小的底層blob數目
- // 最大的底層blob數目
- // 精確的頂層blob數目
- // 最小的頂層blob數目
- // 最大的頂層blob數目
- // 此外還檢查頂層和底層是否一致
- virtual void CheckBlobCounts(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {
- if (ExactNumBottomBlobs() >= 0) {
- CHECK_EQ(ExactNumBottomBlobs(), bottom.size())
- << type() << " Layer takes " << ExactNumBottomBlobs()
- << " bottom blob(s) as input.";
- }
- if (MinBottomBlobs() >= 0) {
- CHECK_LE(MinBottomBlobs(), bottom.size())
- << type() << " Layer takes at least " << MinBottomBlobs()
- << " bottom blob(s) as input.";
- }
- if (MaxBottomBlobs() >= 0) {
- CHECK_GE(MaxBottomBlobs(), bottom.size())
- << type() << " Layer takes at most " << MaxBottomBlobs()
- << " bottom blob(s) as input.";
- }
- if (ExactNumTopBlobs() >= 0) {
- CHECK_EQ(ExactNumTopBlobs(), top.size())
- << type() << " Layer produces " << ExactNumTopBlobs()
- << " top blob(s) as output.";
- }
- if (MinTopBlobs() >= 0) {
- CHECK_LE(MinTopBlobs(), top.size())
- << type() << " Layer produces at least " << MinTopBlobs()
- << " top blob(s) as output.";
- }
- if (MaxTopBlobs() >= 0) {
- CHECK_GE(MaxTopBlobs(), top.size())
- << type() << " Layer produces at most " << MaxTopBlobs()
- << " top blob(s) as output.";
- }
- if (EqualNumBottomTopBlobs()) {
- CHECK_EQ(bottom.size(), top.size())
- << type() << " Layer produces one top blob as output for each "
- << "bottom blob input.";
- }
- }
其中的一些函式的具體實現如下:
主要就是前傳和反傳,前傳呼叫對應的Forward_cpu或者Forward_gpu
而我們知道Forward_cpu是純虛擬函式,必須要實現而Forward_gpu是虛擬函式,如果不實現就呼叫
Forward_cpu函式了。
前傳(你必須實現自己的Forward_cpu,實現Forward_gpu是可選的)
- template <typename Dtype>
- inline Dtype Layer<Dtype>::Forward(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {
- // Lock during forward to ensure sequential forward
- // 前傳的時候需要上鎖,按照順序執行才行,否則就亂了
- Lock();
- Dtype loss = 0;
- // 根據bottom設定top的形狀
- Reshape(bottom, top);
- // 設定執行模式CPU or GPU
- switch (Caffe::mode()) {
- case Caffe::CPU:
- // 呼叫CPU的前傳
- 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();
- // 獲取梯度(\frac{\partial Loss}{\partial net})
- const Dtype* loss_weights = top[top_id]->cpu_diff();
- // data與loss_weight的點積,即得損失函式關於當前層權重的偏導了
- // \frac{\partial Loss}{\partial net} * \frac{\partial net}{\frac{W}}
- // = \frac{\partial Loss}{\partial W}
- loss += caffe_cpu_dot(count, data, loss_weights);
- }
- break;
- case Caffe::GPU:
- // GPU前傳
- Forward_gpu(bottom, top);
- #ifndef CPU_ONLY
- // 同上,只不過這裡用GPU來計算點積了
- for (int top_id = 0; top_id < top.size(); ++top_id) {
- if (!this->loss(top_id)) { continue; }
- const int count = top[top_id]->count();
- // 獲取GPU上的資料
- const Dtype* data = top[top_id]->gpu_data();
- const Dtype* loss_weights = top[top_id]->gpu_diff();
- Dtype blob_loss = 0;
- caffe_gpu_dot(count, data, loss_weights, &blob_loss);
- loss += blob_loss;
- }
- #endif
- break;
- default:
- LOG(FATAL) << "Unknown caffe mode.";
- }
- Unlock();
- return loss;
- }
反傳的道理與前傳的道理很類似
- // 反傳 ,必須實現CPU,但是GPU是可選的
- 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:// CPU反傳
- Backward_cpu(top, propagate_down, bottom);
- break;
- case Caffe::GPU:// GPU反傳
- Backward_gpu(top, propagate_down, bottom);
- break;
- default:
- LOG(FATAL) << "Unknown caffe mode.";
- }
- }
- // 將LayerParameter轉換為ProtoBuf
- template <typename Dtype>
- void Layer<Dtype>::ToProto(LayerParameter* param, bool write_diff) {
- param->Clear();
- param->CopyFrom(layer_param_);
- param->clear_blobs();
- for (int i = 0; i < blobs_.size(); ++i) {
- blobs_[i]->ToProto(param->add_blobs(), write_diff);
- }
- }
- 其他部分的實現:
- // 初始化互斥量
- template <typename Dtype>
- void Layer<Dtype>::InitMutex() {
- forward_mutex_.reset(new boost::mutex());
- }
- // Lock
- template <typename Dtype>
- void Layer<Dtype>::Lock() {
- if (IsShared()) {
- forward_mutex_->lock();
- }
- }
- // UnLock
- template <typename Dtype>
- void Layer<Dtype>::Unlock() {
- if (IsShared()) {
- forward_mutex_->unlock();
- }
- }
三、與Layer類相關類的介紹
(1)用到了device_alternate.hpp
這其中只是定義了一些檢查CUDA是否執行成功的函式、還有就是定義了幾個巨集
下面對其進行介紹:
- // 定義給定類的前向和反向(GPU和CPU)傳播的函式定義
- #define STUB_GPU(classname) \
- template <typename Dtype> \
- void classname<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom, \
- const vector<Blob<Dtype>*>& top) { NO_GPU; } \
- template <typename Dtype> \
- void classname<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top, \
- const vector<bool>& propagate_down, \
- const vector<Blob<Dtype>*>& bottom) { NO_GPU; } \
- #define STUB_GPU_FORWARD(classname, funcname) \
- template <typename Dtype> \
- void classname<Dtype>::funcname##_##gpu(const vector<Blob<Dtype>*>& bottom, \
- const vector<Blob<Dtype>*>& top) { NO_GPU; } \
- #define STUB_GPU_BACKWARD(classname, funcname) \
- template <typename Dtype> \
- void classname<Dtype>::funcname##_##gpu(const vector<Blob<Dtype>*>& top, \
- const vector<bool>& propagate_down, \
- const vector<Blob<Dtype>*>& bottom) { NO_GPU; } \
CUDA檢查的巨集:
- // CUDA: various checks for different function calls.
- #define CUDA_CHECK(condition) \
- /* Code block avoids redefinition of cudaError_t error */ \
- do { \
- cudaError_t error = condition; \
- CHECK_EQ(error, cudaSuccess) << " " << cudaGetErrorString(error); \
- } while (0)
- #define CUBLAS_CHECK(condition) \
- do { \
- cublasStatus_t status = condition; \
- CHECK_EQ(status, CUBLAS_STATUS_SUCCESS) << " " \
- << caffe::cublasGetErrorString(status); \
- } while (0)
- #define CURAND_CHECK(condition) \
- do { \
- curandStatus_t status = condition; \
- CHECK_EQ(status, CURAND_STATUS_SUCCESS) << " " \
- << caffe::curandGetErrorString(status); \
- } while (0)
- // CUDA: grid stride looping
- #define CUDA_KERNEL_LOOP(i, n) \
- for (int i = blockIdx.x * blockDim.x + threadIdx.x; \
- i < (n); \
- i += blockDim.x * gridDim.x)
四、總結
Layer的設計主要就是SetUp、Forward、Backward函式(層一開始的時候的設定、然後就是前傳和反傳)
這其中的SetUp的實現又依賴於CheckBlobCounts、LayerSetUp、Reshape等的實現。這其中Reshape又是必須要實現的,因為它是純虛擬函式
這其中的Forward中又依賴於Forward_cpu、Forward_gpu,這其中Forward_cpu又是必須要實現的。
這其中的Backward中又依賴於Backward_cpu、Backward_gpu,這其中Backward_cpu 又是必須要實現的。
參考:
你可能需要了解一下多層感知機的前向傳播和反向傳播。
具體可以參考UFLDL的相關知識。
相關文章
- 理解virtual dom的實現細節-snabbdom
- 使用Covermap實現地形細節
- 迴圈佇列的實現及細節佇列
- Dubbo2.7的Dubbo SPI實現原理細節
- HDFS 原始碼解讀:HadoopRPC 實現細節的探究原始碼HadoopRPC
- 原始碼閱讀之LinkedList實現細節原始碼
- 原始碼閱讀之ArrayList實現細節原始碼
- Dapr實現分散式有狀態服務的細節分散式
- 細節解析 JavaScript 中 bind 函式的模擬實現JavaScript函式
- Promise 規範解讀及實現細節 (二)Promise
- COP4600 檔案系統實現細節
- 【freertos】006-任務切換實現細節
- Redis高可用之哨兵機制實現細節Redis
- Spartacus 註冊和登入頁面的實現細節
- 【freertos】012-事件標誌概念和實現細節事件
- JVM(四)垃圾回收的實現演算法和執行細節JVM演算法
- [譯] ES6:理解引數預設值的實現細節
- SAP 電商雲 Spartacus UI 的響應式 UI 實現細節UI
- 【freertos】007-系統節拍和系統延時管理實現細節
- 探索 YOLO v3 實現細節 - 第5篇 LossYOLO
- 探索 YOLO v3 實現細節 - 第2篇 模型YOLO模型
- 深入探究JVM之垃圾回收演算法實現細節JVM演算法
- 探索 YOLO v3 實現細節 – 第2篇 模型YOLO模型
- Laravel核心解讀–使用者認證系統的實現細節Laravel
- 探索 YOLO v3 實現細節 - 第3篇 網路YOLO
- Rhinoceros 8:實現細節完美的三維建模 mac/win版ROSMac
- C++17 std::variant 詳解:概念、用法和實現細節C++
- 【freertos】010-訊息佇列概念及其實現細節佇列
- 【freertos】004-任務建立與刪除及其實現細節
- 雲原生平臺 Kyma 上建立的 Lambda Function 的技術實現細節介紹Function
- LayIM.AspNetCore Middleware 開發日記(五)Init介面實現細節NetCore
- FSM自動售貨機 verilog 實現及 code 細節講解
- Laravel核心程式碼學習–使用者認證系統的實現細節Laravel
- Laravel核心程式碼學習--使用者認證系統的實現細節Laravel
- 基於Yii2對RabbitMQ的封裝及程式管理實現細節(四)MQ封裝
- 答疑 - SAP OData 框架處理 Metadata 後設資料請求的實現細節框架
- 慢慢細談Android 面試的細節Android面試
- 隱喻現實的《律法之地》,是如何通過細節提升劇情帶入感的?
- layer.oad,layer.open