From ede8d58c7c7bb5b7167fde93bcb0443cf97d4bc0 Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Sun, 31 Aug 2014 22:39:47 -0700 Subject: [PATCH 1/5] [pycaffe] add raw pointer constructor to PyBlob This is necessary to allow Python to access Blobs from the layer interface, which takes raw pointers. (It does, however, mean that Python layers must not hold onto Blobs beyond their layer calls.) --- python/caffe/_caffe.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/caffe/_caffe.hpp b/python/caffe/_caffe.hpp index ba04d276351..49654d163db 100644 --- a/python/caffe/_caffe.hpp +++ b/python/caffe/_caffe.hpp @@ -23,8 +23,15 @@ namespace caffe { template class PyBlob { public: + // Construct from shared_ptr: memory will be correctly managed, + // even if Python holds onto a Blob beyond the life of its Net. explicit PyBlob(const shared_ptr > &blob) : blob_(blob) {} + // Construct from raw pointer: memory will become invalid once the + // owning Net is deleted. This exists only so that the raw Blob*s + // used in the layer interface can be passed to embedded Python. + explicit PyBlob(Blob* blob) + : blob_(blob, null_deleter()) {} int num() const { return blob_->num(); } int channels() const { return blob_->channels(); } @@ -42,6 +49,13 @@ class PyBlob { protected: shared_ptr > blob_; + + private: + // A dummy class that lets us use raw pointers as shared_ptrs to get + // around the fact that layers take around raw pointers. + struct null_deleter { + void operator()(void const*) const { } + }; }; // We need another wrapper (used as boost::python's HeldType) that receives a From d075e6f9841182da1d901e40102bbfe879351612 Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Sun, 31 Aug 2014 01:56:45 -0700 Subject: [PATCH 2/5] [pycaffe] add wrapper for vector This is needed for passing propagate_down to Python layers. --- python/caffe/_caffe.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/caffe/_caffe.cpp b/python/caffe/_caffe.cpp index 5a81a42329b..44373657a1f 100644 --- a/python/caffe/_caffe.cpp +++ b/python/caffe/_caffe.cpp @@ -198,6 +198,9 @@ BOOST_PYTHON_MODULE(_caffe) { bp::class_ >("StringVec") .def(bp::vector_indexing_suite >()); + bp::class_ >("BoolVec") + .def(bp::vector_indexing_suite >()); + import_array(); } From 9a09a28ffdd2b33d444fcf8a9033fe07eac3ca4a Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Sun, 31 Aug 2014 22:56:35 -0700 Subject: [PATCH 3/5] [pycaffe] small style fixes --- python/caffe/_caffe.cpp | 6 +++--- python/caffe/_caffe.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/caffe/_caffe.cpp b/python/caffe/_caffe.cpp index 44373657a1f..52e966a58a5 100644 --- a/python/caffe/_caffe.cpp +++ b/python/caffe/_caffe.cpp @@ -37,7 +37,7 @@ static void CheckFile(const string& filename) { bp::object PyBlobWrap::get_data() { npy_intp dims[] = {num(), channels(), height(), width()}; - PyObject *obj = PyArray_SimpleNewFromData(4, dims, NPY_FLOAT32, + PyObject* obj = PyArray_SimpleNewFromData(4, dims, NPY_FLOAT32, blob_->mutable_cpu_data()); PyArray_SetBaseObject(reinterpret_cast(obj), self_); Py_INCREF(self_); @@ -49,9 +49,9 @@ bp::object PyBlobWrap::get_data() { bp::object PyBlobWrap::get_diff() { npy_intp dims[] = {num(), channels(), height(), width()}; - PyObject *obj = PyArray_SimpleNewFromData(4, dims, NPY_FLOAT32, + PyObject* obj = PyArray_SimpleNewFromData(4, dims, NPY_FLOAT32, blob_->mutable_cpu_diff()); - PyArray_SetBaseObject(reinterpret_cast(obj), self_); + PyArray_SetBaseObject(reinterpret_cast(obj), self_); Py_INCREF(self_); bp::handle<> h(obj); diff --git a/python/caffe/_caffe.hpp b/python/caffe/_caffe.hpp index 49654d163db..c6ddd194c25 100644 --- a/python/caffe/_caffe.hpp +++ b/python/caffe/_caffe.hpp @@ -18,6 +18,7 @@ using boost::shared_ptr; namespace caffe { + // wrap shared_ptr in a class that we construct in C++ and pass // to Python template @@ -25,13 +26,13 @@ class PyBlob { public: // Construct from shared_ptr: memory will be correctly managed, // even if Python holds onto a Blob beyond the life of its Net. - explicit PyBlob(const shared_ptr > &blob) - : blob_(blob) {} + explicit PyBlob(const shared_ptr >& blob) + : blob_(blob) { } // Construct from raw pointer: memory will become invalid once the // owning Net is deleted. This exists only so that the raw Blob*s // used in the layer interface can be passed to embedded Python. explicit PyBlob(Blob* blob) - : blob_(blob, null_deleter()) {} + : blob_(blob, null_deleter()) { } int num() const { return blob_->num(); } int channels() const { return blob_->channels(); } @@ -105,7 +106,6 @@ class PyNet { void Init(string param_file); - // Generate Python exceptions for badly shaped or discontiguous arrays. inline void check_contiguous_array(PyArrayObject* arr, string name, int channels, int height, int width); From ebf148b4165707a4888a1d2f6080d05297656a5d Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Sun, 31 Aug 2014 15:52:16 -0700 Subject: [PATCH 4/5] add USE_PYTHON_LAYER build option to include Python libraries in caffe This option links the standard caffe binaries, not just pycaffe, against the Python libraries, making it possible to embed Python in caffe. --- Makefile | 10 ++++++++++ Makefile.config.example | 3 +++ 2 files changed, 13 insertions(+) diff --git a/Makefile b/Makefile index 5020b4109d6..6c25f470374 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,9 @@ OBJ_BUILD_DIR := $(BUILD_DIR)/src/$(PROJECT) LAYER_BUILD_DIR := $(OBJ_BUILD_DIR)/layers UTIL_BUILD_DIR := $(OBJ_BUILD_DIR)/util OBJS := $(PROTO_OBJS) $(CXX_OBJS) $(CU_OBJS) +ifeq ($(USE_PYTHON_LAYER), 1) + OBJS += python/$(PROJECT)/_$(PROJECT).o +endif # tool, example, and test objects TOOL_OBJS := $(addprefix $(BUILD_DIR)/, ${TOOL_SRCS:.cpp=.o}) TOOL_BUILD_DIR := $(BUILD_DIR)/tools @@ -172,6 +175,9 @@ LIBRARIES += pthread \ hdf5_hl hdf5 \ opencv_core opencv_highgui opencv_imgproc PYTHON_LIBRARIES := boost_python python2.7 +ifeq ($(USE_PYTHON_LAYER), 1) + LIBRARIES += $(PYTHON_LIBRARIES) +endif WARNINGS := -Wall -Wno-sign-compare ############################## @@ -309,6 +315,10 @@ endif INCLUDE_DIRS += $(BLAS_INCLUDE) LIBRARY_DIRS += $(BLAS_LIB) +ifeq ($(USE_PYTHON_LAYER), 1) + COMMON_FLAGS += -DUSE_PYTHON_LAYER +endif + # Complete build flags. COMMON_FLAGS += $(foreach includedir,$(INCLUDE_DIRS),-I$(includedir)) CXXFLAGS += -pthread -fPIC $(COMMON_FLAGS) $(WARNINGS) diff --git a/Makefile.config.example b/Makefile.config.example index 5cb0b243aca..f5e957a2b20 100644 --- a/Makefile.config.example +++ b/Makefile.config.example @@ -7,6 +7,9 @@ # CPU-only switch (uncomment to build without GPU support). # CPU_ONLY := 1 +# Uncomment to include the Python layer (will link caffe against Python libs). +# USE_PYTHON_LAYER := 1 + # To customize your choice of compiler, uncomment and set the following. # N.B. the default for Linux is g++ and the default for OSX is clang++ # CUSTOM_CXX := g++ From a48990d5951e4c5d286b7d0dfdf04b8afa9cfb62 Mon Sep 17 00:00:00 2001 From: Jonathan L Long Date: Sun, 31 Aug 2014 22:52:35 -0700 Subject: [PATCH 5/5] add PYTHON_LAYER for layers implemented by embedding Python --- include/caffe/python_layer.hpp | 51 +++++++++++++++++++++ src/caffe/layer_factory.cpp | 10 +++++ src/caffe/layers/python_layer.cpp | 74 +++++++++++++++++++++++++++++++ src/caffe/proto/caffe.proto | 12 ++++- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 include/caffe/python_layer.hpp create mode 100644 src/caffe/layers/python_layer.cpp diff --git a/include/caffe/python_layer.hpp b/include/caffe/python_layer.hpp new file mode 100644 index 00000000000..109f78b7c18 --- /dev/null +++ b/include/caffe/python_layer.hpp @@ -0,0 +1,51 @@ +#ifndef CAFFE_PYTHON_LAYER_HPP_ +#define CAFFE_PYTHON_LAYER_HPP_ + +#include +#include + +#include "../python/caffe/_caffe.hpp" +#include "caffe/layer.hpp" + +namespace caffe { + +/** + * @brief Wrap a layer implemented in Python. + */ +template +class PythonLayer : public Layer { + public: + /** + * @param param provides python_param, with required parameters: + * - module. The module to import with the layer implementation. Note that + * the current directory is not in the module search path by default. + * - layer. The name of the layer class, which must implement setup + * (for LayerSetUp), reshape (for Reshape), forward (for Forward_cpu), and + * backward (for Backward_cpu). + */ + explicit PythonLayer(const LayerParameter& param) + : Layer(param) {} + virtual void LayerSetUp(const vector*>& bottom, + const vector*>& top); + virtual void Reshape(const vector*>& bottom, + const vector*>& top); + + virtual inline LayerParameter_LayerType type() const { + return LayerParameter_LayerType_PYTHON; + } + + protected: + virtual void Forward_cpu(const vector*>& bottom, + const vector*>& top); + virtual void Backward_cpu(const vector*>& top, + const vector& propagate_down, const vector*>& bottom); + + boost::python::object layer_; + + private: + vector > PythonBlobVector(const vector*>& vec); +}; + +} // namespace caffe + +#endif diff --git a/src/caffe/layer_factory.cpp b/src/caffe/layer_factory.cpp index b78167f21eb..0fb24cbd25a 100644 --- a/src/caffe/layer_factory.cpp +++ b/src/caffe/layer_factory.cpp @@ -3,6 +3,9 @@ #include "caffe/layer.hpp" #include "caffe/proto/caffe.pb.h" #include "caffe/vision_layers.hpp" +#ifdef USE_PYTHON_LAYER +#include "caffe/python_layer.hpp" +#endif namespace caffe { @@ -231,6 +234,13 @@ Layer* GetLayer(const LayerParameter& param) { return GetPoolingLayer(name, param); case LayerParameter_LayerType_POWER: return new PowerLayer(param); + case LayerParameter_LayerType_PYTHON: +#ifdef USE_PYTHON_LAYER + return new PythonLayer(param); +#else + LOG(FATAL) << "Attempt to use PythonLayer, but built without " + "USE_PYTHON_LAYER option."; +#endif case LayerParameter_LayerType_RELU: return GetReLULayer(name, param); case LayerParameter_LayerType_SILENCE: diff --git a/src/caffe/layers/python_layer.cpp b/src/caffe/layers/python_layer.cpp new file mode 100644 index 00000000000..e553a72a076 --- /dev/null +++ b/src/caffe/layers/python_layer.cpp @@ -0,0 +1,74 @@ +#ifdef USE_PYTHON_LAYER +#include +#include +#include + +#include "caffe/layer.hpp" +#include "caffe/python_layer.hpp" + +namespace bp = boost::python; + +namespace caffe { + +template +vector > PythonLayer::PythonBlobVector( + const vector*>& vec) { + return vector >(vec.begin(), vec.end()); +} + +template +void PythonLayer::LayerSetUp(const vector*>& bottom, + const vector*>& top) { + Py_Initialize(); + init_caffe(); + + try { + bp::object module_ = bp::import( + this->layer_param_.python_param().module().c_str()); + layer_ = module_.attr(this->layer_param_.python_param().layer().c_str())(); + + layer_.attr("setup")(PythonBlobVector(bottom), PythonBlobVector(top)); + } catch (bp::error_already_set) { + PyErr_Print(); + throw; + } +} + +template +void PythonLayer::Reshape(const vector*>& bottom, + const vector*>& top) { + try { + layer_.attr("reshape")(PythonBlobVector(bottom), PythonBlobVector(top)); + } catch (bp::error_already_set) { + PyErr_Print(); + throw; + } +} + +template +void PythonLayer::Forward_cpu(const vector*>& bottom, + const vector*>& top) { + try { + layer_.attr("forward")(PythonBlobVector(bottom), PythonBlobVector(top)); + } catch (bp::error_already_set) { + PyErr_Print(); + throw; + } +} + +template +void PythonLayer::Backward_cpu(const vector*>& top, + const vector& propagate_down, const vector*>& bottom) { + try { + layer_.attr("backward")(PythonBlobVector(top), propagate_down, + PythonBlobVector(bottom)); + } catch (bp::error_already_set) { + PyErr_Print(); + throw; + } +} + +INSTANTIATE_CLASS(PythonLayer); + +} // namespace caffe +#endif diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 01a516ee3b9..a8512c1a883 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -198,7 +198,7 @@ message NetStateRule { // NOTE // Update the next available ID when you add a new LayerParameter field. // -// LayerParameter next available ID: 41 (last added: contrastive_loss_param) +// LayerParameter next available ID: 42 (last added: python_param) message LayerParameter { repeated string bottom = 2; // the name of the bottom blobs repeated string top = 3; // the name of the top blobs @@ -219,7 +219,7 @@ message LayerParameter { // line above the enum. Update the next available ID when you add a new // LayerType. // - // LayerType next available ID: 38 (last added: CONTRASTIVE_LOSS) + // LayerType next available ID: 39 (last added: PYTHON) 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 @@ -251,6 +251,7 @@ message LayerParameter { MVN = 34; POOLING = 17; POWER = 26; + PYTHON = 38; RELU = 18; SIGMOID = 19; SIGMOID_CROSS_ENTROPY_LOSS = 27; @@ -310,6 +311,7 @@ message LayerParameter { optional MVNParameter mvn_param = 34; optional PoolingParameter pooling_param = 19; optional PowerParameter power_param = 21; + optional PythonParameter python_param = 41; optional ReLUParameter relu_param = 30; optional SigmoidParameter sigmoid_param = 38; optional SoftmaxParameter softmax_param = 39; @@ -603,6 +605,12 @@ message PowerParameter { optional float shift = 3 [default = 0.0]; } +// Message that stores parameters used by PythonLayer +message PythonParameter { + optional string module = 1; + optional string layer = 2; +} + // Message that stores parameters used by ReLULayer message ReLUParameter { // Allow non-zero slope for negative inputs to speed up optimization