Skip to content

Commit

Permalink
Merge pull request BVLC#2083 from jeffdonahue/tile-layer
Browse files Browse the repository at this point in the history
TileLayer

* jeffdonahue/tile-layer:
  Add TileLayer
  • Loading branch information
myfavouritekk committed Mar 22, 2015
2 parents b12d70a + 77f3cf5 commit 662f336
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 1 deletion.
29 changes: 29 additions & 0 deletions include/caffe/common_layers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,35 @@ class SliceLayer : public Layer<Dtype> {
vector<int> slice_point_;
};

/**
* @brief Copy a Blob along specified dimensions.
*/
template <typename Dtype>
class TileLayer : public Layer<Dtype> {
public:
explicit TileLayer(const LayerParameter& param)
: Layer<Dtype>(param) {}
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);

virtual inline const char* type() const { return "Tile"; }
virtual inline int ExactNumBottomBlobs() const { return 1; }
virtual inline int ExactNumTopBlobs() const { return 1; }

protected:
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);
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

unsigned int axis_, tiles_, outer_dim_, inner_dim_;
};

} // namespace caffe

#endif // CAFFE_COMMON_LAYERS_HPP_
62 changes: 62 additions & 0 deletions src/caffe/layers/tile_layer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <vector>

#include "caffe/common_layers.hpp"
#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void TileLayer<Dtype>::Reshape(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
const TileParameter& tile_param = this->layer_param_.tile_param();
axis_ = bottom[0]->CanonicalAxisIndex(tile_param.axis());
CHECK(tile_param.has_tiles()) << "Number of tiles must be specified";
tiles_ = tile_param.tiles();
CHECK_GT(tiles_, 0) << "Number of tiles must be positive.";
vector<int> top_shape = bottom[0]->shape();
top_shape[axis_] = bottom[0]->shape(axis_) * tiles_;
top[0]->Reshape(top_shape);
outer_dim_ = bottom[0]->count(0, axis_);
inner_dim_ = bottom[0]->count(axis_);
}

template <typename Dtype>
void TileLayer<Dtype>::Forward_cpu(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
const Dtype* bottom_data = bottom[0]->cpu_data();
Dtype* top_data = top[0]->mutable_cpu_data();
for (int i = 0; i < outer_dim_; ++i) {
for (int t = 0; t < tiles_; ++t) {
caffe_copy(inner_dim_, bottom_data, top_data);
top_data += inner_dim_;
}
bottom_data += inner_dim_;
}
}

template <typename Dtype>
void TileLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (!propagate_down[0]) { return; }
const Dtype* top_diff = top[0]->cpu_diff();
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
for (int i = 0; i < outer_dim_; ++i) {
caffe_copy(inner_dim_, top_diff, bottom_diff);
top_diff += inner_dim_;
for (int t = 1; t < tiles_; ++t) {
caffe_axpy(inner_dim_, Dtype(1), top_diff, bottom_diff);
top_diff += inner_dim_;
}
bottom_diff += inner_dim_;
}
}

#ifdef CPU_ONLY
STUB_GPU(TileLayer);
#endif // #ifdef CPU_ONLY

INSTANTIATE_CLASS(TileLayer);
REGISTER_LAYER_CLASS(Tile);

} // namespace caffe
42 changes: 42 additions & 0 deletions src/caffe/layers/tile_layer.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <vector>

#include "caffe/common_layers.hpp"
#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void TileLayer<Dtype>::Forward_gpu(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
const Dtype* bottom_data = bottom[0]->gpu_data();
Dtype* top_data = top[0]->mutable_gpu_data();
for (int i = 0; i < outer_dim_; ++i) {
for (int t = 0; t < tiles_; ++t) {
caffe_copy(inner_dim_, bottom_data, top_data);
top_data += inner_dim_;
}
bottom_data += inner_dim_;
}
}

template <typename Dtype>
void TileLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (!propagate_down[0]) { return; }
const Dtype* top_diff = top[0]->gpu_diff();
Dtype* bottom_diff = bottom[0]->mutable_gpu_diff();
for (int i = 0; i < outer_dim_; ++i) {
caffe_copy(inner_dim_, top_diff, bottom_diff);
top_diff += inner_dim_;
for (int t = 1; t < tiles_; ++t) {
caffe_gpu_axpy(inner_dim_, Dtype(1), top_diff, bottom_diff);
top_diff += inner_dim_;
}
bottom_diff += inner_dim_;
}
}

INSTANTIATE_LAYER_GPU_FUNCS(TileLayer);

} // namespace caffe
12 changes: 11 additions & 1 deletion src/caffe/proto/caffe.proto
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ message ParamSpec {
// NOTE
// Update the next available ID when you add a new LayerParameter field.
//
// LayerParameter next available layer-specific ID: 135 (last added: reduction_param)
// LayerParameter next available layer-specific ID: 136 (last added: tile_param)
message LayerParameter {
optional string name = 1; // the layer name
optional string type = 2; // the layer type
Expand Down Expand Up @@ -337,6 +337,7 @@ message LayerParameter {
optional SliceParameter slice_param = 126;
optional TanHParameter tanh_param = 127;
optional ThresholdParameter threshold_param = 128;
optional TileParameter tile_param = 135;
optional WindowDataParameter window_data_param = 129;
}

Expand Down Expand Up @@ -784,6 +785,15 @@ message TanHParameter {
optional Engine engine = 1 [default = DEFAULT];
}

// Message that stores parameters used by TileLayer
message TileParameter {
// The index of the axis to tile.
optional int32 axis = 1 [default = 1];

// The number of copies (tiles) of the blob to output.
optional int32 tiles = 2;
}

// Message that stores parameters used by ThresholdLayer
message ThresholdParameter {
optional float threshold = 1 [default = 0]; // Strictly positive values
Expand Down
162 changes: 162 additions & 0 deletions src/caffe/test/test_tile_layer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#include <cstring>
#include <vector>

#include "gtest/gtest.h"

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/vision_layers.hpp"

#include "caffe/test/test_caffe_main.hpp"
#include "caffe/test/test_gradient_check_util.hpp"

namespace caffe {

template <typename TypeParam>
class TileLayerTest : public MultiDeviceTest<TypeParam> {
typedef typename TypeParam::Dtype Dtype;

protected:
TileLayerTest()
: blob_bottom_(new Blob<Dtype>(2, 3, 4, 5)),
blob_top_(new Blob<Dtype>()) {}
virtual void SetUp() {
blob_bottom_vec_.push_back(blob_bottom_);
blob_top_vec_.push_back(blob_top_);
FillerParameter filler_param;
filler_param.set_mean(0.0);
filler_param.set_std(1.0);
GaussianFiller<Dtype> filler(filler_param);
filler.Fill(blob_bottom_);
}

virtual ~TileLayerTest() {
delete blob_bottom_;
delete blob_top_;
}

Blob<Dtype>* const blob_bottom_;
Blob<Dtype>* const blob_top_;
vector<Blob<Dtype>*> blob_bottom_vec_;
vector<Blob<Dtype>*> blob_top_vec_;
};

TYPED_TEST_CASE(TileLayerTest, TestDtypesAndDevices);

TYPED_TEST(TileLayerTest, TestTrivialSetup) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kNumTiles = 1;
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
for (int i = 0; i < this->blob_bottom_->num_axes(); ++i) {
layer_param.mutable_tile_param()->set_axis(i);
TileLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
ASSERT_EQ(this->blob_top_->num_axes(), this->blob_bottom_->num_axes());
for (int j = 0; j < this->blob_bottom_->num_axes(); ++j) {
EXPECT_EQ(this->blob_top_->shape(j), this->blob_bottom_->shape(j));
}
}
}

TYPED_TEST(TileLayerTest, TestSetup) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kNumTiles = 3;
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
for (int i = 0; i < this->blob_bottom_->num_axes(); ++i) {
layer_param.mutable_tile_param()->set_axis(i);
TileLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
ASSERT_EQ(this->blob_top_->num_axes(), this->blob_bottom_->num_axes());
for (int j = 0; j < this->blob_bottom_->num_axes(); ++j) {
const int top_dim =
((i == j) ? kNumTiles : 1) * this->blob_bottom_->shape(j);
EXPECT_EQ(top_dim, this->blob_top_->shape(j));
}
}
}

TYPED_TEST(TileLayerTest, TestForwardNum) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kTileAxis = 0;
const int kNumTiles = 3;
layer_param.mutable_tile_param()->set_axis(kTileAxis);
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
TileLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
layer.Forward(this->blob_bottom_vec_, this->blob_top_vec_);
for (int n = 0; n < this->blob_top_->num(); ++n) {
for (int c = 0; c < this->blob_top_->channels(); ++c) {
for (int h = 0; h < this->blob_top_->height(); ++h) {
for (int w = 0; w < this->blob_top_->width(); ++w) {
const int bottom_n = n % this->blob_bottom_->num();
EXPECT_EQ(this->blob_bottom_->data_at(bottom_n, c, h, w),
this->blob_top_->data_at(n, c, h, w));
}
}
}
}
}

TYPED_TEST(TileLayerTest, TestForwardChannels) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kNumTiles = 3;
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
TileLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
layer.Forward(this->blob_bottom_vec_, this->blob_top_vec_);
for (int n = 0; n < this->blob_top_->num(); ++n) {
for (int c = 0; c < this->blob_top_->channels(); ++c) {
for (int h = 0; h < this->blob_top_->height(); ++h) {
for (int w = 0; w < this->blob_top_->width(); ++w) {
const int bottom_c = c % this->blob_bottom_->channels();
EXPECT_EQ(this->blob_bottom_->data_at(n, bottom_c, h, w),
this->blob_top_->data_at(n, c, h, w));
}
}
}
}
}

TYPED_TEST(TileLayerTest, TestTrivialGradient) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kNumTiles = 1;
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
TileLayer<Dtype> layer(layer_param);
GradientChecker<Dtype> checker(1e-2, 1e-2);
checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
this->blob_top_vec_);
}

TYPED_TEST(TileLayerTest, TestGradientNum) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kTileAxis = 0;
const int kNumTiles = 3;
layer_param.mutable_tile_param()->set_axis(kTileAxis);
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
TileLayer<Dtype> layer(layer_param);
GradientChecker<Dtype> checker(1e-2, 1e-2);
checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
this->blob_top_vec_);
}

TYPED_TEST(TileLayerTest, TestGradientChannels) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
const int kTileAxis = 1;
const int kNumTiles = 3;
layer_param.mutable_tile_param()->set_axis(kTileAxis);
layer_param.mutable_tile_param()->set_tiles(kNumTiles);
TileLayer<Dtype> layer(layer_param);
GradientChecker<Dtype> checker(1e-2, 1e-2);
checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
this->blob_top_vec_);
}

} // namespace caffe

0 comments on commit 662f336

Please sign in to comment.