Caffe源码精读 - 11 - Caffe Layers之concat_layer

Class_11 Caffe Layers之concat

概述

concat层的主要作用就是做特征融合。将两个及以上的特征图,在某一个数据轴(一般是通道轴)做拼接,起到组合特征的作用。

1. 操作

concat操作相对简单,只是简单地在同纬度进行拼接,并没有像eltwise层一样的运算操作。因此,在caffe中核心的处理就是拷贝(caffe_copy)。

举个栗子,有两个通道不同,但其他维度均相同的特征图A和B,shape分别是1*3*300*300和1*6*300*300。如果按照C轴进行concat操作,得到得新的特征图的shape为1*(3+6)*300*300。

需要特别注意的是,如果concat输入只有一个,即bottom.size()==0,则不作任何操作,仅是简单复制。

2. 直接看代码

concat_layer的代码相对简单,主要就是四个函数成员,分别是LayerSetUp、Reshape、Forward_cpu(Forward_gpu)和Backward_cpu(Backward_gpu)。

(1)LayerSetUp

LayerSetUp相对简单,只是做了一个检查,检查caffe.proto中是否指定了axis参数或concat_dim参数,注意两者有其一即可,不能同时指定。

template <typename Dtype>

void ConcatLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) 

{

    const ConcatParameter& concat_param = this->layer_param_.concat_param();

    CHECK(!(concat_param.has_axis() && concat_param.has_concat_dim()))

        << "Either axis or concat_dim should be specified; not both.";

}

(2)Reshape

Reshape和其它层功能是一样的,无非就是推算输出blob的shape,外加一些参数的推算。

template <typename Dtype>

void ConcatLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) 

{

    const int num_axes = bottom[0]->num_axes(); ///< 有多少个数据轴,一般是4个,分别是NCHW

    const ConcatParameter& concat_param = this->layer_param_.concat_param();

    if (concat_param.has_concat_dim()) {  ///< 是否指定了操作的轴,即在哪个轴(NCHW中某一个)上执行concat

        concat_axis_ = static_cast<int>(concat_param.concat_dim());

        // Don't allow negative indexing for concat_dim, a uint32 -- almost

        // certainly unintended.

        CHECK_GE(concat_axis_, 0) << "casting concat_dim from uint32 to int32 "

            << "produced negative result; concat_dim must satisfy "

            << "0 <= concat_dim < " << kMaxBlobAxes;

        CHECK_LT(concat_axis_, num_axes) << "concat_dim out of range.";

    } else {

        concat_axis_ = bottom[0]->CanonicalAxisIndex(concat_param.axis()); ///< axis默认取值为1

    }

    // Initialize with the first blob.

    vector<int> top_shape = bottom[0]->shape();

    num_concats_ = bottom[0]->count(0, concat_axis_); ///< 共有多少个参与concat的模块,如concat_axis_ = 1,即是N,也就是batch_size

    concat_input_size_ = bottom[0]->count(concat_axis_ + 1); ///< H*W

    int bottom_count_sum = bottom[0]->count(); ///< 一个输入的总数据量 N*C*H*W

    for (int i = 1; i < bottom.size(); ++i) { ///< 一般concat层有>=2个bottom

        CHECK_EQ(num_axes, bottom[i]->num_axes())

            << "All inputs must have the same #axes.";

        for (int j = 0; j < num_axes; ++j) { ///< 一般是遍历0-3,即NCHW四个轴,此处是做检查,除了执行concat的轴(轴1)之外,其他轴的的shape应该相同

            if (j == concat_axis_) { 

                continue; 

            }

            CHECK_EQ(top_shape[j], bottom[i]->shape(j))

                << "All inputs must have the same shape, except at concat_axis.";

        }

        bottom_count_sum += bottom[i]->count();  ///< 累计总的输出数据量

        top_shape[concat_axis_] += bottom[i]->shape(concat_axis_); ///< concat轴的shape信息更新,如在channel轴执行concat,那么channel的shape会累加

    }

    top[0]->Reshape(top_shape);  ///< 执行reshape

    CHECK_EQ(bottom_count_sum, top[0]->count());  ///< 检查输出的数据量

    if (bottom.size() == 1) {  ///< 如果输入只有一个,那么直接引用数据存储块的指针即可

        top[0]->ShareData(*bottom[0]);

        top[0]->ShareDiff(*bottom[0]);

    }

}

concat_axis_: 指定执行concat操作的数据轴,即在哪一个维度上进行组合。一般取值为1。Caffe支持的数据shape形式为NCHW,维度1指通道轴;

num_concats_: 指batch_size信息;

concat_input_size_: 取值为H*W,即特征图的分辨率;

bottom_count_sum: 初始化为索引0输入blob的数据总数,即bottom[0]的N*C*H*W。最终会累计计算出所有输入的总的数据量,作为输出blob的总数据计数。

top_shape: 输出的shape信息。一般只在concat操作的维度上进行累加运算,其他维度不变。

(3)Forward_cpu

Forward_cpu即是在对应轴上,将数据copy一份,组合到一起。特别指出,如果只有一个输入,就仅仅是简单的复制一份到输出blob。

template <typename Dtype>

void ConcatLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) 

{

    if (bottom.size() == 1) { ///< 如果输入只有一个,那么直接引用数据存储块的指针即可

        return; 

    }

    Dtype* top_data = top[0]->mutable_cpu_data();

    int offset_concat_axis = 0;

    const int top_concat_axis = top[0]->shape(concat_axis_);

    for (int i = 0; i < bottom.size(); ++i) { ///< 处理每一路输入

        const Dtype* bottom_data = bottom[i]->cpu_data();

     const int bottom_concat_axis = bottom[i]->shape(concat_axis_); ///< shape值,如果concat_axis_==1,即通道轴,如三通道,bottom_concat_axis取3

///< 将值复制到对应位置  如concat_axis_ = 1,num_concats_即是N,也就是batch_size

        for (int n = 0; n < num_concats_; ++n) {             

caffe_copy(bottom_concat_axis * concat_input_size_, ///< concat_input_size_=H*W       复制的数量

                           bottom_data + n * bottom_concat_axis * concat_input_size_,  ///< 数据源

                           top_data + (n * top_concat_axis + offset_concat_axis) * concat_input_size_ ); ///< 数据目的地

        }

        offset_concat_axis += bottom_concat_axis;

    }

}

(4)Backward_cpu

Forward_cpu是将数据,前向的组合。Backward_cpu是将diff后向传播。

template <typename Dtype>

void ConcatLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,

      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) 

{

    if (bottom.size() == 1) { ///< 单一输入,不做处理

        return; 

    }

    const Dtype* top_diff = top[0]->cpu_diff();

    int offset_concat_axis = 0;

    const int top_concat_axis = top[0]->shape(concat_axis_); ///< 如果concat_axis_==1,指输出的通道数

    for (int i = 0; i < bottom.size(); ++i) {

        const int bottom_concat_axis = bottom[i]->shape(concat_axis_); ///< 第i个输入的通道数

        if (propagate_down[i]) { ///< 如果执行反向传播

            Dtype* bottom_diff = bottom[i]->mutable_cpu_diff();

            for (int n = 0; n < num_concats_; ++n) { ///< 执行concat的块数(num_concats_ = N*C,即有多少个H*W)

                caffe_copy(bottom_concat_axis * concat_input_size_, 

                           top_diff +(n * top_concat_axis + offset_concat_axis) * concat_input_size_, ///< 将输出diff中的数据拷贝至输入diff,实现diff的反向传递

                           bottom_diff + n * bottom_concat_axis * concat_input_size_);

            }

        }

        offset_concat_axis += bottom_concat_axis;

    }

}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值