【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这一看就明白了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值