From 324973aa6cc9909fc0b09c939f6c0056942cfc04 Mon Sep 17 00:00:00 2001 From: bhack Date: Sun, 13 Jul 2014 01:09:10 +0200 Subject: [PATCH] Add split dim layer Differentiate top test blob vector size Rename to SplitLayer Add slicing points --- include/caffe/common_layers.hpp | 38 ++++++++++ src/caffe/layer_factory.cpp | 2 + src/caffe/layers/slice_layer.cpp | 135 ++++++++++++++++++++++++++++++++++++ src/caffe/layers/slice_layer.cu | 74 ++++++++++++++++++++ src/caffe/proto/caffe.proto | 13 +++- src/caffe/test/test_slice_layer.cpp | 131 ++++++++++++++++++++++++++++++++++ 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 src/caffe/layers/slice_layer.cpp create mode 100644 src/caffe/layers/slice_layer.cu create mode 100644 src/caffe/test/test_slice_layer.cpp diff --git a/include/caffe/common_layers.hpp b/include/caffe/common_layers.hpp index 395ea7d..643c0ee 100644 --- a/include/caffe/common_layers.hpp +++ b/include/caffe/common_layers.hpp @@ -178,6 +178,44 @@ class SplitLayer : public Layer { int count_; }; +/* SliceLayer + Takes one blobs and slice it along either num or + channel dim, outputting the result. +*/ +template +class SliceLayer : public Layer { + public: + explicit SliceLayer(const LayerParameter& param) + : Layer(param) {} + virtual void SetUp(const vector*>& bottom, + vector*>* top); + + virtual inline LayerParameter_LayerType type() const { + return LayerParameter_LayerType_SLICE; + } + virtual inline int ExactNumBottomBlobs() const { return 1; } + virtual inline int MinTopBlobs() const { return 2; } + + protected: + virtual Dtype Forward_cpu(const vector*>& bottom, + vector*>* top); + virtual Dtype Forward_gpu(const vector*>& bottom, + vector*>* top); + virtual void Backward_cpu(const vector*>& top, + const vector& propagate_down, vector*>* bottom); + virtual void Backward_gpu(const vector*>& top, + const vector& propagate_down, vector*>* bottom); + + Blob col_bob_; + int count_; + int num_; + int channels_; + int height_; + int width_; + int slice_dim_; + google::protobuf::RepeatedField slice_point_; +}; + } // namespace caffe #endif // CAFFE_COMMON_LAYERS_HPP_ diff --git a/src/caffe/layer_factory.cpp b/src/caffe/layer_factory.cpp index e6cbe18..de96021 100644 --- a/src/caffe/layer_factory.cpp +++ b/src/caffe/layer_factory.cpp @@ -71,6 +71,8 @@ Layer* GetLayer(const LayerParameter& param) { return new SigmoidLayer(param); case LayerParameter_LayerType_SIGMOID_CROSS_ENTROPY_LOSS: return new SigmoidCrossEntropyLossLayer(param); + case LayerParameter_LayerType_SLICE: + return new SliceLayer(param); case LayerParameter_LayerType_SOFTMAX: return new SoftmaxLayer(param); case LayerParameter_LayerType_SOFTMAX_LOSS: diff --git a/src/caffe/layers/slice_layer.cpp b/src/caffe/layers/slice_layer.cpp new file mode 100644 index 0000000..c6a9b05 --- /dev/null +++ b/src/caffe/layers/slice_layer.cpp @@ -0,0 +1,135 @@ +// Copyright 2014 BVLC and contributors. + +#include + +#include "caffe/layer.hpp" +#include "caffe/vision_layers.hpp" +#include "caffe/util/math_functions.hpp" + +namespace caffe { + +template +void SliceLayer::SetUp(const vector*>& bottom, + vector*>* top) { + Layer::SetUp(bottom, top); + slice_dim_ = this->layer_param_.slice_param().slice_dim(); + slice_point_ = this->layer_param_.slice_param().slice_point(); + CHECK_GE(slice_dim_, 0) << + "slice_dim should be >= 0"; + CHECK_LE(slice_dim_, 1) << + "For now slice_dim <=1, it can only slice num and channels"; + count_ = 0; + num_ = bottom[0]->num(); + channels_ = bottom[0]->channels(); + height_ = bottom[0]->height(); + width_ = bottom[0]->width(); + if (slice_point_.size() != 0) { + CHECK_EQ(slice_point_.size(), top->size()-1); + if (slice_dim_ == 0) { + CHECK_LE(top->size(), num_); + } else { + CHECK_LE(top->size(), channels_); + } + int prev = 0; + vector slices; + for (int i = 0; i < slice_point_.size(); ++i) { + CHECK_GT(slice_point_.Get(i), prev); + slices.push_back(slice_point_.Get(i) - prev); + prev = slice_point_.Get(i); + } + if (slice_dim_ == 0) { + slices.push_back(num_ - prev); + for (int i = 0; i < top->size(); ++i) { + (*top)[i]->Reshape(slices[i], channels_, height_, width_); + count_ += (*top)[i]->count(); + } + } else { + slices.push_back(channels_ - prev); + for (int i = 0; i < top->size(); ++i) { + (*top)[i]->Reshape(num_, slices[i], height_, width_); + count_ += (*top)[i]->count(); + } + } + + } else { + if (slice_dim_ == 0) { + int remainder = num_%top->size(); + CHECK_EQ(remainder, 0); + num_ = num_/top->size(); + } else { + int remainder = channels_%top->size(); + CHECK_EQ(remainder, 0); + channels_ = channels_/top->size(); + } + for (int i = 0; i < top->size(); ++i) { + (*top)[i]->Reshape(num_, channels_, height_, width_); + count_ += (*top)[i]->count(); + } + } + CHECK_EQ(count_, bottom[0]->count()); +} + +template +Dtype SliceLayer::Forward_cpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->mutable_cpu_data(); + if (slice_dim_ == 0) { + int offset_num = 0; + for (int i = 0; i < top->size(); ++i) { + Blob* blob = (*top)[i]; + Dtype* top_data = blob->mutable_cpu_data(); + caffe_copy(blob->count(), bottom_data + bottom[0]->offset(offset_num), + top_data); + offset_num += blob->num(); + } + } else if (slice_dim_ == 1) { + int offset_channel = 0; + for (int i = 0; i < top->size(); ++i) { + Blob* blob = (*top)[i]; + Dtype* top_data = blob->mutable_cpu_data(); + int num_elem = blob->channels()*blob->height()*blob->width(); + for (int n = 0; n < num_; ++n) { + caffe_copy(num_elem, bottom_data + bottom[0]->offset(n, offset_channel), + top_data + blob->offset(n)); + } + offset_channel += blob->channels(); + } + } // slice_dim_ is guaranteed to be 0 or 1 by SetU + return Dtype(0.); +} + +template +void SliceLayer::Backward_cpu(const vector*>& top, + const vector& propagate_down, vector*>* bottom) { + Dtype* bottom_diff = (*bottom)[0]->mutable_cpu_diff(); + if (slice_dim_ == 0) { + int offset_num = 0; + for (int i = 0; i < top.size(); ++i) { + Blob* blob = top[i]; + if (propagate_down[i]) { + const Dtype* top_diff = blob->cpu_diff(); + caffe_copy(blob->count(), top_diff, + bottom_diff + (*bottom)[0]->offset(offset_num)); + } + offset_num += blob->num(); + } + } else if (slice_dim_ == 1) { + int offset_channel = 0; + for (int i = 0; i < top.size(); ++i) { + Blob* blob = top[i]; + if (propagate_down[i]) { + const Dtype* top_diff = blob->cpu_diff(); + int num_elem = blob->channels()*blob->height()*blob->width(); + for (int n = 0; n < num_; ++n) { + caffe_copy(num_elem, top_diff + blob->offset(n), + bottom_diff + (*bottom)[0]->offset(n, offset_channel)); + } + } + offset_channel += blob->channels(); + } + } // slice_dim_ is guaranteed to be 0 or 1 by SetUp. +} + +INSTANTIATE_CLASS(SliceLayer); + +} // namespace caffe diff --git a/src/caffe/layers/slice_layer.cu b/src/caffe/layers/slice_layer.cu new file mode 100644 index 0000000..c2eeb5c --- /dev/null +++ b/src/caffe/layers/slice_layer.cu @@ -0,0 +1,74 @@ +// Copyright 2014 BVLC and contributors. + +#include + +#include "caffe/layer.hpp" +#include "caffe/vision_layers.hpp" +#include "caffe/util/math_functions.hpp" + +namespace caffe { + +template +Dtype SliceLayer::Forward_gpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->mutable_gpu_data(); + if (slice_dim_ == 0) { + int offset_num = 0; + for (int i = 0; i < top->size(); ++i) { + Blob* blob = (*top)[i]; + Dtype* top_data = blob->mutable_gpu_data(); + caffe_copy(blob->count(), bottom_data + bottom[0]->offset(offset_num), + top_data); + offset_num += blob->num(); + } + } else if (slice_dim_ == 1) { + int offset_channel = 0; + for (int i = 0; i < top->size(); ++i) { + Blob* blob = (*top)[i]; + Dtype* top_data = blob->mutable_gpu_data(); + int num_elem = blob->channels()*blob->height()*blob->width(); + for (int n = 0; n < num_; ++n) { + caffe_copy(num_elem, bottom_data + bottom[0]->offset(n, offset_channel), + top_data + blob->offset(n)); + } + offset_channel += blob->channels(); + } + } // slice_dim_ is guaranteed to be 0 or 1 by SetU + return Dtype(0.); +} + +template +void SliceLayer::Backward_gpu(const vector*>& top, + const vector& propagate_down, vector*>* bottom) { + Dtype* bottom_diff = (*bottom)[0]->mutable_gpu_diff(); + if (slice_dim_ == 0) { + int offset_num = 0; + for (int i = 0; i < top.size(); ++i) { + Blob* blob = top[i]; + if (propagate_down[i]) { + const Dtype* top_diff = blob->gpu_diff(); + caffe_copy(blob->count(), top_diff, + bottom_diff + (*bottom)[0]->offset(offset_num)); + } + offset_num += blob->num(); + } + } else if (slice_dim_ == 1) { + int offset_channel = 0; + for (int i = 0; i < top.size(); ++i) { + Blob* blob = top[i]; + if (propagate_down[i]) { + const Dtype* top_diff = blob->gpu_diff(); + int num_elem = blob->channels()*blob->height()*blob->width(); + for (int n = 0; n < num_; ++n) { + caffe_copy(num_elem, top_diff + blob->offset(n), + bottom_diff + (*bottom)[0]->offset(n, offset_channel)); + } + } + offset_channel += blob->channels(); + } + } // slice_dim_ is guaranteed to be 0 or 1 by SetUp. +} + +INSTANTIATE_CLASS(SliceLayer); + +} // namespace caffe diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 9c3ddfe..d1d5336 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -129,7 +129,7 @@ message LayerParameter { // line above the enum. Update the next available ID when you add a new // LayerType. // - // LayerType next available ID: 33 (last added: DUMMY_DATA) + // LayerType next available ID: 34 (last added: SLICE) enum LayerType { // "NONE" layer type is 0th enum element so that we don't cause confusion // by defaulting to an existent LayerType (instead, should usually error if @@ -164,6 +164,7 @@ message LayerParameter { SOFTMAX = 20; SOFTMAX_LOSS = 21; SPLIT = 22; + SLICE = 33; TANH = 23; WINDOW_DATA = 24; THRESHOLD = 31; @@ -211,6 +212,7 @@ message LayerParameter { optional WindowDataParameter window_data_param = 20; optional ThresholdParameter threshold_param = 25; optional HingeLossParameter hinge_loss_param = 29; + optional SliceParameter slice_param=30; // DEPRECATED: The layer parameters specified as a V0LayerParameter. // This should never be used by any code except to upgrade to the new @@ -440,6 +442,15 @@ message ReLUParameter { optional float negative_slope = 1 [default = 0]; } +// Message that stores parameters used by SliceLayer +message SliceParameter { + // Slice Layer needs to specify the dimension along the slice will happen, + // the other dimensions must be the same for all the bottom blobs + // By default it will slice blobs along channels dimension + optional uint32 slice_dim = 1 [default = 1]; + repeated uint32 slice_point = 2; +} + // Message that stores parameters used by WindowDataLayer message WindowDataParameter { // Specify the data source. diff --git a/src/caffe/test/test_slice_layer.cpp b/src/caffe/test/test_slice_layer.cpp new file mode 100644 index 0000000..8f7bb60 --- /dev/null +++ b/src/caffe/test/test_slice_layer.cpp @@ -0,0 +1,131 @@ +// Copyright 2014 BVLC and contributors. + +#include +#include + +#include "cuda_runtime.h" +#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 SliceLayerTest : public ::testing::Test { + protected: + SliceLayerTest() + : blob_bottom_(new Blob(6, 12, 6, 5)), + blob_top_0(new Blob()), + blob_top_1(new Blob()), + blob_top_2(new Blob()) {} + virtual void SetUp() { + // fill the values + FillerParameter filler_param; + filler_param.set_value(1.); + ConstantFiller filler(filler_param); + filler.Fill(this->blob_bottom_); + blob_top_vec_0.push_back(blob_top_0); + blob_top_vec_0.push_back(blob_top_1); + blob_top_vec_1.push_back(blob_top_0); + blob_top_vec_1.push_back(blob_top_1); + blob_top_vec_1.push_back(blob_top_2); + blob_bottom_vec_.push_back(blob_bottom_); + } + + virtual ~SliceLayerTest() { + delete blob_top_0; delete blob_top_1; + delete blob_top_2; delete blob_bottom_; + } + + Blob* const blob_bottom_; + Blob* const blob_top_0; + Blob* const blob_top_1; + Blob* const blob_top_2; + vector*> blob_top_vec_0, blob_top_vec_1; + vector*> blob_bottom_vec_; +}; + +typedef ::testing::Types Dtypes; +TYPED_TEST_CASE(SliceLayerTest, Dtypes); + +TYPED_TEST(SliceLayerTest, TestCPUSetupNum) { + LayerParameter layer_param; + layer_param.mutable_slice_param()->set_slice_dim(0); + SliceLayer layer(layer_param); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_1)); + EXPECT_EQ(this->blob_bottom_->num(), + this->blob_top_0->num() + this->blob_top_1->num() + + this->blob_top_2->num()); + EXPECT_EQ(this->blob_bottom_->channels(), this->blob_top_0->channels()); + EXPECT_EQ(this->blob_bottom_->height(), this->blob_top_0->height()); + EXPECT_EQ(this->blob_bottom_->width(), this->blob_top_0->width()); +} + +TYPED_TEST(SliceLayerTest, TestCPUSetupChannels) { + LayerParameter layer_param; + layer_param.mutable_slice_param()->add_slice_point(3); + SliceLayer layer(layer_param); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_0)); + EXPECT_EQ(this->blob_top_0->num(), this->blob_bottom_->num()); + EXPECT_EQ(this->blob_top_0->channels(), 3); + EXPECT_EQ(this->blob_top_1->channels(), 9); + EXPECT_EQ(this->blob_bottom_->channels(), + this->blob_top_0->channels()+this->blob_top_1->channels()); + EXPECT_EQ(this->blob_bottom_->height(), this->blob_top_0->height()); + EXPECT_EQ(this->blob_bottom_->width(), this->blob_top_0->width()); +} + + +TYPED_TEST(SliceLayerTest, TestCPUNum) { + LayerParameter layer_param; + SliceLayer layer(layer_param); + Caffe::set_mode(Caffe::CPU); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_0)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_0)); + for (int n = 0; n < this->blob_bottom_->num(); ++n) { + for (int c = 0; c < this->blob_top_0->channels(); ++c) { + for (int h = 0; h < this->blob_bottom_->height(); ++h) { + for (int w = 0; w < this->blob_bottom_->width(); ++w) { + EXPECT_EQ(this->blob_bottom_->data_at(n, c, h, w), + this->blob_top_vec_0[0]->data_at(n, c, h, w)); + } + } + } + for (int c = 0; c < this->blob_top_1->channels(); ++c) { + for (int h = 0; h < this->blob_bottom_->height(); ++h) { + for (int w = 0; w < this->blob_bottom_->width(); ++w) { + EXPECT_EQ(this->blob_bottom_->data_at(n, c+3, h, w), + this->blob_top_vec_0[1]->data_at(n, c, h, w)); + } + } + } + } +} + + +TYPED_TEST(SliceLayerTest, TestCPUGradient) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::CPU); + SliceLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-3); + checker.CheckGradient(&layer, &(this->blob_bottom_vec_), + &(this->blob_top_vec_0)); +} + +TYPED_TEST(SliceLayerTest, TestGPUGradient) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::GPU); + SliceLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-3); + checker.CheckGradient(&layer, &(this->blob_bottom_vec_), + &(this->blob_top_vec_0)); +} + +} // namespace caffe -- 2.7.4