caffe代码阅读5:Layer的实现细节-2016.3.17
2016-03-17 15:57
531 查看
一、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是虚函数,如果不实现就调用
[b]Forward_cpu函数了。[/b]
前传(你必须实现自己的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的相关知识。
相关文章推荐
- 关于代码阅读问题的小技巧 脚本之家原创(适合所有网站)不定时更新
- bp神经网络及matlab实现
- Some Notes of Caffe Installation
- Some Notes of Python Interfaces Pycaffe (Caffe)
- 如何用70行代码实现深度神经网络算法
- 基于神经网络的预测模型
- 计算机视觉领域的牛人博客和有实力的研究机构
- 神经网络初步学习手记
- 科研工作的关注点
- Linux内核阅读--文件路径查找(一)
- Linux内核阅读--文件路径查找(二)
- RBF的一点个人理解
- 利用自收敛深度人工神经网络构建(DNN)构建多语种大词汇量连续语音识别系统
- 最小外接矩形(MBR)
- 色彩量化评价指标 Quantitative measure methods for color quantization
- UFLDL Exercise: Convolutional Neural Network
- 基于遗传算法(GA)的神经网络训练算法
- 图像处理的网址(转载)
- 神经网络 caffe 的 vs2013 版本代码
- 传统BP神经网络完整例子(电力负荷预测)