diff --git a/include/caffe/common_layers.hpp b/include/caffe/common_layers.hpp index b1ac3a93eff..89659e469e6 100644 --- a/include/caffe/common_layers.hpp +++ b/include/caffe/common_layers.hpp @@ -459,6 +459,35 @@ class SliceLayer : public Layer { vector slice_point_; }; +/** + * @brief Copy a Blob along specified dimensions. + */ +template +class TileLayer : public Layer { + public: + explicit TileLayer(const LayerParameter& param) + : Layer(param) {} + virtual void Reshape(const vector*>& bottom, + const vector*>& 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*>& bottom, + const vector*>& top); + virtual void Forward_gpu(const vector*>& bottom, + const vector*>& top); + + virtual void Backward_cpu(const vector*>& top, + const vector& propagate_down, const vector*>& bottom); + virtual void Backward_gpu(const vector*>& top, + const vector& propagate_down, const vector*>& bottom); + + unsigned int axis_, tiles_, outer_dim_, inner_dim_; +}; + } // namespace caffe #endif // CAFFE_COMMON_LAYERS_HPP_ diff --git a/src/caffe/layers/tile_layer.cpp b/src/caffe/layers/tile_layer.cpp new file mode 100644 index 00000000000..2f942e139f3 --- /dev/null +++ b/src/caffe/layers/tile_layer.cpp @@ -0,0 +1,97 @@ +#include + +#include "caffe/common_layers.hpp" +#include "caffe/layer.hpp" +#include "caffe/util/math_functions.hpp" + +namespace caffe { + +template +void TileLayer::Reshape( + const vector*>& bottom, const vector*>& top) { + const TileParameter& tile_param = this->layer_param_.tile_param(); + CHECK(tile_param.has_tiles()) << "Number of tiles must be specified"; + axis_ = bottom[0]->CanonicalAxisIndex(tile_param.axis()); + tiles_ = tile_param.tiles(); + CHECK_GT(tiles_, 0) << "Number of tiles must be positive."; + vector 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 +void TileLayer::Forward_cpu( + const vector*>& bottom, const vector*>& 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 +void TileLayer::Backward_cpu(const vector*>& top, + const vector& propagate_down, const vector*>& 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); +#else + +template +void TileLayer::Forward_gpu( + const vector*>& bottom, const vector*>& 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 +void TileLayer::Backward_gpu(const vector*>& top, + const vector& propagate_down, const vector*>& 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); + +#endif // #ifdef CPU_ONLY + +INSTANTIATE_CLASS(TileLayer); +REGISTER_LAYER_CLASS(Tile); + +} // namespace caffe diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 3b4794664b5..39bff3bc6c8 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -259,7 +259,7 @@ message ParamSpec { // NOTE // Update the next available ID when you add a new LayerParameter field. // -// LayerParameter next available layer-specific ID: 131 (last added: python_param) +// LayerParameter next available layer-specific ID: 132 (last added: tile_param) message LayerParameter { optional string name = 1; // the layer name optional string type = 2; // the layer type @@ -330,6 +330,7 @@ message LayerParameter { optional SliceParameter slice_param = 126; optional TanHParameter tanh_param = 127; optional ThresholdParameter threshold_param = 128; + optional TileParameter tile_param = 131; optional WindowDataParameter window_data_param = 129; } @@ -711,6 +712,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 diff --git a/src/caffe/test/test_tile_layer.cpp b/src/caffe/test/test_tile_layer.cpp new file mode 100644 index 00000000000..540aac3c2d3 --- /dev/null +++ b/src/caffe/test/test_tile_layer.cpp @@ -0,0 +1,162 @@ +#include +#include + +#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 +class TileLayerTest : public MultiDeviceTest { + typedef typename TypeParam::Dtype Dtype; + + protected: + TileLayerTest() + : blob_bottom_(new Blob(2, 3, 4, 5)), + blob_top_(new Blob()) {} + 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 filler(filler_param); + filler.Fill(blob_bottom_); + } + + virtual ~TileLayerTest() { + delete blob_bottom_; + delete blob_top_; + } + + Blob* const blob_bottom_; + Blob* const blob_top_; + vector*> blob_bottom_vec_; + vector*> 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 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 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 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 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 layer(layer_param); + GradientChecker 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 layer(layer_param); + GradientChecker 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 layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_, + this->blob_top_vec_); +} + +} // namespace caffe