From 2c9abcb8a938eef2eeb1fefaf9ec63eb5182a09e Mon Sep 17 00:00:00 2001 From: cypof Date: Fri, 17 Oct 2014 15:58:34 -0700 Subject: [PATCH] Modular model definitions --- .gitignore | 4 ++ Makefile | 6 +- examples/mnist/lenet_conv_pool.prototxt | 30 +++++++++ examples/mnist/lenet_train_test.prototxt | 69 ++++--------------- include/caffe/net.hpp | 7 ++ include/caffe/util/io.hpp | 2 + src/caffe/net.cpp | 67 ++++++++++++++++++- src/caffe/proto/caffe.proto | 20 +++++- src/caffe/test/test_data/module.prototxt | 21 ++++++ src/caffe/test/test_imports.cpp | 85 ++++++++++++++++++++++++ src/caffe/util/io.cpp | 12 ++++ 11 files changed, 261 insertions(+), 62 deletions(-) create mode 100644 examples/mnist/lenet_conv_pool.prototxt create mode 100644 src/caffe/test/test_data/module.prototxt create mode 100644 src/caffe/test/test_imports.cpp diff --git a/.gitignore b/.gitignore index fa279f9071b..f91f275e9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,10 @@ docs/dev # Eclipse Project settings *.*project +.settings # CMake generated files *.gen.cmake + +# OSX files +.DS_Store diff --git a/Makefile b/Makefile index 26d5964cacc..c708eecb563 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ CONFIG_FILE := Makefile.config include $(CONFIG_FILE) BUILD_DIR_LINK := $(BUILD_DIR) -RELEASE_BUILD_DIR := .$(BUILD_DIR)_release -DEBUG_BUILD_DIR := .$(BUILD_DIR)_debug +RELEASE_BUILD_DIR ?= .$(BUILD_DIR)_release +DEBUG_BUILD_DIR ?= .$(BUILD_DIR)_debug DEBUG ?= 0 ifeq ($(DEBUG), 1) @@ -269,7 +269,7 @@ endif # Debugging ifeq ($(DEBUG), 1) - COMMON_FLAGS += -DDEBUG -g -O0 + COMMON_FLAGS += -DDEBUG -g -O0 -DBOOST_NOINLINE='__attribute__ ((noinline))' NVCCFLAGS += -G else COMMON_FLAGS += -DNDEBUG -O2 diff --git a/examples/mnist/lenet_conv_pool.prototxt b/examples/mnist/lenet_conv_pool.prototxt new file mode 100644 index 00000000000..5e2b7886e22 --- /dev/null +++ b/examples/mnist/lenet_conv_pool.prototxt @@ -0,0 +1,30 @@ +layers { + name: "conv" + type: CONVOLUTION + bottom: "${bottom}" + top: "conv" + blobs_lr: 1 + blobs_lr: 2 + convolution_param { + num_output: ${num_output} + kernel_size: 5 + stride: 1 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + } + } +} +layers { + name: "pool" + type: POOLING + bottom: "conv" + top: "pool" + pooling_param { + pool: MAX + kernel_size: 2 + stride: 2 + } +} diff --git a/examples/mnist/lenet_train_test.prototxt b/examples/mnist/lenet_train_test.prototxt index 2bd960b56aa..646fbe6509e 100644 --- a/examples/mnist/lenet_train_test.prototxt +++ b/examples/mnist/lenet_train_test.prototxt @@ -29,71 +29,28 @@ layers { } include: { phase: TEST } } - layers { - name: "conv1" - type: CONVOLUTION - bottom: "data" - top: "conv1" - blobs_lr: 1 - blobs_lr: 2 - convolution_param { - num_output: 20 - kernel_size: 5 - stride: 1 - weight_filler { - type: "xavier" - } - bias_filler { - type: "constant" - } - } -} -layers { - name: "pool1" - type: POOLING - bottom: "conv1" - top: "pool1" - pooling_param { - pool: MAX - kernel_size: 2 - stride: 2 - } -} -layers { - name: "conv2" - type: CONVOLUTION - bottom: "pool1" - top: "conv2" - blobs_lr: 1 - blobs_lr: 2 - convolution_param { - num_output: 50 - kernel_size: 5 - stride: 1 - weight_filler { - type: "xavier" - } - bias_filler { - type: "constant" - } + name: "cp1" + type: IMPORT + import_param { + net: "examples/mnist/lenet_conv_pool.prototxt" + var { name: "bottom" value: "/data" } + var { name: "num_output" value: "20" } } } layers { - name: "pool2" - type: POOLING - bottom: "conv2" - top: "pool2" - pooling_param { - pool: MAX - kernel_size: 2 - stride: 2 + name: "cp2" + type: IMPORT + import_param { + net: "examples/mnist/lenet_conv_pool.prototxt" + var { name: "bottom" value: "../cp1/pool" } + var { name: "num_output" value: "50" } } } layers { name: "ip1" type: INNER_PRODUCT - bottom: "pool2" + bottom: "cp2/pool" top: "ip1" blobs_lr: 1 blobs_lr: 2 diff --git a/include/caffe/net.hpp b/include/caffe/net.hpp index 879f474674d..a28afe6ee37 100644 --- a/include/caffe/net.hpp +++ b/include/caffe/net.hpp @@ -192,6 +192,13 @@ class Net { /// @brief Get misc parameters, e.g. the LR multiplier and weight decay. void GetLearningRateAndWeightDecay(); + // @brief Loads imports, for modular network definitions + static void LoadImports(const NetParameter& source, NetParameter* target); + static void LoadImports(const NetParameter& source, NetParameter* target, + const string& pwd); + // @brief Resolves a layer or blob name, e.g. "../data" + static string ResolveImportName(const string& path, const string& pwd); + /// @brief Individual layers in the net vector > > layers_; vector layer_names_; diff --git a/include/caffe/util/io.hpp b/include/caffe/util/io.hpp index 64df0155780..62b9c144224 100644 --- a/include/caffe/util/io.hpp +++ b/include/caffe/util/io.hpp @@ -88,6 +88,8 @@ inline void WriteProtoToBinaryFile( WriteProtoToBinaryFile(proto, filename.c_str()); } +string ReadFile(const string& filename); + bool ReadFileToDatum(const string& filename, const int label, Datum* datum); inline bool ReadFileToDatum(const string& filename, Datum* datum) { diff --git a/src/caffe/net.cpp b/src/caffe/net.cpp index 21ab15fd31b..07e9a43ddcf 100644 --- a/src/caffe/net.cpp +++ b/src/caffe/net.cpp @@ -1,3 +1,6 @@ +#include +#include + #include #include #include @@ -15,6 +18,7 @@ #include "caffe/util/upgrade_proto.hpp" #include "caffe/test/test_caffe_main.hpp" +using boost::replace_all; namespace caffe { @@ -32,10 +36,13 @@ Net::Net(const string& param_file) { template void Net::Init(const NetParameter& in_param) { + // Load import layers + NetParameter expanded(in_param); + LoadImports(in_param, &expanded); // Filter layers based on their include/exclude rules and // the current NetState. NetParameter filtered_param; - FilterNet(in_param, &filtered_param); + FilterNet(expanded, &filtered_param); LOG(INFO) << "Initializing net from parameters: " << std::endl << filtered_param.DebugString(); // Create a copy of filtered_param with splits added where necessary. @@ -462,6 +469,64 @@ void Net::AppendParam(const NetParameter& param, const int layer_id, } } +template +void Net::LoadImports(const NetParameter& source, NetParameter* target) { + target->CopyFrom(source); + target->clear_layers(); + LoadImports(source, target, ""); +} + +template +void Net::LoadImports(const NetParameter& source, NetParameter* target, + const string& pwd) { + for (int i = 0; i < source.layers_size(); ++i) { + if (source.layers(i).type() == LayerParameter_LayerType_IMPORT) { + const LayerParameter& layer = source.layers(i); + CHECK(layer.has_import_param()) << "Missing import_param"; + const ImportParameter& import = layer.import_param(); + string proto = ReadFile(import.net()); + // Replace variables and references + for (int j = 0; j < import.var_size(); ++j) { + const Pair& p = import.var(j); + replace_all(proto, "${" + p.name() + "}", p.value()); + } + NetParameter net; + bool parse = google::protobuf::TextFormat::ParseFromString(proto, &net); + CHECK(parse) << "Failed to parse NetParameter file: " << import.net(); + CHECK(layer.has_name() && layer.name().length() > 0) + << "Import layer must have a name"; + LoadImports(net, target, ResolveImportName(layer.name(), pwd)); + } else { + LayerParameter *t = target->add_layers(); + t->CopyFrom(source.layers(i)); + t->set_name(ResolveImportName(t->name(), pwd)); + for (int j = 0; j < source.layers(i).top_size(); ++j) + t->set_top(j, ResolveImportName(source.layers(i).top(j), pwd)); + for (int j = 0; j < source.layers(i).bottom_size(); ++j) + t->set_bottom(j, ResolveImportName(source.layers(i).bottom(j), pwd)); + } + } +} + +template +string Net::ResolveImportName(const string& path, const string& pwd) { + CHECK(!boost::starts_with(pwd, "/") && !boost::ends_with(pwd, "/")); + if (boost::starts_with(path, "/")) + return path.substr(1, path.size() - 1); + string cpath = path; + string cpwd = pwd; + while (boost::starts_with(cpath, "../")) { + cpath = cpath.substr(3, cpath.size() - 3); + size_t i = cpwd.find_last_of('/'); + cpwd = i == string::npos ? "" : cpwd.substr(0, i); + } + if (!cpwd.size()) + return cpath; + if (!cpath.size() || cpath == ".") + return cpwd; + return cpwd + '/' + cpath; +} + template void Net::GetLearningRateAndWeightDecay() { LOG(INFO) << "Collecting Learning Rate and Weight Decay."; diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 8086ad66579..f4c6150f2c5 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -206,7 +206,7 @@ message NetStateRule { // NOTE // Update the next available ID when you add a new LayerParameter field. // -// LayerParameter next available ID: 42 (last added: exp_param) +// LayerParameter next available ID: 43 (last added: import_param) message LayerParameter { repeated string bottom = 2; // the name of the bottom blobs repeated string top = 3; // the name of the top blobs @@ -227,7 +227,7 @@ message LayerParameter { // line above the enum. Update the next available ID when you add a new // LayerType. // - // LayerType next available ID: 39 (last added: EXP) + // LayerType next available ID: 40 (last added: IMPORT) 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 @@ -252,6 +252,7 @@ message LayerParameter { HINGE_LOSS = 28; IM2COL = 11; IMAGE_DATA = 12; + IMPORT = 39; INFOGAIN_LOSS = 13; INNER_PRODUCT = 14; LRN = 15; @@ -313,6 +314,7 @@ message LayerParameter { optional HDF5OutputParameter hdf5_output_param = 14; optional HingeLossParameter hinge_loss_param = 29; optional ImageDataParameter image_data_param = 15; + optional ImportParameter import_param = 42; optional InfogainLossParameter infogain_loss_param = 16; optional InnerProductParameter inner_product_param = 17; optional LRNParameter lrn_param = 18; @@ -342,6 +344,20 @@ message LayerParameter { optional V0LayerParameter layer = 1; } +message Pair { + required string name = 1; + required string value = 2; +} + +// Message that stores parameters used by ImportLayer +message ImportParameter { + // Proto file to import + required string net = 1; + // Variable names to replace before importing the file. Variables can + // be used in the file in this format: ${name} + repeated Pair var = 2; +} + // Message that stores parameters used to apply transformation // to the data layer's data message TransformationParameter { diff --git a/src/caffe/test/test_data/module.prototxt b/src/caffe/test/test_data/module.prototxt new file mode 100644 index 00000000000..4f911613f6e --- /dev/null +++ b/src/caffe/test/test_data/module.prototxt @@ -0,0 +1,21 @@ +layers: { + name: 'innerproduct' + type: INNER_PRODUCT + inner_product_param { + num_output: ${num_output} + weight_filler { + type: 'gaussian' + std: 0.01 + } + bias_filler { + type: 'constant' + value: 0 + } + } + blobs_lr: 1. + blobs_lr: 2. + weight_decay: 1. + weight_decay: 0. + bottom: '../data' + top: 'innerproduct' +} \ No newline at end of file diff --git a/src/caffe/test/test_imports.cpp b/src/caffe/test/test_imports.cpp new file mode 100644 index 00000000000..3eb4ba42426 --- /dev/null +++ b/src/caffe/test/test_imports.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +#include "google/protobuf/text_format.h" + +#include "gtest/gtest.h" + +#include "caffe/blob.hpp" +#include "caffe/common.hpp" +#include "caffe/filler.hpp" +#include "caffe/net.hpp" +#include "caffe/proto/caffe.pb.h" +#include "caffe/util/io.hpp" +#include "caffe/vision_layers.hpp" + +#include "caffe/test/test_caffe_main.hpp" + +namespace caffe { + +template +class ImportsTest : public MultiDeviceTest { + typedef typename TypeParam::Dtype Dtype; + + protected: + virtual void InitNetFromProtoString(const string& proto) { + NetParameter param; + CHECK(google::protobuf::TextFormat::ParseFromString(proto, ¶m)); + net_.reset(new Net(param)); + } + + virtual void InitNet() { + string proto = + "name: 'TestNetwork' " + "layers: { " + " name: 'data' " + " type: DUMMY_DATA " + " dummy_data_param { " + " num: 5 " + " channels: 2 " + " height: 3 " + " width: 4 " + " num: 5 " + " channels: 1 " + " height: 1 " + " width: 1 " + " data_filler { " + " type: 'gaussian' " + " std: 0.01 " + " } " + " } " + " top: 'data' " + " top: 'label' " + "} " + "layers: { " + " name: 'import' " + " type: IMPORT " + " import_param { " + " net: 'src/caffe/test/test_data/module.prototxt' " + " var { name: 'num_output' value: '1000' } " + " } " + "} " + "layers: { " + " name: 'loss' " + " type: SOFTMAX_LOSS " + " bottom: 'import/innerproduct' " + " bottom: 'label' " + " top: 'top_loss' " + "} "; + InitNetFromProtoString(proto); + } + + shared_ptr > net_; +}; + +TYPED_TEST_CASE(ImportsTest, TestDtypesAndDevices); + +TYPED_TEST(ImportsTest, ConvPool) { + this->InitNet(); + EXPECT_TRUE(this->net_->has_blob("data")); + EXPECT_TRUE(this->net_->has_blob("label")); + EXPECT_TRUE(this->net_->has_blob("import/innerproduct")); + EXPECT_FALSE(this->net_->has_blob("loss")); +} +} // namespace caffe diff --git a/src/caffe/util/io.cpp b/src/caffe/util/io.cpp index b136bc8a120..da4aa779b71 100644 --- a/src/caffe/util/io.cpp +++ b/src/caffe/util/io.cpp @@ -65,6 +65,18 @@ void WriteProtoToBinaryFile(const Message& proto, const char* filename) { CHECK(proto.SerializeToOstream(&output)); } +string ReadFile(const string& filename) { + std::ifstream in(filename.c_str(), std::ios::in | std::ios::binary); + CHECK(in) << "Failed to read file: " << filename; + std::string contents; + in.seekg(0, std::ios::end); + contents.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&contents[0], contents.size()); + in.close(); + return contents; +} + cv::Mat ReadImageToCVMat(const string& filename, const int height, const int width, const bool is_color) { cv::Mat cv_img;