一.Concate
作用:實現多個輸入數據的拼接
輸入:x1,x2,…,xk 輸出:y
x1: N*C*H*W
x2: N*C*H*W
xk: N*C*H*W
y: kN*C*H*W(concate_dim=0)
y: N*kC*H*W(concate_dim=1)
參數:兩個作用相同 ①axis ②concate_dim
(1)caffe.proto層參數定義
message ConcatParameter {
//指定拼接的維度,默認爲1:channel;支持負索引:-1表示最後一個維度。
optional int32 axis = 2 [default = 1];
//作用同axis一樣,但不能指定爲負索引。
optional uint32 concat_dim = 1 [default = 1];
}
(2)LayerSetUp
template <typename Dtype>
void ConcatLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
//給出axis以及concate_dim參數信息
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.";
}
(3)Reshape
template <typename Dtype>
void ConcatLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
//獲取輸入維度:num_axis
//獲取拼接參數:concate_param
const int num_axes = bottom[0]->num_axes();
const ConcatParameter& concat_param = this->layer_param_.concat_param();
if (concat_param.has_concat_dim()) {
//獲取指定拼接維度:concate_dim
concat_axis_ = static_cast<int>(concat_param.concat_dim());
// Don't allow negative indexing for concat_dim, a uint32 -- almost
// certainly unintended.
//CHECK_GE(≥):拼接維度≥0,不支持負索引。
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(<):拼接維度<輸入維度
CHECK_LT(concat_axis_, num_axes) << "concat_dim out of range.";
} else {否則,以channel維度進行拼接
//標準化參數索引
concat_axis_ = bottom[0]->CanonicalAxisIndex(concat_param.axis());
}
// Initialize with the first blob.
//初始化輸出top_shape與輸入shape大小一致
vector<int> top_shape = bottom[0]->shape();//輸出top和輸入bottom的shape相同
num_concats_ = bottom[0]->count(0, concat_axis_);//拼接的數目:num_concats_
concat_input_size_ = bottom[0]->count(concat_axis_ + 1);
int bottom_count_sum = bottom[0]->count();
for (int i = 1; i < bottom.size(); ++i) {
CHECK_EQ(num_axes, bottom[i]->num_axes())//判斷輸入維度num_axes是否一致
<< "All inputs must have the same #axes.";
for (int j = 0; j < num_axes; ++j) {
//除去待拼接的維度concate_axis_外,其他維度大小是否相同
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();//累加第i個輸入的個數
top_shape[concat_axis_] += bottom[i]->shape(concat_axis_);//累加到輸出top
}
top[0]->Reshape(top_shape);//Reshape輸出top
CHECK_EQ(bottom_count_sum, top[0]->count());
if (bottom.size() == 1) {
//只有一個輸入bottom[0],直接複製成輸出top[0]
//梯度shape也和輸入bottom[0]一致
top[0]->ShareData(*bottom[0]);
top[0]->ShareDiff(*bottom[0]);
}
}
(2)Forward前向傳播
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) {//遍歷所有bottom
const Dtype* bottom_data = bottom[i]->cpu_data();
const int bottom_concat_axis = bottom[i]->shape(concat_axis_);
for (int n = 0; n < num_concats_; ++n) {
//bottom複製給top
caffe_copy(bottom_concat_axis * concat_input_size_,
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;
}
}
(3) Backward反向傳播
原理:把輸出求得的梯度直接複製給輸入即可。
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_);
for (int i = 0; i < bottom.size(); ++i) {
const int bottom_concat_axis = bottom[i]->shape(concat_axis_);
if (propagate_down[i]) {
Dtype* bottom_diff = bottom[i]->mutable_cpu_diff();
for (int n = 0; n < num_concats_; ++n) {
caffe_copy(bottom_concat_axis * concat_input_size_, top_diff +
(n * top_concat_axis + offset_concat_axis) * concat_input_size_,
bottom_diff + n * bottom_concat_axis * concat_input_size_);
}
}
offset_concat_axis += bottom_concat_axis;
}
}
(4)Usage使用
layer{
name: "feat_all"
type: "Concat"
bottom: "feat"//輸入a
bottom: "feat_p"//輸入b
top: "feat_all"//concate輸出
concat_param {
axis: 1//在channel維度進行拼接
}
}
二.Slice
作用:把輸入按維度進行切片
(1)caffe.proto層參數定義
參數:①axis ②slice_point ③slice_dim
message SliceParameter {
optional int32 axis = 3 [default = 1];//要進行切片的維度
repeated uint32 slice_point = 2;//將維度axis按照slice_point進行切分
optional uint32 slice_dim = 1 [default = 1];//作用同axis
}
同樣包括LayerSetUp、Reshape、Forward、Backard
(1)LayerSetUp
template <typename Dtype>
void SliceLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
//獲取slice參數(slice_param):axis和slice_dim
const SliceParameter& slice_param = this->layer_param_.slice_param();
CHECK(!(slice_param.has_axis() && slice_param.has_slice_dim()))
<< "Either axis or slice_dim should be specified; not both.";
slice_point_.clear();
std::copy(slice_param.slice_point().begin(),
slice_param.slice_point().end(),
std::back_inserter(slice_point_));
}
(2)Reshape
template <typename Dtype>
void SliceLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const int num_axes = bottom[0]->num_axes();//輸入維度數目
const SliceParameter& slice_param = this->layer_param_.slice_param();
if (slice_param.has_slice_dim()) {
//若指定切片維度,則在相應維度上進行切片
slice_axis_ = static_cast<int>(slice_param.slice_dim());
// Don't allow negative indexing for slice_dim, a uint32 -- almost
// certainly unintended.
//0≤切片維度<輸入維度數目
CHECK_GE(slice_axis_, 0) << "casting slice_dim from uint32 to int32 "
<< "produced negative result; slice_dim must satisfy "
<< "0 <= slice_dim < " << kMaxBlobAxes;
CHECK_LT(slice_axis_, num_axes) << "slice_dim out of range.";
//若沒有指定切片維度,則在channel維度上進行切分
//標準化參數索引
slice_axis_ = bottom[0]->CanonicalAxisIndex(slice_param.axis());
}
vector<int> top_shape = bottom[0]->shape();
//獲取切片維度slice_axis_大小shape(.)
const int bottom_slice_axis = bottom[0]->shape(slice_axis_);
//計算每次切分在一個batch中需要切分的次數,若在channel維進行切分,等於batchsize
num_slices_ = bottom[0]->count(0, slice_axis_);
//切分後feature map的大小,若在channel維度切分,等於H×W
slice_size_ = bottom[0]->count(slice_axis_ + 1);
int count = 0;
if (slice_point_.size() != 0) {
CHECK_EQ(slice_point_.size(), top.size() - 1);
CHECK_LE(top.size(), bottom_slice_axis);
int prev = 0;
vector<int> slices;
for (int i = 0; i < slice_point_.size(); ++i) {
CHECK_GT(slice_point_[i], prev);
//統計每次切分的跨度
slices.push_back(slice_point_[i] - prev);
prev = slice_point_[i];
}
slices.push_back(bottom_slice_axis - prev);
//根據切分跨度,對輸出top_shape進行Reshape,並統計每個輸出切分維度的大小
for (int i = 0; i < top.size(); ++i) {
top_shape[slice_axis_] = slices[i];
top[i]->Reshape(top_shape);
count += top[i]->count();
}
} else {
CHECK_EQ(bottom_slice_axis % top.size(), 0)
<< "Number of top blobs (" << top.size() << ") should evenly "
<< "divide input slice axis (" << bottom_slice_axis << ")";
top_shape[slice_axis_] = bottom_slice_axis / top.size();
for (int i = 0; i < top.size(); ++i) {
top[i]->Reshape(top_shape);
count += top[i]->count();
}
}
CHECK_EQ(count, bottom[0]->count());
if (top.size() == 1) {
top[0]->ShareData(*bottom[0]);
top[0]->ShareDiff(*bottom[0]);
}
}
(3)Forward前向傳播
template <typename Dtype>
void SliceLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
//只有一個輸出數據,不進行切分
if (top.size() == 1) { return; }
int offset_slice_axis = 0;
//獲取輸入數據bottom_data
const Dtype* bottom_data = bottom[0]->cpu_data();
//獲取要切分的維度slice_axis_大小shape(.)
const int bottom_slice_axis = bottom[0]->shape(slice_axis_);
for (int i = 0; i < top.size(); ++i) {
Dtype* top_data = top[i]->mutable_cpu_data();//對輸出top_data進行slice
const int top_slice_axis = top[i]->shape(slice_axis_);//要切分的維度大小
for (int n = 0; n < num_slices_; ++n) {
const int top_offset = n * top_slice_axis * slice_size_;
const int bottom_offset =
(n * bottom_slice_axis + offset_slice_axis) * slice_size_;
//拷貝數據
caffe_copy(top_slice_axis * slice_size_,
bottom_data + bottom_offset, top_data + top_offset);
}
//移動下一個切分點
offset_slice_axis += top_slice_axis;
}
}
(4)Backward反向傳播
template <typename Dtype>
void SliceLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (!propagate_down[0] || top.size() == 1) { return; }
int offset_slice_axis = 0;
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
const int bottom_slice_axis = bottom[0]->shape(slice_axis_);
for (int i = 0; i < top.size(); ++i) {
const Dtype* top_diff = top[i]->cpu_diff();
const int top_slice_axis = top[i]->shape(slice_axis_);
for (int n = 0; n < num_slices_; ++n) {
//將top diff按照切分大小分別拷貝到bottom_diff中,每次移動top_slice_axis = 4個feature map
const int top_offset = n * top_slice_axis * slice_size_;
//而bottom diff則每次要移動16個feature map,對應不同的輸入
const int bottom_offset =
(n * bottom_slice_axis + offset_slice_axis) * slice_size_;
//拷貝數據
caffe_copy(top_slice_axis * slice_size_,
top_diff + top_offset, bottom_diff + bottom_offset);
}
//移動下一個切分點
//bottom diff指針移動到下一個切分點,然後繼續從top diff中拷貝
offset_slice_axis += top_slice_axis;
}
}
(5)Usage使用
說明:slice_point的個數等於top個數減去1
layer {
name: "slice_pair"
type: "Slice"
bottom: "pair_data"
top: "data"
top: "data_p"
slice_param {
slice_dim: 1 //在channel維度上進行slice
slice_point: 1
}
}