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