From ba09df7250bc2042209358b9169e01e61c46850a Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 11:53:24 -0800 Subject: [PATCH 01/19] add SplitLayer and Net::AddSplits to transform shared bottom blobs into split layers --- include/caffe/net.hpp | 3 ++ include/caffe/vision_layers.hpp | 21 +++++++++ src/caffe/layer_factory.cpp | 2 + src/caffe/layers/split_layer.cpp | 73 ++++++++++++++++++++++++++++++++ src/caffe/net.cpp | 72 ++++++++++++++++++++++++++++++- 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/caffe/layers/split_layer.cpp diff --git a/include/caffe/net.hpp b/include/caffe/net.hpp index 684d6c5a018..cee1d386705 100644 --- a/include/caffe/net.hpp +++ b/include/caffe/net.hpp @@ -28,6 +28,9 @@ class Net { // Initialize a network with the network parameter. void Init(const NetParameter& param); + // Copy NetParameters with SplitLayers added to replace any shared bottom + // blobs with unique bottom blobs provided by the SplitLayer. + void AddSplits(const NetParameter& param, NetParameter* param_split); // Run forward with the input blobs already fed separately. You can get the // input blobs using input_blobs(). diff --git a/include/caffe/vision_layers.hpp b/include/caffe/vision_layers.hpp index 82e52cd5bfe..4db2556de62 100644 --- a/include/caffe/vision_layers.hpp +++ b/include/caffe/vision_layers.hpp @@ -108,6 +108,27 @@ class DropoutLayer : public NeuronLayer { }; +template +class SplitLayer : public Layer { + public: + explicit SplitLayer(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_; +}; + + template class FlattenLayer : public Layer { public: diff --git a/src/caffe/layer_factory.cpp b/src/caffe/layer_factory.cpp index b62ba3839c9..48d6edf7c86 100644 --- a/src/caffe/layer_factory.cpp +++ b/src/caffe/layer_factory.cpp @@ -53,6 +53,8 @@ Layer* GetLayer(const LayerParameter& param) { return new SoftmaxLayer(param); } else if (type == "softmax_loss") { return new SoftmaxWithLossLayer(param); + } else if (type == "split") { + return new SplitLayer(param); } else if (type == "multinomial_logistic_loss") { return new MultinomialLogisticLossLayer(param); } else { diff --git a/src/caffe/layers/split_layer.cpp b/src/caffe/layers/split_layer.cpp new file mode 100644 index 00000000000..0fbd6d93493 --- /dev/null +++ b/src/caffe/layers/split_layer.cpp @@ -0,0 +1,73 @@ +// Copyright 2014 Jeff Donahue + +#include + +#include "caffe/layer.hpp" +#include "caffe/vision_layers.hpp" +#include "caffe/util/math_functions.hpp" + +namespace caffe { + +template +void SplitLayer::SetUp(const vector*>& bottom, + vector*>* top) { + CHECK_EQ(bottom.size(), 1) << "Split Layer takes a single blob as input."; + CHECK_GE(top->size(), 1) << "Split Layer takes at least one blob as output."; + count_ = bottom[0]->count(); + for (int i = 0; i < top->size(); ++i) { + (*top)[i]->Reshape(bottom[0]->num(), bottom[0]->channels(), + bottom[0]->height(), bottom[0]->width()); + CHECK_EQ(count_, (*top)[i]->count()); + } +}; + +template +void SplitLayer::Forward_cpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->cpu_data(); + for (int i = 0; i < top->size(); ++i) { + Dtype* top_data = (*top)[i]->mutable_cpu_data(); + caffe_copy(count_, bottom_data, top_data); + } +} + +template +void SplitLayer::Forward_gpu(const vector*>& bottom, + vector*>* top) { + const Dtype* bottom_data = bottom[0]->gpu_data(); + for (int i = 0; i < top->size(); ++i) { + Dtype* top_data = (*top)[i]->mutable_gpu_data(); + caffe_gpu_copy(count_, bottom_data, top_data); + } +} + +template +Dtype SplitLayer::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); + for (int i = 1; i < top.size(); ++i) { + top_diff = top[i]->cpu_diff(); + caffe_axpy(count_, Dtype(1.), top_diff, bottom_diff); + } + return Dtype(0.); +} + + +template +Dtype SplitLayer::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); + for (int i = 1; i < top.size(); ++i) { + top_diff = top[i]->gpu_diff(); + caffe_gpu_axpy(count_, Dtype(1.), top_diff, bottom_diff); + } + return Dtype(0.); +} + +INSTANTIATE_CLASS(SplitLayer); + +} // namespace caffe diff --git a/src/caffe/net.cpp b/src/caffe/net.cpp index f265cd36c55..3c4148fe68f 100644 --- a/src/caffe/net.cpp +++ b/src/caffe/net.cpp @@ -1,5 +1,6 @@ // Copyright Yangqing Jia 2013 +#include #include #include #include @@ -29,7 +30,10 @@ Net::Net(const string& param_file) { } template -void Net::Init(const NetParameter& param) { +void Net::Init(const NetParameter& in_param) { + // Create a copy of in_param with splits added where necessary. + NetParameter param; + AddSplits(in_param, ¶m); // Basically, build all the layers and set up its connections. name_ = param.name(); map blob_name_to_idx; @@ -153,6 +157,72 @@ void Net::Init(const NetParameter& param) { } +template +void Net::AddSplits(const NetParameter& param, + NetParameter* param_split) { + // Initialize by copying from the input NetParameter. + param_split->CopyFrom(param); + param_split->clear_layers(); + map blob_name_to_bottom_count; + map blob_name_to_bottom_split_idx; + // Determine for each top blob the number of times it's used as a bottom blob. + for (int i = 0; i < param.layers_size(); ++i) { + const LayerConnection& layer_connection = param.layers(i); + for (int j = 0; j < layer_connection.bottom_size(); ++j) { + const string& blob_name = layer_connection.bottom(j); + blob_name_to_bottom_count[blob_name]++; + } + for (int j = 0; j < layer_connection.top_size(); ++j) { + const string& blob_name = layer_connection.top(j); + blob_name_to_bottom_count[blob_name] = 0; + blob_name_to_bottom_split_idx[blob_name] = 0; + } + } + for (int i = 0; i < param.layers_size(); ++i) { + LayerConnection* layer_connection = param_split->add_layers(); + layer_connection->CopyFrom(param.layers(i)); + // Replace any shared bottom blobs with split layer outputs. + for (int j = 0; j < layer_connection->bottom_size(); ++j) { + const string& blob_name = layer_connection->bottom(j); + const int split_count = blob_name_to_bottom_count[blob_name]; + if (split_count > 1) { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", blob_name_to_bottom_split_idx[blob_name]++); + CHECK_LT(suffix_length, suffix_max_length); + const string& split_blob_name = blob_name + split_suffix; + layer_connection->set_bottom(j, split_blob_name); + } + } + // Create split blob for any top blobs used by other layers as bottom + // blobs more than once. + for (int j = 0; j < layer_connection->top_size(); ++j) { + const string& blob_name = layer_connection->top(j); + const int split_count = blob_name_to_bottom_count[blob_name]; + if (split_count > 1) { + LayerConnection* split_layer_connection = param_split->add_layers(); + split_layer_connection->add_bottom(blob_name); + LayerParameter* split_layer_param = + split_layer_connection->mutable_layer(); + split_layer_param->set_name(blob_name + "_split"); + split_layer_param->set_type("split"); + vector*> split_top_blobs(split_count); + for (int k = 0; k < split_count; ++k) { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", k); + CHECK_LT(suffix_length, suffix_max_length); + const string& split_blob_name = blob_name + split_suffix; + split_layer_connection->add_top(split_blob_name); + } + } + } + } +} + + template void Net::GetLearningRateAndWeightDecay() { LOG(INFO) << "Collecting Learning Rate and Weight Decay."; From 88d1b5fb11ac77b047652f25fe125586425431e8 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 12:07:02 -0800 Subject: [PATCH 02/19] make split_layer backward obey propagate_down --- src/caffe/layers/split_layer.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/caffe/layers/split_layer.cpp b/src/caffe/layers/split_layer.cpp index 0fbd6d93493..cdfd53925c8 100644 --- a/src/caffe/layers/split_layer.cpp +++ b/src/caffe/layers/split_layer.cpp @@ -44,12 +44,14 @@ void SplitLayer::Forward_gpu(const vector*>& bottom, template Dtype SplitLayer::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); - for (int i = 1; i < top.size(); ++i) { - top_diff = top[i]->cpu_diff(); - caffe_axpy(count_, Dtype(1.), top_diff, bottom_diff); + if (propagate_down) { + const Dtype* top_diff = top[0]->cpu_diff(); + Dtype* bottom_diff = (*bottom)[0]->mutable_cpu_diff(); + caffe_copy(count_, top_diff, bottom_diff); + for (int i = 1; i < top.size(); ++i) { + top_diff = top[i]->cpu_diff(); + caffe_axpy(count_, Dtype(1.), top_diff, bottom_diff); + } } return Dtype(0.); } @@ -58,12 +60,14 @@ Dtype SplitLayer::Backward_cpu(const vector*>& top, template Dtype SplitLayer::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); - for (int i = 1; i < top.size(); ++i) { - top_diff = top[i]->gpu_diff(); - caffe_gpu_axpy(count_, Dtype(1.), top_diff, bottom_diff); + if (propagate_down) { + const Dtype* top_diff = top[0]->gpu_diff(); + Dtype* bottom_diff = (*bottom)[0]->mutable_gpu_diff(); + caffe_gpu_copy(count_, top_diff, bottom_diff); + for (int i = 1; i < top.size(); ++i) { + top_diff = top[i]->gpu_diff(); + caffe_gpu_axpy(count_, Dtype(1.), top_diff, bottom_diff); + } } return Dtype(0.); } From 3231853b9edee7bf299b06d95b0caa0548e2f506 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 12:24:00 -0800 Subject: [PATCH 03/19] add split layer tests --- src/caffe/test/test_split_layer.cpp | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/caffe/test/test_split_layer.cpp diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp new file mode 100644 index 00000000000..894f4c50a85 --- /dev/null +++ b/src/caffe/test/test_split_layer.cpp @@ -0,0 +1,105 @@ +// Copyright 2014 Jeff Donahue + +#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 SplitLayerTest : public ::testing::Test { + protected: + SplitLayerTest() + : blob_bottom_(new Blob(2, 3, 6, 5)), + blob_top_a_(new Blob()), + blob_top_b_(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_a_); + blob_top_vec_.push_back(blob_top_b_); + }; + virtual ~SplitLayerTest() { + delete blob_bottom_; + delete blob_top_a_; + delete blob_top_b_; + } + Blob* const blob_bottom_; + Blob* const blob_top_a_; + Blob* const blob_top_b_; + vector*> blob_bottom_vec_; + vector*> blob_top_vec_; +}; + +typedef ::testing::Types Dtypes; +TYPED_TEST_CASE(SplitLayerTest, Dtypes); + +TYPED_TEST(SplitLayerTest, TestSetup) { + LayerParameter layer_param; + SplitLayer layer(layer_param); + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + EXPECT_EQ(this->blob_top_a_->num(), 2); + EXPECT_EQ(this->blob_top_a_->channels(), 3); + EXPECT_EQ(this->blob_top_a_->height(), 6); + EXPECT_EQ(this->blob_top_a_->width(), 5); + EXPECT_EQ(this->blob_top_b_->num(), 2); + EXPECT_EQ(this->blob_top_b_->channels(), 3); + EXPECT_EQ(this->blob_top_b_->height(), 6); + EXPECT_EQ(this->blob_top_b_->width(), 5); +} + +TYPED_TEST(SplitLayerTest, TestCPU) { + LayerParameter layer_param; + SplitLayer 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 i = 0; i < this->blob_bottom_->count(); ++i) { + TypeParam bottom_value = this->blob_bottom_->cpu_data()[i]; + EXPECT_EQ(bottom_value, this->blob_top_a_->cpu_data()[i]); + EXPECT_EQ(bottom_value, this->blob_top_b_->cpu_data()[i]); + } +} + +TYPED_TEST(SplitLayerTest, TestGPU) { + LayerParameter layer_param; + SplitLayer 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 i = 0; i < this->blob_bottom_->count(); ++i) { + TypeParam bottom_value = this->blob_bottom_->cpu_data()[i]; + EXPECT_EQ(bottom_value, this->blob_top_a_->cpu_data()[i]); + EXPECT_EQ(bottom_value, this->blob_top_b_->cpu_data()[i]); + } +} + +TYPED_TEST(SplitLayerTest, TestCPUGradient) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::CPU); + SplitLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); +} + +TYPED_TEST(SplitLayerTest, TestGPUGradient) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::GPU); + SplitLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); +} + +} From d86f9b0ce94b45d899a077ebeb47e78e257fa7df Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 12:24:00 -0800 Subject: [PATCH 04/19] add split layer tests --- src/caffe/test/test_split_layer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index 894f4c50a85..9a3a2d37579 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -92,6 +92,8 @@ TYPED_TEST(SplitLayerTest, TestCPUGradient) { SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, + this->blob_top_vec_); } TYPED_TEST(SplitLayerTest, TestGPUGradient) { @@ -100,6 +102,8 @@ TYPED_TEST(SplitLayerTest, TestGPUGradient) { SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, + this->blob_top_vec_); } } From 5e89b52bab682340322f707241cd53adc2256192 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 14:39:44 -0800 Subject: [PATCH 05/19] add split layer insertion tests; move split insertion code to util file --- include/caffe/net.hpp | 3 - include/caffe/util/insert_splits.hpp | 21 ++ src/caffe/net.cpp | 69 +----- src/caffe/test/test_split_layer.cpp | 302 +++++++++++++++++++++++++++ src/caffe/util/insert_splits.cpp | 97 +++++++++ 5 files changed, 422 insertions(+), 70 deletions(-) create mode 100644 include/caffe/util/insert_splits.hpp create mode 100644 src/caffe/util/insert_splits.cpp diff --git a/include/caffe/net.hpp b/include/caffe/net.hpp index cee1d386705..684d6c5a018 100644 --- a/include/caffe/net.hpp +++ b/include/caffe/net.hpp @@ -28,9 +28,6 @@ class Net { // Initialize a network with the network parameter. void Init(const NetParameter& param); - // Copy NetParameters with SplitLayers added to replace any shared bottom - // blobs with unique bottom blobs provided by the SplitLayer. - void AddSplits(const NetParameter& param, NetParameter* param_split); // Run forward with the input blobs already fed separately. You can get the // input blobs using input_blobs(). diff --git a/include/caffe/util/insert_splits.hpp b/include/caffe/util/insert_splits.hpp new file mode 100644 index 00000000000..f8aae2885fa --- /dev/null +++ b/include/caffe/util/insert_splits.hpp @@ -0,0 +1,21 @@ +// Copyright 2014 Jeff Donahue + +#ifndef _CAFFE_UTIL_INSERT_SPLITS_HPP_ +#define _CAFFE_UTIL_INSERT_SPLITS_HPP_ + +#include "caffe/proto/caffe.pb.h" + +using std::string; + +namespace caffe { + +// Copy NetParameters with SplitLayers added to replace any shared bottom +// blobs with unique bottom blobs provided by the SplitLayer. +void insert_splits(const NetParameter& param, NetParameter* param_split); + +void configure_split_layer(const string& blob_name, + const int split_count, LayerConnection* split_layer_connection); + +} // namespace caffe + +#endif // CAFFE_UTIL_INSERT_SPLITS_HPP_ diff --git a/src/caffe/net.cpp b/src/caffe/net.cpp index 3c4148fe68f..fbb109fe03b 100644 --- a/src/caffe/net.cpp +++ b/src/caffe/net.cpp @@ -10,6 +10,7 @@ #include "caffe/layer.hpp" #include "caffe/net.hpp" #include "caffe/util/io.hpp" +#include "caffe/util/insert_splits.hpp" using std::pair; using std::map; @@ -33,7 +34,7 @@ template void Net::Init(const NetParameter& in_param) { // Create a copy of in_param with splits added where necessary. NetParameter param; - AddSplits(in_param, ¶m); + insert_splits(in_param, ¶m); // Basically, build all the layers and set up its connections. name_ = param.name(); map blob_name_to_idx; @@ -157,72 +158,6 @@ void Net::Init(const NetParameter& in_param) { } -template -void Net::AddSplits(const NetParameter& param, - NetParameter* param_split) { - // Initialize by copying from the input NetParameter. - param_split->CopyFrom(param); - param_split->clear_layers(); - map blob_name_to_bottom_count; - map blob_name_to_bottom_split_idx; - // Determine for each top blob the number of times it's used as a bottom blob. - for (int i = 0; i < param.layers_size(); ++i) { - const LayerConnection& layer_connection = param.layers(i); - for (int j = 0; j < layer_connection.bottom_size(); ++j) { - const string& blob_name = layer_connection.bottom(j); - blob_name_to_bottom_count[blob_name]++; - } - for (int j = 0; j < layer_connection.top_size(); ++j) { - const string& blob_name = layer_connection.top(j); - blob_name_to_bottom_count[blob_name] = 0; - blob_name_to_bottom_split_idx[blob_name] = 0; - } - } - for (int i = 0; i < param.layers_size(); ++i) { - LayerConnection* layer_connection = param_split->add_layers(); - layer_connection->CopyFrom(param.layers(i)); - // Replace any shared bottom blobs with split layer outputs. - for (int j = 0; j < layer_connection->bottom_size(); ++j) { - const string& blob_name = layer_connection->bottom(j); - const int split_count = blob_name_to_bottom_count[blob_name]; - if (split_count > 1) { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", blob_name_to_bottom_split_idx[blob_name]++); - CHECK_LT(suffix_length, suffix_max_length); - const string& split_blob_name = blob_name + split_suffix; - layer_connection->set_bottom(j, split_blob_name); - } - } - // Create split blob for any top blobs used by other layers as bottom - // blobs more than once. - for (int j = 0; j < layer_connection->top_size(); ++j) { - const string& blob_name = layer_connection->top(j); - const int split_count = blob_name_to_bottom_count[blob_name]; - if (split_count > 1) { - LayerConnection* split_layer_connection = param_split->add_layers(); - split_layer_connection->add_bottom(blob_name); - LayerParameter* split_layer_param = - split_layer_connection->mutable_layer(); - split_layer_param->set_name(blob_name + "_split"); - split_layer_param->set_type("split"); - vector*> split_top_blobs(split_count); - for (int k = 0; k < split_count; ++k) { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", k); - CHECK_LT(suffix_length, suffix_max_length); - const string& split_blob_name = blob_name + split_suffix; - split_layer_connection->add_top(split_blob_name); - } - } - } - } -} - - template void Net::GetLearningRateAndWeightDecay() { LOG(INFO) << "Collecting Learning Rate and Weight Decay."; diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index 9a3a2d37579..aefa50c57d7 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "gtest/gtest.h" #include "caffe/blob.hpp" @@ -9,6 +10,7 @@ #include "caffe/filler.hpp" #include "caffe/vision_layers.hpp" #include "caffe/test/test_gradient_check_util.hpp" +#include "caffe/util/insert_splits.hpp" #include "caffe/test/test_caffe_main.hpp" @@ -106,4 +108,304 @@ TYPED_TEST(SplitLayerTest, TestGPUGradient) { this->blob_top_vec_); } + +template +class SplitLayerInsertionTest : public ::testing::Test { + protected: + SplitLayerInsertionTest() { }; + void RunInsertionTest( + const string& input_param_string, const string& output_param_string) { + NetParameter input_param; + CHECK(google::protobuf::TextFormat::ParseFromString( + input_param_string, &input_param)); + NetParameter expected_output_param; + CHECK(google::protobuf::TextFormat::ParseFromString( + output_param_string, &expected_output_param)); + NetParameter actual_output_param; + insert_splits(input_param, &actual_output_param); + CHECK_EQ(expected_output_param.DebugString(), + actual_output_param.DebugString()); + EXPECT_EQ(expected_output_param.DebugString(), + actual_output_param.DebugString()); + } +}; + +typedef ::testing::Types InsertionDtypes; +TYPED_TEST_CASE(SplitLayerInsertionTest, InsertionDtypes); + +TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion1) { + const string& input_proto = + "name: \"TestNetwork\"\n" + "layers: {\n" + " layer {\n" + " name: \"data\"\n" + " type: \"data\"\n" + " }\n" + " top: \"data\"\n" + " top: \"label\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"softmax_with_loss\"\n" + " }\n" + " bottom: \"innerprod\"\n" + " bottom: \"label\"\n" + "}\n"; + this->RunInsertionTest(input_proto, input_proto); +} + +TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion2) { + const string& input_proto = + "name: \"TestNetwork\"\n" + "layers: {\n" + " layer {\n" + " name: \"data\"\n" + " type: \"data\"\n" + " }\n" + " top: \"data\"\n" + " top: \"label\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"data_split\"\n" + " type: \"split\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"data_split_0\"\n" + " top: \"data_split_1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod1\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_0\"\n" + " top: \"innerprod1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_1\"\n" + " top: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod1\"\n" + " bottom: \"innerprod2\"\n" + "}\n"; + this->RunInsertionTest(input_proto, input_proto); +} + +TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { + const string& input_proto = + "name: \"TestNetwork\"\n" + "layers: {\n" + " layer {\n" + " name: \"data\"\n" + " type: \"data\"\n" + " }\n" + " top: \"data\"\n" + " top: \"label\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod1\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod3\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod3\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod1\"\n" + " bottom: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod2\"\n" + " bottom: \"innerprod3\"\n" + "}\n"; + const string& expected_output_proto = + "name: \"TestNetwork\"\n" + "layers: {\n" + " layer {\n" + " name: \"data\"\n" + " type: \"data\"\n" + " }\n" + " top: \"data\"\n" + " top: \"label\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"data_split\"\n" + " type: \"split\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"data_split_0\"\n" + " top: \"data_split_1\"\n" + " top: \"data_split_2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod1\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_0\"\n" + " top: \"innerprod1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_1\"\n" + " top: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2_split\"\n" + " type: \"split\"\n" + " }\n" + " bottom: \"innerprod2\"\n" + " top: \"innerprod2_split_0\"\n" + " top: \"innerprod2_split_1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod3\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_2\"\n" + " top: \"innerprod3\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod1\"\n" + " bottom: \"innerprod2_split_0\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod2_split_1\"\n" + " bottom: \"innerprod3\"\n" + "}\n"; + this->RunInsertionTest(input_proto, expected_output_proto); +} + +TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { + const string& input_proto = + "name: \"TestNetwork\"\n" + "input: \"data\"\n" + "input_dim: 10\n" + "input_dim: 3\n" + "input_dim: 227\n" + "input_dim: 227\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod1\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod1\"\n" + " bottom: \"innerprod2\"\n" + "}\n"; + const string& expected_output_proto = + "name: \"TestNetwork\"\n" + "input: \"data\"\n" + "input_dim: 10\n" + "input_dim: 3\n" + "input_dim: 227\n" + "input_dim: 227\n" + "layers: {\n" + " layer {\n" + " name: \"data_split\"\n" + " type: \"split\"\n" + " }\n" + " bottom: \"data\"\n" + " top: \"data_split_0\"\n" + " top: \"data_split_1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod1\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_0\"\n" + " top: \"innerprod1\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"innerprod2\"\n" + " type: \"inner_product\"\n" + " }\n" + " bottom: \"data_split_1\"\n" + " top: \"innerprod2\"\n" + "}\n" + "layers: {\n" + " layer {\n" + " name: \"loss\"\n" + " type: \"euclidean_loss\"\n" + " }\n" + " bottom: \"innerprod1\"\n" + " bottom: \"innerprod2\"\n" + "}\n"; + this->RunInsertionTest(input_proto, expected_output_proto); +} + } diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp new file mode 100644 index 00000000000..df8bf5daa26 --- /dev/null +++ b/src/caffe/util/insert_splits.cpp @@ -0,0 +1,97 @@ +// Copyright 2014 Jeff Donahue + +#include +#include +#include + +#include "caffe/common.hpp" +#include "caffe/util/insert_splits.hpp" + +using std::map; + +namespace caffe { + +void insert_splits(const NetParameter& param, NetParameter* param_split) { + // Initialize by copying from the input NetParameter. + param_split->CopyFrom(param); + param_split->clear_layers(); + map blob_name_to_bottom_count; + map blob_name_to_bottom_split_idx; + // Determine for each top blob (including input blobs) the number of times + // it's used as a bottom blob. + for (int i = 0; i < param.input_size(); ++i) { + const string& blob_name = param.input(i); + blob_name_to_bottom_count[blob_name] = 0; + blob_name_to_bottom_split_idx[blob_name] = 0; + } + for (int i = 0; i < param.layers_size(); ++i) { + const LayerConnection& layer_connection = param.layers(i); + for (int j = 0; j < layer_connection.bottom_size(); ++j) { + const string& blob_name = layer_connection.bottom(j); + blob_name_to_bottom_count[blob_name]++; + } + for (int j = 0; j < layer_connection.top_size(); ++j) { + const string& blob_name = layer_connection.top(j); + blob_name_to_bottom_count[blob_name] = 0; + blob_name_to_bottom_split_idx[blob_name] = 0; + } + } + for (int i = 0; i < param.input_size(); ++i) { + const string& blob_name = param.input(i); + const int split_count = blob_name_to_bottom_count[blob_name]; + if (split_count > 1) { + LayerConnection* split_layer_connection = param_split->add_layers(); + configure_split_layer(blob_name, split_count, split_layer_connection); + } + } + for (int i = 0; i < param.layers_size(); ++i) { + LayerConnection* layer_connection = param_split->add_layers(); + layer_connection->CopyFrom(param.layers(i)); + // Replace any shared bottom blobs with split layer outputs. + for (int j = 0; j < layer_connection->bottom_size(); ++j) { + const string& blob_name = layer_connection->bottom(j); + const int split_count = blob_name_to_bottom_count[blob_name]; + if (split_count > 1) { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", blob_name_to_bottom_split_idx[blob_name]++); + CHECK_LT(suffix_length, suffix_max_length); + const string& split_blob_name = blob_name + split_suffix; + layer_connection->set_bottom(j, split_blob_name); + } + } + // Create split blob for any top blobs used by other layers as bottom + // blobs more than once. + for (int j = 0; j < layer_connection->top_size(); ++j) { + const string& blob_name = layer_connection->top(j); + const int split_count = blob_name_to_bottom_count[blob_name]; + if (split_count > 1) { + LayerConnection* split_layer_connection = param_split->add_layers(); + split_layer_connection->add_bottom(blob_name); + configure_split_layer(blob_name, split_count, split_layer_connection); + } + } + } +} + +void configure_split_layer(const string& blob_name, + const int split_count, LayerConnection* split_layer_connection) { + split_layer_connection->Clear(); + split_layer_connection->add_bottom(blob_name); + LayerParameter* split_layer_param = + split_layer_connection->mutable_layer(); + split_layer_param->set_name(blob_name + "_split"); + split_layer_param->set_type("split"); + for (int k = 0; k < split_count; ++k) { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", k); + CHECK_LT(suffix_length, suffix_max_length); + const string& split_blob_name = blob_name + split_suffix; + split_layer_connection->add_top(split_blob_name); + } +} + +} // namespace caffe From add03b8b23ca337d68f9dd87cf28cdcb1c2da7bb Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 14:51:37 -0800 Subject: [PATCH 06/19] some cleanup --- src/caffe/net.cpp | 1 - src/caffe/test/test_split_layer.cpp | 2 -- src/caffe/util/insert_splits.cpp | 4 +++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/caffe/net.cpp b/src/caffe/net.cpp index fbb109fe03b..e976dfd5fd0 100644 --- a/src/caffe/net.cpp +++ b/src/caffe/net.cpp @@ -1,6 +1,5 @@ // Copyright Yangqing Jia 2013 -#include #include #include #include diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index aefa50c57d7..19df34fa03d 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -123,8 +123,6 @@ class SplitLayerInsertionTest : public ::testing::Test { output_param_string, &expected_output_param)); NetParameter actual_output_param; insert_splits(input_param, &actual_output_param); - CHECK_EQ(expected_output_param.DebugString(), - actual_output_param.DebugString()); EXPECT_EQ(expected_output_param.DebugString(), actual_output_param.DebugString()); } diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index df8bf5daa26..c963dc80867 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -36,6 +36,8 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { blob_name_to_bottom_split_idx[blob_name] = 0; } } + // Create split layer for any input blobs user by other layers as bottom + // blobs more than once. for (int i = 0; i < param.input_size(); ++i) { const string& blob_name = param.input(i); const int split_count = blob_name_to_bottom_count[blob_name]; @@ -61,7 +63,7 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { layer_connection->set_bottom(j, split_blob_name); } } - // Create split blob for any top blobs used by other layers as bottom + // Create split layer for any top blobs used by other layers as bottom // blobs more than once. for (int j = 0; j < layer_connection->top_size(); ++j) { const string& blob_name = layer_connection->top(j); From 09c034d757a13d6579197ffc04f3eb38eb0644e1 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 15:52:48 -0800 Subject: [PATCH 07/19] eliminate redundant code with get_split_blob_name method --- include/caffe/util/insert_splits.hpp | 3 +++ src/caffe/util/insert_splits.cpp | 27 +++++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/caffe/util/insert_splits.hpp b/include/caffe/util/insert_splits.hpp index f8aae2885fa..5bf49f1a637 100644 --- a/include/caffe/util/insert_splits.hpp +++ b/include/caffe/util/insert_splits.hpp @@ -16,6 +16,9 @@ void insert_splits(const NetParameter& param, NetParameter* param_split); void configure_split_layer(const string& blob_name, const int split_count, LayerConnection* split_layer_connection); +void get_split_blob_name(const string& blob_name, const int split_index, + string* split_blob_name); + } // namespace caffe #endif // CAFFE_UTIL_INSERT_SPLITS_HPP_ diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index c963dc80867..2638f8ce586 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -54,12 +54,9 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { const string& blob_name = layer_connection->bottom(j); const int split_count = blob_name_to_bottom_count[blob_name]; if (split_count > 1) { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", blob_name_to_bottom_split_idx[blob_name]++); - CHECK_LT(suffix_length, suffix_max_length); - const string& split_blob_name = blob_name + split_suffix; + string split_blob_name; + get_split_blob_name(blob_name, + blob_name_to_bottom_split_idx[blob_name]++, &split_blob_name); layer_connection->set_bottom(j, split_blob_name); } } @@ -86,14 +83,20 @@ void configure_split_layer(const string& blob_name, split_layer_param->set_name(blob_name + "_split"); split_layer_param->set_type("split"); for (int k = 0; k < split_count; ++k) { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", k); - CHECK_LT(suffix_length, suffix_max_length); - const string& split_blob_name = blob_name + split_suffix; + string split_blob_name; + get_split_blob_name(blob_name, k, &split_blob_name); split_layer_connection->add_top(split_blob_name); } } +void get_split_blob_name(const string& blob_name, const int split_index, + string* split_blob_name) { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", split_index); + CHECK_LT(suffix_length, suffix_max_length); + *split_blob_name = blob_name + split_suffix; +} + } // namespace caffe From 167d31a1f6d8d85ef9615342328d12539f61ef62 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 15:56:15 -0800 Subject: [PATCH 08/19] change \n's to less distracting spaces in hard-coded proto strings --- src/caffe/test/test_split_layer.cpp | 506 ++++++++++++++-------------- 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index 19df34fa03d..5d5a184129b 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -133,276 +133,276 @@ TYPED_TEST_CASE(SplitLayerInsertionTest, InsertionDtypes); TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion1) { const string& input_proto = - "name: \"TestNetwork\"\n" - "layers: {\n" - " layer {\n" - " name: \"data\"\n" - " type: \"data\"\n" - " }\n" - " top: \"data\"\n" - " top: \"label\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"softmax_with_loss\"\n" - " }\n" - " bottom: \"innerprod\"\n" - " bottom: \"label\"\n" - "}\n"; + "name: \"TestNetwork\" " + "layers: { " + " layer { " + " name: \"data\" " + " type: \"data\" " + " } " + " top: \"data\" " + " top: \"label\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"softmax_with_loss\" " + " } " + " bottom: \"innerprod\" " + " bottom: \"label\" " + "} "; this->RunInsertionTest(input_proto, input_proto); } TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion2) { const string& input_proto = - "name: \"TestNetwork\"\n" - "layers: {\n" - " layer {\n" - " name: \"data\"\n" - " type: \"data\"\n" - " }\n" - " top: \"data\"\n" - " top: \"label\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"data_split\"\n" - " type: \"split\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"data_split_0\"\n" - " top: \"data_split_1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod1\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_0\"\n" - " top: \"innerprod1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_1\"\n" - " top: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod1\"\n" - " bottom: \"innerprod2\"\n" - "}\n"; + "name: \"TestNetwork\" " + "layers: { " + " layer { " + " name: \"data\" " + " type: \"data\" " + " } " + " top: \"data\" " + " top: \"label\" " + "} " + "layers: { " + " layer { " + " name: \"data_split\" " + " type: \"split\" " + " } " + " bottom: \"data\" " + " top: \"data_split_0\" " + " top: \"data_split_1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod1\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_0\" " + " top: \"innerprod1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_1\" " + " top: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod1\" " + " bottom: \"innerprod2\" " + "} "; this->RunInsertionTest(input_proto, input_proto); } TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { const string& input_proto = - "name: \"TestNetwork\"\n" - "layers: {\n" - " layer {\n" - " name: \"data\"\n" - " type: \"data\"\n" - " }\n" - " top: \"data\"\n" - " top: \"label\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod1\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod3\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod3\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod1\"\n" - " bottom: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod2\"\n" - " bottom: \"innerprod3\"\n" - "}\n"; + "name: \"TestNetwork\" " + "layers: { " + " layer { " + " name: \"data\" " + " type: \"data\" " + " } " + " top: \"data\" " + " top: \"label\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod1\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod3\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod3\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod1\" " + " bottom: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod2\" " + " bottom: \"innerprod3\" " + "} "; const string& expected_output_proto = - "name: \"TestNetwork\"\n" - "layers: {\n" - " layer {\n" - " name: \"data\"\n" - " type: \"data\"\n" - " }\n" - " top: \"data\"\n" - " top: \"label\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"data_split\"\n" - " type: \"split\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"data_split_0\"\n" - " top: \"data_split_1\"\n" - " top: \"data_split_2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod1\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_0\"\n" - " top: \"innerprod1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_1\"\n" - " top: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2_split\"\n" - " type: \"split\"\n" - " }\n" - " bottom: \"innerprod2\"\n" - " top: \"innerprod2_split_0\"\n" - " top: \"innerprod2_split_1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod3\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_2\"\n" - " top: \"innerprod3\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod1\"\n" - " bottom: \"innerprod2_split_0\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod2_split_1\"\n" - " bottom: \"innerprod3\"\n" - "}\n"; + "name: \"TestNetwork\" " + "layers: { " + " layer { " + " name: \"data\" " + " type: \"data\" " + " } " + " top: \"data\" " + " top: \"label\" " + "} " + "layers: { " + " layer { " + " name: \"data_split\" " + " type: \"split\" " + " } " + " bottom: \"data\" " + " top: \"data_split_0\" " + " top: \"data_split_1\" " + " top: \"data_split_2\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod1\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_0\" " + " top: \"innerprod1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_1\" " + " top: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2_split\" " + " type: \"split\" " + " } " + " bottom: \"innerprod2\" " + " top: \"innerprod2_split_0\" " + " top: \"innerprod2_split_1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod3\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_2\" " + " top: \"innerprod3\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod1\" " + " bottom: \"innerprod2_split_0\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod2_split_1\" " + " bottom: \"innerprod3\" " + "} "; this->RunInsertionTest(input_proto, expected_output_proto); } TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { const string& input_proto = - "name: \"TestNetwork\"\n" - "input: \"data\"\n" - "input_dim: 10\n" - "input_dim: 3\n" - "input_dim: 227\n" - "input_dim: 227\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod1\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod1\"\n" - " bottom: \"innerprod2\"\n" - "}\n"; + "name: \"TestNetwork\" " + "input: \"data\" " + "input_dim: 10 " + "input_dim: 3 " + "input_dim: 227 " + "input_dim: 227 " + "layers: { " + " layer { " + " name: \"innerprod1\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2\" " + " type: \"inner_product\" " + " } " + " bottom: \"data\" " + " top: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod1\" " + " bottom: \"innerprod2\" " + "} "; const string& expected_output_proto = - "name: \"TestNetwork\"\n" - "input: \"data\"\n" - "input_dim: 10\n" - "input_dim: 3\n" - "input_dim: 227\n" - "input_dim: 227\n" - "layers: {\n" - " layer {\n" - " name: \"data_split\"\n" - " type: \"split\"\n" - " }\n" - " bottom: \"data\"\n" - " top: \"data_split_0\"\n" - " top: \"data_split_1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod1\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_0\"\n" - " top: \"innerprod1\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"innerprod2\"\n" - " type: \"inner_product\"\n" - " }\n" - " bottom: \"data_split_1\"\n" - " top: \"innerprod2\"\n" - "}\n" - "layers: {\n" - " layer {\n" - " name: \"loss\"\n" - " type: \"euclidean_loss\"\n" - " }\n" - " bottom: \"innerprod1\"\n" - " bottom: \"innerprod2\"\n" - "}\n"; + "name: \"TestNetwork\" " + "input: \"data\" " + "input_dim: 10 " + "input_dim: 3 " + "input_dim: 227 " + "input_dim: 227 " + "layers: { " + " layer { " + " name: \"data_split\" " + " type: \"split\" " + " } " + " bottom: \"data\" " + " top: \"data_split_0\" " + " top: \"data_split_1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod1\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_0\" " + " top: \"innerprod1\" " + "} " + "layers: { " + " layer { " + " name: \"innerprod2\" " + " type: \"inner_product\" " + " } " + " bottom: \"data_split_1\" " + " top: \"innerprod2\" " + "} " + "layers: { " + " layer { " + " name: \"loss\" " + " type: \"euclidean_loss\" " + " } " + " bottom: \"innerprod1\" " + " bottom: \"innerprod2\" " + "} "; this->RunInsertionTest(input_proto, expected_output_proto); } From 9b72c9472187d0978f83642a6770e35b09376337 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 16:00:44 -0800 Subject: [PATCH 09/19] give first top split blob same name as bottom blob --- src/caffe/test/test_split_layer.cpp | 14 ++++++++------ src/caffe/util/insert_splits.cpp | 16 ++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index 5d5a184129b..a3252b23378 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -123,6 +123,8 @@ class SplitLayerInsertionTest : public ::testing::Test { output_param_string, &expected_output_param)); NetParameter actual_output_param; insert_splits(input_param, &actual_output_param); + CHECK_EQ(expected_output_param.DebugString(), + actual_output_param.DebugString()); EXPECT_EQ(expected_output_param.DebugString(), actual_output_param.DebugString()); } @@ -275,7 +277,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { " type: \"split\" " " } " " bottom: \"data\" " - " top: \"data_split_0\" " + " top: \"data\" " " top: \"data_split_1\" " " top: \"data_split_2\" " "} " @@ -284,7 +286,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { " name: \"innerprod1\" " " type: \"inner_product\" " " } " - " bottom: \"data_split_0\" " + " bottom: \"data\" " " top: \"innerprod1\" " "} " "layers: { " @@ -301,7 +303,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { " type: \"split\" " " } " " bottom: \"innerprod2\" " - " top: \"innerprod2_split_0\" " + " top: \"innerprod2\" " " top: \"innerprod2_split_1\" " "} " "layers: { " @@ -318,7 +320,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { " type: \"euclidean_loss\" " " } " " bottom: \"innerprod1\" " - " bottom: \"innerprod2_split_0\" " + " bottom: \"innerprod2\" " "} " "layers: { " " layer { " @@ -376,7 +378,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { " type: \"split\" " " } " " bottom: \"data\" " - " top: \"data_split_0\" " + " top: \"data\" " " top: \"data_split_1\" " "} " "layers: { " @@ -384,7 +386,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { " name: \"innerprod1\" " " type: \"inner_product\" " " } " - " bottom: \"data_split_0\" " + " bottom: \"data\" " " top: \"innerprod1\" " "} " "layers: { " diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index 2638f8ce586..afbaf7f770f 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -91,12 +91,16 @@ void configure_split_layer(const string& blob_name, void get_split_blob_name(const string& blob_name, const int split_index, string* split_blob_name) { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", split_index); - CHECK_LT(suffix_length, suffix_max_length); - *split_blob_name = blob_name + split_suffix; + if (split_index == 0) { + *split_blob_name = blob_name; + } else { + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + const int suffix_length = snprintf(split_suffix, suffix_max_length, + "_split_%d", split_index); + CHECK_LT(suffix_length, suffix_max_length); + *split_blob_name = blob_name + split_suffix; + } } } // namespace caffe From 79ca72d9edcccab5de7d4b27175dab054f7e6332 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 16:28:44 -0800 Subject: [PATCH 10/19] allow in place computation of SplitLayer 0th top blob --- src/caffe/layers/split_layer.cpp | 28 +++++++++++++++-- src/caffe/test/test_split_layer.cpp | 48 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/caffe/layers/split_layer.cpp b/src/caffe/layers/split_layer.cpp index cdfd53925c8..5accdd08e32 100644 --- a/src/caffe/layers/split_layer.cpp +++ b/src/caffe/layers/split_layer.cpp @@ -15,6 +15,12 @@ void SplitLayer::SetUp(const vector*>& bottom, CHECK_GE(top->size(), 1) << "Split Layer takes at least one blob as output."; count_ = bottom[0]->count(); for (int i = 0; i < top->size(); ++i) { + // Allow the 0th top blob to be 'in-place', but no others. + if (i == 0 && (*top)[i] == bottom[0]) { + continue; + } else { + CHECK_NE((*top)[i], bottom[0]) << "Only 0th top blob may be in place."; + } (*top)[i]->Reshape(bottom[0]->num(), bottom[0]->channels(), bottom[0]->height(), bottom[0]->width()); CHECK_EQ(count_, (*top)[i]->count()); @@ -26,6 +32,9 @@ void SplitLayer::Forward_cpu(const vector*>& bottom, vector*>* top) { const Dtype* bottom_data = bottom[0]->cpu_data(); for (int i = 0; i < top->size(); ++i) { + if (i == 0 && (*top)[i] == bottom[0]) { + continue; + } Dtype* top_data = (*top)[i]->mutable_cpu_data(); caffe_copy(count_, bottom_data, top_data); } @@ -36,6 +45,9 @@ void SplitLayer::Forward_gpu(const vector*>& bottom, vector*>* top) { const Dtype* bottom_data = bottom[0]->gpu_data(); for (int i = 0; i < top->size(); ++i) { + if (i == 0 && (*top)[i] == bottom[0]) { + continue; + } Dtype* top_data = (*top)[i]->mutable_gpu_data(); caffe_gpu_copy(count_, bottom_data, top_data); } @@ -47,7 +59,13 @@ Dtype SplitLayer::Backward_cpu(const vector*>& top, if (propagate_down) { const Dtype* top_diff = top[0]->cpu_diff(); Dtype* bottom_diff = (*bottom)[0]->mutable_cpu_diff(); - caffe_copy(count_, top_diff, bottom_diff); + // Initialize by copying first top blob diff to our diff, unless we're + // doing in-place computation for the first blob, in which case the diff is + // already initialized. + if (top[0] != (*bottom)[0]) { + caffe_copy(count_, top_diff, bottom_diff); + } + // Add remaining top blob diffs. for (int i = 1; i < top.size(); ++i) { top_diff = top[i]->cpu_diff(); caffe_axpy(count_, Dtype(1.), top_diff, bottom_diff); @@ -63,7 +81,13 @@ Dtype SplitLayer::Backward_gpu(const vector*>& top, if (propagate_down) { const Dtype* top_diff = top[0]->gpu_diff(); Dtype* bottom_diff = (*bottom)[0]->mutable_gpu_diff(); - caffe_gpu_copy(count_, top_diff, bottom_diff); + // Initialize by copying first top blob diff to our diff, unless we're + // doing in-place computation for the first blob, in which case the diff is + // already initialized. + if (top[0] != (*bottom)[0]) { + caffe_gpu_copy(count_, top_diff, bottom_diff); + } + // Add remaining top blob diffs. for (int i = 1; i < top.size(); ++i) { top_diff = top[i]->gpu_diff(); caffe_gpu_axpy(count_, Dtype(1.), top_diff, bottom_diff); diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index a3252b23378..f6d5d521fec 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -88,6 +88,32 @@ TYPED_TEST(SplitLayerTest, TestGPU) { } } +TYPED_TEST(SplitLayerTest, TestCPUInPlace) { + LayerParameter layer_param; + SplitLayer layer(layer_param); + Caffe::set_mode(Caffe::CPU); + this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int i = 0; i < this->blob_bottom_->count(); ++i) { + TypeParam bottom_value = this->blob_bottom_->cpu_data()[i]; + EXPECT_EQ(bottom_value, this->blob_top_b_->cpu_data()[i]); + } +} + +TYPED_TEST(SplitLayerTest, TestGPUInPlace) { + LayerParameter layer_param; + SplitLayer layer(layer_param); + Caffe::set_mode(Caffe::GPU); + this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; + layer.SetUp(this->blob_bottom_vec_, &(this->blob_top_vec_)); + layer.Forward(this->blob_bottom_vec_, &(this->blob_top_vec_)); + for (int i = 0; i < this->blob_bottom_->count(); ++i) { + TypeParam bottom_value = this->blob_bottom_->cpu_data()[i]; + EXPECT_EQ(bottom_value, this->blob_top_b_->cpu_data()[i]); + } +} + TYPED_TEST(SplitLayerTest, TestCPUGradient) { LayerParameter layer_param; Caffe::set_mode(Caffe::CPU); @@ -108,6 +134,28 @@ TYPED_TEST(SplitLayerTest, TestGPUGradient) { this->blob_top_vec_); } +TYPED_TEST(SplitLayerTest, TestCPUGradientInPlace) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::CPU); + SplitLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, + this->blob_top_vec_); +} + +TYPED_TEST(SplitLayerTest, TestGPUGradientInPlace) { + LayerParameter layer_param; + Caffe::set_mode(Caffe::GPU); + SplitLayer layer(layer_param); + GradientChecker checker(1e-2, 1e-2); + this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); + checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, + this->blob_top_vec_); +} + template class SplitLayerInsertionTest : public ::testing::Test { From 5d04d57a194f000a27e946656f98138f0808c1b2 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 16:38:47 -0800 Subject: [PATCH 11/19] change \" in test_split_layer to ' for readability --- src/caffe/test/test_split_layer.cpp | 260 ++++++++++++++-------------- 1 file changed, 129 insertions(+), 131 deletions(-) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index f6d5d521fec..1d7c3d56d27 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -171,8 +171,6 @@ class SplitLayerInsertionTest : public ::testing::Test { output_param_string, &expected_output_param)); NetParameter actual_output_param; insert_splits(input_param, &actual_output_param); - CHECK_EQ(expected_output_param.DebugString(), - actual_output_param.DebugString()); EXPECT_EQ(expected_output_param.DebugString(), actual_output_param.DebugString()); } @@ -183,275 +181,275 @@ TYPED_TEST_CASE(SplitLayerInsertionTest, InsertionDtypes); TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion1) { const string& input_proto = - "name: \"TestNetwork\" " + "name: 'TestNetwork' " "layers: { " " layer { " - " name: \"data\" " - " type: \"data\" " + " name: 'data' " + " type: 'data' " " } " - " top: \"data\" " - " top: \"label\" " + " top: 'data' " + " top: 'label' " "} " "layers: { " " layer { " - " name: \"innerprod\" " - " type: \"inner_product\" " + " name: 'innerprod' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod\" " + " bottom: 'data' " + " top: 'innerprod' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"softmax_with_loss\" " + " name: 'loss' " + " type: 'softmax_with_loss' " " } " - " bottom: \"innerprod\" " - " bottom: \"label\" " + " bottom: 'innerprod' " + " bottom: 'label' " "} "; this->RunInsertionTest(input_proto, input_proto); } TYPED_TEST(SplitLayerInsertionTest, TestNoInsertion2) { const string& input_proto = - "name: \"TestNetwork\" " + "name: 'TestNetwork' " "layers: { " " layer { " - " name: \"data\" " - " type: \"data\" " + " name: 'data' " + " type: 'data' " " } " - " top: \"data\" " - " top: \"label\" " + " top: 'data' " + " top: 'label' " "} " "layers: { " " layer { " - " name: \"data_split\" " - " type: \"split\" " + " name: 'data_split' " + " type: 'split' " " } " - " bottom: \"data\" " - " top: \"data_split_0\" " - " top: \"data_split_1\" " + " bottom: 'data' " + " top: 'data_split_0' " + " top: 'data_split_1' " "} " "layers: { " " layer { " - " name: \"innerprod1\" " - " type: \"inner_product\" " + " name: 'innerprod1' " + " type: 'inner_product' " " } " - " bottom: \"data_split_0\" " - " top: \"innerprod1\" " + " bottom: 'data_split_0' " + " top: 'innerprod1' " "} " "layers: { " " layer { " - " name: \"innerprod2\" " - " type: \"inner_product\" " + " name: 'innerprod2' " + " type: 'inner_product' " " } " - " bottom: \"data_split_1\" " - " top: \"innerprod2\" " + " bottom: 'data_split_1' " + " top: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod1\" " - " bottom: \"innerprod2\" " + " bottom: 'innerprod1' " + " bottom: 'innerprod2' " "} "; this->RunInsertionTest(input_proto, input_proto); } TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { const string& input_proto = - "name: \"TestNetwork\" " + "name: 'TestNetwork' " "layers: { " " layer { " - " name: \"data\" " - " type: \"data\" " + " name: 'data' " + " type: 'data' " " } " - " top: \"data\" " - " top: \"label\" " + " top: 'data' " + " top: 'label' " "} " "layers: { " " layer { " - " name: \"innerprod1\" " - " type: \"inner_product\" " + " name: 'innerprod1' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod1\" " + " bottom: 'data' " + " top: 'innerprod1' " "} " "layers: { " " layer { " - " name: \"innerprod2\" " - " type: \"inner_product\" " + " name: 'innerprod2' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod2\" " + " bottom: 'data' " + " top: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"innerprod3\" " - " type: \"inner_product\" " + " name: 'innerprod3' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod3\" " + " bottom: 'data' " + " top: 'innerprod3' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod1\" " - " bottom: \"innerprod2\" " + " bottom: 'innerprod1' " + " bottom: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod2\" " - " bottom: \"innerprod3\" " + " bottom: 'innerprod2' " + " bottom: 'innerprod3' " "} "; const string& expected_output_proto = - "name: \"TestNetwork\" " + "name: 'TestNetwork' " "layers: { " " layer { " - " name: \"data\" " - " type: \"data\" " + " name: 'data' " + " type: 'data' " " } " - " top: \"data\" " - " top: \"label\" " + " top: 'data' " + " top: 'label' " "} " "layers: { " " layer { " - " name: \"data_split\" " - " type: \"split\" " + " name: 'data_split' " + " type: 'split' " " } " - " bottom: \"data\" " - " top: \"data\" " - " top: \"data_split_1\" " - " top: \"data_split_2\" " + " bottom: 'data' " + " top: 'data' " + " top: 'data_split_1' " + " top: 'data_split_2' " "} " "layers: { " " layer { " - " name: \"innerprod1\" " - " type: \"inner_product\" " + " name: 'innerprod1' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod1\" " + " bottom: 'data' " + " top: 'innerprod1' " "} " "layers: { " " layer { " - " name: \"innerprod2\" " - " type: \"inner_product\" " + " name: 'innerprod2' " + " type: 'inner_product' " " } " - " bottom: \"data_split_1\" " - " top: \"innerprod2\" " + " bottom: 'data_split_1' " + " top: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"innerprod2_split\" " - " type: \"split\" " + " name: 'innerprod2_split' " + " type: 'split' " " } " - " bottom: \"innerprod2\" " - " top: \"innerprod2\" " - " top: \"innerprod2_split_1\" " + " bottom: 'innerprod2' " + " top: 'innerprod2' " + " top: 'innerprod2_split_1' " "} " "layers: { " " layer { " - " name: \"innerprod3\" " - " type: \"inner_product\" " + " name: 'innerprod3' " + " type: 'inner_product' " " } " - " bottom: \"data_split_2\" " - " top: \"innerprod3\" " + " bottom: 'data_split_2' " + " top: 'innerprod3' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod1\" " - " bottom: \"innerprod2\" " + " bottom: 'innerprod1' " + " bottom: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod2_split_1\" " - " bottom: \"innerprod3\" " + " bottom: 'innerprod2_split_1' " + " bottom: 'innerprod3' " "} "; this->RunInsertionTest(input_proto, expected_output_proto); } TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { const string& input_proto = - "name: \"TestNetwork\" " - "input: \"data\" " + "name: 'TestNetwork' " + "input: 'data' " "input_dim: 10 " "input_dim: 3 " "input_dim: 227 " "input_dim: 227 " "layers: { " " layer { " - " name: \"innerprod1\" " - " type: \"inner_product\" " + " name: 'innerprod1' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod1\" " + " bottom: 'data' " + " top: 'innerprod1' " "} " "layers: { " " layer { " - " name: \"innerprod2\" " - " type: \"inner_product\" " + " name: 'innerprod2' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod2\" " + " bottom: 'data' " + " top: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod1\" " - " bottom: \"innerprod2\" " + " bottom: 'innerprod1' " + " bottom: 'innerprod2' " "} "; const string& expected_output_proto = - "name: \"TestNetwork\" " - "input: \"data\" " + "name: 'TestNetwork' " + "input: 'data' " "input_dim: 10 " "input_dim: 3 " "input_dim: 227 " "input_dim: 227 " "layers: { " " layer { " - " name: \"data_split\" " - " type: \"split\" " + " name: 'data_split' " + " type: 'split' " " } " - " bottom: \"data\" " - " top: \"data\" " - " top: \"data_split_1\" " + " bottom: 'data' " + " top: 'data' " + " top: 'data_split_1' " "} " "layers: { " " layer { " - " name: \"innerprod1\" " - " type: \"inner_product\" " + " name: 'innerprod1' " + " type: 'inner_product' " " } " - " bottom: \"data\" " - " top: \"innerprod1\" " + " bottom: 'data' " + " top: 'innerprod1' " "} " "layers: { " " layer { " - " name: \"innerprod2\" " - " type: \"inner_product\" " + " name: 'innerprod2' " + " type: 'inner_product' " " } " - " bottom: \"data_split_1\" " - " top: \"innerprod2\" " + " bottom: 'data_split_1' " + " top: 'innerprod2' " "} " "layers: { " " layer { " - " name: \"loss\" " - " type: \"euclidean_loss\" " + " name: 'loss' " + " type: 'euclidean_loss' " " } " - " bottom: \"innerprod1\" " - " bottom: \"innerprod2\" " + " bottom: 'innerprod1' " + " bottom: 'innerprod2' " "} "; this->RunInsertionTest(input_proto, expected_output_proto); } From 11a41fe22b01267cd1285d5242b70b3a7875a8f1 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sat, 15 Feb 2014 16:48:10 -0800 Subject: [PATCH 12/19] remove pointlessly duplicated CheckGradientExhaustive calls (I screwed up when merging, I think) --- src/caffe/test/test_split_layer.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index 1d7c3d56d27..f76f55a53c6 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -119,7 +119,6 @@ TYPED_TEST(SplitLayerTest, TestCPUGradient) { Caffe::set_mode(Caffe::CPU); SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); - checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); } @@ -129,7 +128,6 @@ TYPED_TEST(SplitLayerTest, TestGPUGradient) { Caffe::set_mode(Caffe::GPU); SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); - checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); } @@ -140,7 +138,6 @@ TYPED_TEST(SplitLayerTest, TestCPUGradientInPlace) { SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; - checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); } @@ -151,7 +148,6 @@ TYPED_TEST(SplitLayerTest, TestGPUGradientInPlace) { SplitLayer layer(layer_param); GradientChecker checker(1e-2, 1e-2); this->blob_top_vec_[0] = this->blob_bottom_vec_[0]; - checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); checker.CheckGradientExhaustive(layer, this->blob_bottom_vec_, this->blob_top_vec_); } From 20f50790b4c47219d75fa5a2baec327e30828376 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 01:38:32 -0800 Subject: [PATCH 13/19] fix comment typo --- src/caffe/util/insert_splits.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index afbaf7f770f..d3e254e8287 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -36,7 +36,7 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { blob_name_to_bottom_split_idx[blob_name] = 0; } } - // Create split layer for any input blobs user by other layers as bottom + // Create split layer for any input blobs used by other layers as bottom // blobs more than once. for (int i = 0; i < param.input_size(); ++i) { const string& blob_name = param.input(i); From 1a4feb196e4d41032ad12ba738c6ea2c7096cc45 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 01:49:27 -0800 Subject: [PATCH 14/19] add test for layer with two tops that are inputs to multiple layers --- src/caffe/test/test_split_layer.cpp | 146 +++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/src/caffe/test/test_split_layer.cpp b/src/caffe/test/test_split_layer.cpp index f76f55a53c6..002b7875317 100644 --- a/src/caffe/test/test_split_layer.cpp +++ b/src/caffe/test/test_split_layer.cpp @@ -289,7 +289,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { "} " "layers: { " " layer { " - " name: 'loss' " + " name: 'loss1' " " type: 'euclidean_loss' " " } " " bottom: 'innerprod1' " @@ -297,7 +297,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { "} " "layers: { " " layer { " - " name: 'loss' " + " name: 'loss2' " " type: 'euclidean_loss' " " } " " bottom: 'innerprod2' " @@ -358,7 +358,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { "} " "layers: { " " layer { " - " name: 'loss' " + " name: 'loss1' " " type: 'euclidean_loss' " " } " " bottom: 'innerprod1' " @@ -366,7 +366,7 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { "} " "layers: { " " layer { " - " name: 'loss' " + " name: 'loss2' " " type: 'euclidean_loss' " " } " " bottom: 'innerprod2_split_1' " @@ -375,6 +375,144 @@ TYPED_TEST(SplitLayerInsertionTest, TestInsertion) { this->RunInsertionTest(input_proto, expected_output_proto); } +TYPED_TEST(SplitLayerInsertionTest, TestInsertionTwoTop) { + const string& input_proto = + "name: 'TestNetwork' " + "layers: { " + " layer { " + " name: 'data' " + " type: 'data' " + " } " + " top: 'data' " + " top: 'label' " + "} " + "layers: { " + " layer { " + " name: 'innerprod1' " + " type: 'inner_product' " + " } " + " bottom: 'data' " + " top: 'innerprod1' " + "} " + "layers: { " + " layer { " + " name: 'innerprod2' " + " type: 'inner_product' " + " } " + " bottom: 'label' " + " top: 'innerprod2' " + "} " + "layers: { " + " layer { " + " name: 'innerprod3' " + " type: 'inner_product' " + " } " + " bottom: 'data' " + " top: 'innerprod3' " + "} " + "layers: { " + " layer { " + " name: 'innerprod4' " + " type: 'inner_product' " + " } " + " bottom: 'label' " + " top: 'innerprod4' " + "} " + "layers: { " + " layer { " + " name: 'loss1' " + " type: 'euclidean_loss' " + " } " + " bottom: 'innerprod1' " + " bottom: 'innerprod3' " + "} " + "layers: { " + " layer { " + " name: 'loss2' " + " type: 'euclidean_loss' " + " } " + " bottom: 'innerprod2' " + " bottom: 'innerprod4' " + "} "; + const string& expected_output_proto = + "name: 'TestNetwork' " + "layers: { " + " layer { " + " name: 'data' " + " type: 'data' " + " } " + " top: 'data' " + " top: 'label' " + "} " + "layers: { " + " layer { " + " name: 'data_split' " + " type: 'split' " + " } " + " bottom: 'data' " + " top: 'data' " + " top: 'data_split_1' " + "} " + "layers: { " + " layer { " + " name: 'label_split' " + " type: 'split' " + " } " + " bottom: 'label' " + " top: 'label' " + " top: 'label_split_1' " + "} " + "layers: { " + " layer { " + " name: 'innerprod1' " + " type: 'inner_product' " + " } " + " bottom: 'data' " + " top: 'innerprod1' " + "} " + "layers: { " + " layer { " + " name: 'innerprod2' " + " type: 'inner_product' " + " } " + " bottom: 'label' " + " top: 'innerprod2' " + "} " + "layers: { " + " layer { " + " name: 'innerprod3' " + " type: 'inner_product' " + " } " + " bottom: 'data_split_1' " + " top: 'innerprod3' " + "} " + "layers: { " + " layer { " + " name: 'innerprod4' " + " type: 'inner_product' " + " } " + " bottom: 'label_split_1' " + " top: 'innerprod4' " + "} " + "layers: { " + " layer { " + " name: 'loss1' " + " type: 'euclidean_loss' " + " } " + " bottom: 'innerprod1' " + " bottom: 'innerprod3' " + "} " + "layers: { " + " layer { " + " name: 'loss2' " + " type: 'euclidean_loss' " + " } " + " bottom: 'innerprod2' " + " bottom: 'innerprod4' " + "} "; + this->RunInsertionTest(input_proto, expected_output_proto); +} + TYPED_TEST(SplitLayerInsertionTest, TestInputInsertion) { const string& input_proto = "name: 'TestNetwork' " From 939d29f310e6859105bac053cb435e7803116c44 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 02:02:32 -0800 Subject: [PATCH 15/19] remove redundant add_bottom (immediately cleared and then re-added) --- src/caffe/util/insert_splits.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index d3e254e8287..b80988442a9 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -67,7 +67,6 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { const int split_count = blob_name_to_bottom_count[blob_name]; if (split_count > 1) { LayerConnection* split_layer_connection = param_split->add_layers(); - split_layer_connection->add_bottom(blob_name); configure_split_layer(blob_name, split_count, split_layer_connection); } } From e9fbc240817cfde6557e10d487b15f01567fb2b1 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 03:18:39 -0800 Subject: [PATCH 16/19] get_split_blob_name returns a string to remove some verbosity --- include/caffe/util/insert_splits.hpp | 3 +-- src/caffe/util/insert_splits.cpp | 27 ++++++++++----------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/include/caffe/util/insert_splits.hpp b/include/caffe/util/insert_splits.hpp index 5bf49f1a637..2224c716044 100644 --- a/include/caffe/util/insert_splits.hpp +++ b/include/caffe/util/insert_splits.hpp @@ -16,8 +16,7 @@ void insert_splits(const NetParameter& param, NetParameter* param_split); void configure_split_layer(const string& blob_name, const int split_count, LayerConnection* split_layer_connection); -void get_split_blob_name(const string& blob_name, const int split_index, - string* split_blob_name); +string get_split_blob_name(const string& blob_name, const int split_index); } // namespace caffe diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index b80988442a9..eaf1f230a37 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -54,10 +54,8 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { const string& blob_name = layer_connection->bottom(j); const int split_count = blob_name_to_bottom_count[blob_name]; if (split_count > 1) { - string split_blob_name; - get_split_blob_name(blob_name, - blob_name_to_bottom_split_idx[blob_name]++, &split_blob_name); - layer_connection->set_bottom(j, split_blob_name); + layer_connection->set_bottom(j, get_split_blob_name(blob_name, + blob_name_to_bottom_split_idx[blob_name]++)); } } // Create split layer for any top blobs used by other layers as bottom @@ -82,24 +80,19 @@ void configure_split_layer(const string& blob_name, split_layer_param->set_name(blob_name + "_split"); split_layer_param->set_type("split"); for (int k = 0; k < split_count; ++k) { - string split_blob_name; - get_split_blob_name(blob_name, k, &split_blob_name); - split_layer_connection->add_top(split_blob_name); + split_layer_connection->add_top(get_split_blob_name(blob_name, k)); } } -void get_split_blob_name(const string& blob_name, const int split_index, - string* split_blob_name) { +string get_split_blob_name(const string& blob_name, const int split_index) { if (split_index == 0) { - *split_blob_name = blob_name; - } else { - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - const int suffix_length = snprintf(split_suffix, suffix_max_length, - "_split_%d", split_index); - CHECK_LT(suffix_length, suffix_max_length); - *split_blob_name = blob_name + split_suffix; + return blob_name; } + const int suffix_max_length = 16; + char split_suffix[suffix_max_length]; + CHECK_LT(snprintf(split_suffix, suffix_max_length, "_split_%d", split_index), + suffix_max_length); + return blob_name + split_suffix; } } // namespace caffe From bd1771bc665b26ca16540a3b69d10c79af80d8eb Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 10:02:42 -0800 Subject: [PATCH 17/19] get rid of messy snprintf string concatenation --- src/caffe/util/insert_splits.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index eaf1f230a37..9ced99b1bc8 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -3,11 +3,12 @@ #include #include #include +#include -#include "caffe/common.hpp" #include "caffe/util/insert_splits.hpp" using std::map; +using std::ostringstream; namespace caffe { @@ -88,11 +89,9 @@ string get_split_blob_name(const string& blob_name, const int split_index) { if (split_index == 0) { return blob_name; } - const int suffix_max_length = 16; - char split_suffix[suffix_max_length]; - CHECK_LT(snprintf(split_suffix, suffix_max_length, "_split_%d", split_index), - suffix_max_length); - return blob_name + split_suffix; + ostringstream split_blob_name; + split_blob_name << blob_name << "_split_" << split_index; + return split_blob_name.str(); } } // namespace caffe From 562185900ae1ebd2c620dd2ea15f74bbb708e6a3 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 11:11:03 -0800 Subject: [PATCH 18/19] eliminate some cruft by relying on std::map default initializations --- src/caffe/util/insert_splits.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index 9ced99b1bc8..09492d478b7 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -18,23 +18,12 @@ void insert_splits(const NetParameter& param, NetParameter* param_split) { param_split->clear_layers(); map blob_name_to_bottom_count; map blob_name_to_bottom_split_idx; - // Determine for each top blob (including input blobs) the number of times - // it's used as a bottom blob. - for (int i = 0; i < param.input_size(); ++i) { - const string& blob_name = param.input(i); - blob_name_to_bottom_count[blob_name] = 0; - blob_name_to_bottom_split_idx[blob_name] = 0; - } + // Determine the number of times each blob is used as an input (bottom) blob. for (int i = 0; i < param.layers_size(); ++i) { const LayerConnection& layer_connection = param.layers(i); for (int j = 0; j < layer_connection.bottom_size(); ++j) { const string& blob_name = layer_connection.bottom(j); - blob_name_to_bottom_count[blob_name]++; - } - for (int j = 0; j < layer_connection.top_size(); ++j) { - const string& blob_name = layer_connection.top(j); - blob_name_to_bottom_count[blob_name] = 0; - blob_name_to_bottom_split_idx[blob_name] = 0; + ++blob_name_to_bottom_count[blob_name]; } } // Create split layer for any input blobs used by other layers as bottom @@ -76,8 +65,7 @@ void configure_split_layer(const string& blob_name, const int split_count, LayerConnection* split_layer_connection) { split_layer_connection->Clear(); split_layer_connection->add_bottom(blob_name); - LayerParameter* split_layer_param = - split_layer_connection->mutable_layer(); + LayerParameter* split_layer_param = split_layer_connection->mutable_layer(); split_layer_param->set_name(blob_name + "_split"); split_layer_param->set_type("split"); for (int k = 0; k < split_count; ++k) { @@ -86,6 +74,8 @@ void configure_split_layer(const string& blob_name, } string get_split_blob_name(const string& blob_name, const int split_index) { + // 0th split top blob is given the same name as the bottom blob so that + // computation is done 'in-place', saving a bit of time and memory. if (split_index == 0) { return blob_name; } From 4f97cfa0de9e8373e7a271695c9d0c5a38a1ccd6 Mon Sep 17 00:00:00 2001 From: Jeff Donahue Date: Sun, 16 Feb 2014 12:31:40 -0800 Subject: [PATCH 19/19] remove unnecessary include --- src/caffe/util/insert_splits.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/caffe/util/insert_splits.cpp b/src/caffe/util/insert_splits.cpp index 09492d478b7..48c7fb18281 100644 --- a/src/caffe/util/insert_splits.cpp +++ b/src/caffe/util/insert_splits.cpp @@ -1,6 +1,5 @@ // Copyright 2014 Jeff Donahue -#include #include #include #include