diff --git a/include/caffe/vision_layers.hpp b/include/caffe/vision_layers.hpp index 82e52cd5bfe..a62325a931d 100644 --- a/include/caffe/vision_layers.hpp +++ b/include/caffe/vision_layers.hpp @@ -128,6 +128,29 @@ class FlattenLayer : public Layer { int count_; }; +template +class ReshapeLayer : public Layer { + public: + explicit ReshapeLayer(const LayerParameter& param) + : Layer(param) {} + virtual void SetUp(const vector*>& bottom, + vector*>* top); + + protected: + virtual void Forward_cpu(const vector*>& bottom, + vector*>* top); + virtual void Forward_gpu(const vector*>& bottom, + vector*>* top); + virtual Dtype Backward_cpu(const vector*>& top, + const bool propagate_down, vector*>* bottom); + virtual Dtype Backward_gpu(const vector*>& top, + const bool propagate_down, vector*>* bottom); + int COUNT_; + int NUM_; + int CHANNELS_; + int HEIGHT_; + int WIDTH_; +}; template class InnerProductLayer : public Layer { diff --git a/src/caffe/layer_factory.cpp b/src/caffe/layer_factory.cpp index b62ba3839c9..153cceb48a3 100644 --- a/src/caffe/layer_factory.cpp +++ b/src/caffe/layer_factory.cpp @@ -33,6 +33,8 @@ Layer* GetLayer(const LayerParameter& param) { return new EuclideanLossLayer(param); } else if (type == "flatten") { return new FlattenLayer(param); + } else if (type == "reshape") { + return new ReshapeLayer(param); } else if (type == "im2col") { return new Im2colLayer(param); } else if (type == "infogain_loss") { diff --git a/src/caffe/layers/reshape_layer.cpp b/src/caffe/layers/reshape_layer.cpp new file mode 100644 index 00000000000..344d1aeace3 --- /dev/null +++ b/src/caffe/layers/reshape_layer.cpp @@ -0,0 +1,64 @@ +// Copyright 2014 Sergio Guadarama + +#include +#include + +#include "caffe/layer.hpp" +#include "caffe/vision_layers.hpp" +#include "caffe/util/math_functions.hpp" + +namespace caffe { + +template +void ReshapeLayer::SetUp(const vector*>& bottom, + vector*>* top) { + CHECK_EQ(bottom.size(), 1) << "Reshape Layer takes a single blob as input."; + CHECK_EQ(top->size(), 1) << "Reshape Layer takes a single blob as output."; + NUM_ = this->layer_param_.new_num(); + CHANNELS_ = this->layer_param_.new_channels(); + HEIGHT_ = this->layer_param_.new_height(); + WIDTH_ = this->layer_param_.new_width(); + COUNT_ = NUM_*CHANNELS_*HEIGHT_*WIDTH_; + (*top)[0]->Reshape(NUM_, CHANNELS_, HEIGHT_, WIDTH_); + CHECK_EQ(COUNT_, bottom[0]->count()); + CHECK_EQ(COUNT_, (*top)[0]->count()); +}; + +template +void ReshapeLayer::Forward_cpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->cpu_data(); + Dtype* top_data = (*top)[0]->mutable_cpu_data(); + caffe_copy(COUNT_, bottom_data, top_data); +} + +template +void ReshapeLayer::Forward_gpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->gpu_data(); + Dtype* top_data = (*top)[0]->mutable_gpu_data(); + caffe_gpu_copy(COUNT_, bottom_data, top_data); +} + +template +Dtype ReshapeLayer::Backward_cpu(const vector*>& top, + const bool propagate_down, vector*>* bottom) { + const Dtype* top_diff = top[0]->cpu_diff(); + Dtype* bottom_diff = (*bottom)[0]->mutable_cpu_diff(); + caffe_copy(COUNT_, top_diff, bottom_diff); + return Dtype(0.); +} + + +template +Dtype ReshapeLayer::Backward_gpu(const vector*>& top, + const bool propagate_down, vector*>* bottom) { + const Dtype* top_diff = top[0]->gpu_diff(); + Dtype* bottom_diff = (*bottom)[0]->mutable_gpu_diff(); + caffe_gpu_copy(COUNT_, top_diff, bottom_diff); + return Dtype(0.); +} + +INSTANTIATE_CLASS(ReshapeLayer); + +} // namespace caffe diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 4da8e8d3522..9a4300c4d33 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -91,6 +91,12 @@ message LayerParameter { // point would be set as rand_skip * rand(0,1). Note that rand_skip should not // be larger than the number of keys in the leveldb. optional uint32 rand_skip = 53 [ default = 0 ]; + + // For the Reshape Layer one need to specify the new dimensions + optional int32 new_num = 60 [default = 0]; + optional int32 new_channels = 61 [default = 0]; + optional int32 new_height = 62 [default = 0]; + optional int32 new_width = 63 [default = 0]; } message LayerConnection { diff --git a/src/caffe/test/test_reshape_layer.cpp b/src/caffe/test/test_reshape_layer.cpp new file mode 100644 index 00000000000..929b7f5617c --- /dev/null +++ b/src/caffe/test/test_reshape_layer.cpp @@ -0,0 +1,174 @@ +// Copyright 2014 Sergio Guadarrama + +#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_gradient_check_util.hpp" + +#include "caffe/test/test_caffe_main.hpp" + +namespace caffe { + +extern cudaDeviceProp CAFFE_TEST_CUDA_PROP; + +template +class ReshapeLayerTest : public ::testing::Test { + protected: + ReshapeLayerTest() + : blob_bottom_(new Blob(2, 3, 6, 5)), + blob_top_(new Blob()) { + // fill the values + FillerParameter filler_param; + GaussianFiller filler(filler_param); + filler.Fill(this->blob_bottom_); + blob_bottom_vec_.push_back(blob_bottom_); + blob_top_vec_.push_back(blob_top_); + }; + virtual ~ReshapeLayerTest() { delete blob_bottom_; delete blob_top_; } + Blob* const blob_bottom_; + Blob* const blob_top_; + vector*> blob_bottom_vec_; + vector*> blob_top_vec_; +}; + +typedef ::testing::Types Dtypes; +TYPED_TEST_CASE(ReshapeLayerTest, Dtypes); + +TYPED_TEST(ReshapeLayerTest, TestSetup) { + LayerParameter layer_param; + layer_param.set_new_num(1); + layer_param.set_new_channels(2 * 3); + layer_param.set_new_height(6); + layer_param.set_new_width(5); + ReshapeLayer layer(layer_param); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + EXPECT_EQ(this->blob_top_->num(), 1); + EXPECT_EQ(this->blob_top_->channels(), 2 * 3); + EXPECT_EQ(this->blob_top_->height(), 6); + EXPECT_EQ(this->blob_top_->width(), 5); +} + +TYPED_TEST(ReshapeLayerTest, TestSetup2) { + // Reshape like flatten + LayerParameter layer_param; + layer_param.set_new_num(2); + layer_param.set_new_channels(3 * 6 * 5); + layer_param.set_new_height(1); + layer_param.set_new_width(1); + ReshapeLayer layer(layer_param); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + EXPECT_EQ(this->blob_top_->num(), 2); + EXPECT_EQ(this->blob_top_->channels(), 3 * 6 * 5); + EXPECT_EQ(this->blob_top_->height(), 1); + EXPECT_EQ(this->blob_top_->width(), 1); +} + +TYPED_TEST(ReshapeLayerTest, TestCPU) { + LayerParameter layer_param; + layer_param.set_new_num(1); + layer_param.set_new_channels(2 * 3); + layer_param.set_new_height(6); + layer_param.set_new_width(5); + ReshapeLayer layer(layer_param); + Caffe::set_mode(Caffe::CPU); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int c = 0; c < 2 * 3; ++c) { + for (int h = 0; h < 6; ++h) { + for (int w = 0; w < 5; ++w) { + EXPECT_EQ(this->blob_top_->data_at(0, c, h, w), + this->blob_bottom_->data_at(c / 3, c % 3, h, w)); + } + } + } +} + +TYPED_TEST(ReshapeLayerTest, TestCPU2) { + LayerParameter layer_param; + layer_param.set_new_num(2); + layer_param.set_new_channels(3 * 6 * 5); + layer_param.set_new_height(1); + layer_param.set_new_width(1); + ReshapeLayer layer(layer_param); + Caffe::set_mode(Caffe::CPU); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int c = 0; c < 3 * 6 * 5; ++c) { + EXPECT_EQ(this->blob_top_->data_at(0, c, 0, 0), + this->blob_bottom_->data_at(0, c / (6 * 5), (c / 5) % 6, c % 5)); + EXPECT_EQ(this->blob_top_->data_at(1, c, 0, 0), + this->blob_bottom_->data_at(1, c / (6 * 5), (c / 5) % 6, c % 5)); + } +} + + + +TYPED_TEST(ReshapeLayerTest, TestGPU) { + LayerParameter layer_param; + layer_param.set_new_num(1); + layer_param.set_new_channels(2 * 3); + layer_param.set_new_height(6); + layer_param.set_new_width(5); + ReshapeLayer layer(layer_param); + Caffe::set_mode(Caffe::GPU); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int c = 0; c < 2 * 3; ++c) { + for (int h = 0; h < 6; ++h) { + for (int w = 0; w < 5; ++w) { + EXPECT_EQ(this->blob_top_->data_at(0, c, h, w), + this->blob_bottom_->data_at(c / 3, c % 3, h, w)); + } + } + } +} + +TYPED_TEST(ReshapeLayerTest, TestGPU2) { + LayerParameter layer_param; + layer_param.set_new_num(2); + layer_param.set_new_channels(3 * 6 * 5); + layer_param.set_new_height(1); + layer_param.set_new_width(1); + ReshapeLayer layer(layer_param); + Caffe::set_mode(Caffe::GPU); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int c = 0; c < 3 * 6 * 5; ++c) { + EXPECT_EQ(this->blob_top_->data_at(0, c, 0, 0), + this->blob_bottom_->data_at(0, c / (6 * 5), (c / 5) % 6, c % 5)); + EXPECT_EQ(this->blob_top_->data_at(1, c, 0, 0), + this->blob_bottom_->data_at(1, c / (6 * 5), (c / 5) % 6, c % 5)); + } +} + +TYPED_TEST(ReshapeLayerTest, TestCPUGradient) { + LayerParameter layer_param; + layer_param.set_new_num(1); + layer_param.set_new_channels(2 * 3); + layer_param.set_new_height(6); + layer_param.set_new_width(5); + Caffe::set_mode(Caffe::CPU); + ReshapeLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); +} + +TYPED_TEST(ReshapeLayerTest, TestGPUGradient) { + LayerParameter layer_param; + layer_param.set_new_num(1); + layer_param.set_new_channels(2 * 3); + layer_param.set_new_height(6); + layer_param.set_new_width(5); + Caffe::set_mode(Caffe::GPU); + ReshapeLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); +} + + +}