Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Compilation] I think this causes the mutliple definition problem: python/caffe/_caffe.o is going to be linked into libcaffe.a because of this, and then when we make pycaffe, python/caffe/_caffe.cpp (which _caffe.o comes from) gets linked again - causing multiple definitions.

We should remove this line (together with other changes, see below).

endif
# tool, example, and test objects
TOOL_OBJS := $(addprefix $(BUILD_DIR)/, ${TOOL_SRCS:.cpp=.o})
TOOL_BUILD_DIR := $(BUILD_DIR)/tools
Expand Down Expand Up @@ -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

##############################
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions Makefile.config.example
Original file line number Diff line number Diff line change
Expand Up @@ -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++
Expand Down
51 changes: 51 additions & 0 deletions include/caffe/python_layer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef CAFFE_PYTHON_LAYER_HPP_
#define CAFFE_PYTHON_LAYER_HPP_

#include <boost/python.hpp>
#include <vector>

#include "../python/caffe/_caffe.hpp"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Compilation] Remove this include. Put necessary things in python_layer.hpp instead of _caffe.hpp.

#include "caffe/layer.hpp"

namespace caffe {

/**
* @brief Wrap a layer implemented in Python.
*/
template <typename Dtype>
class PythonLayer : public Layer<Dtype> {
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<Dtype>(param) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);

virtual inline LayerParameter_LayerType type() const {
return LayerParameter_LayerType_PYTHON;
}

protected:
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

boost::python::object layer_;

private:
vector<PyBlob<Dtype> > PythonBlobVector(const vector<Blob<Dtype>*>& vec);
};

} // namespace caffe

#endif
9 changes: 6 additions & 3 deletions python/caffe/_caffe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PyArrayObject *>(obj), self_);
Py_INCREF(self_);
Expand All @@ -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<PyArrayObject *>(obj), self_);
PyArray_SetBaseObject(reinterpret_cast<PyArrayObject*>(obj), self_);
Py_INCREF(self_);
bp::handle<> h(obj);

Expand Down Expand Up @@ -198,6 +198,9 @@ BOOST_PYTHON_MODULE(_caffe) {
bp::class_<vector<string> >("StringVec")
.def(bp::vector_indexing_suite<vector<string> >());

bp::class_<vector<bool> >("BoolVec")
.def(bp::vector_indexing_suite<vector<bool> >());

import_array();
}

Expand Down
20 changes: 17 additions & 3 deletions python/caffe/_caffe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ using boost::shared_ptr;

namespace caffe {


// wrap shared_ptr<Blob> in a class that we construct in C++ and pass
// to Python
template <typename Dtype>
class PyBlob {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Compilation] As far as I understand it, PyBlob is what gets used in python_layer.hpp: we should remove it from _caffe.hpp, and put it in python_layer.hpp so libcaffe.a does not rely on _caffe.hpp or _caffe.cpp. If there is anything implemented in _caffe.cpp for PyBlob, move that to python_layer.cpp too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or if you would like things to be clearer, create caffe/util/python_util.hpp for PyBlob.)

public:
explicit PyBlob(const shared_ptr<Blob<Dtype> > &blob)
: blob_(blob) {}
// 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<Dtype> >& 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<Dtype>* blob)
: blob_(blob, null_deleter()) { }

int num() const { return blob_->num(); }
int channels() const { return blob_->channels(); }
Expand All @@ -42,6 +50,13 @@ class PyBlob {

protected:
shared_ptr<Blob<Dtype> > 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
Expand Down Expand Up @@ -91,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);
Expand Down
10 changes: 10 additions & 0 deletions src/caffe/layer_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -231,6 +234,13 @@ Layer<Dtype>* GetLayer(const LayerParameter& param) {
return GetPoolingLayer<Dtype>(name, param);
case LayerParameter_LayerType_POWER:
return new PowerLayer<Dtype>(param);
case LayerParameter_LayerType_PYTHON:
#ifdef USE_PYTHON_LAYER

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Compilation] Hmm, after you merge to the dev head there should be no more legacy factory code, no?

Under the new factory code you wouldn't need to do ifdef anymore. If the code is not compiled with python layer, the code automatically tells you it's not available - that's the beauty of registraiton.

return new PythonLayer<Dtype>(param);
#else
LOG(FATAL) << "Attempt to use PythonLayer, but built without "
"USE_PYTHON_LAYER option.";
#endif
case LayerParameter_LayerType_RELU:
return GetReLULayer<Dtype>(name, param);
case LayerParameter_LayerType_SILENCE:
Expand Down
74 changes: 74 additions & 0 deletions src/caffe/layers/python_layer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#ifdef USE_PYTHON_LAYER
#include <boost/python.hpp>
#include <Python.h>
#include <vector>

#include "caffe/layer.hpp"
#include "caffe/python_layer.hpp"

namespace bp = boost::python;

namespace caffe {

template <typename Dtype>
vector<PyBlob<Dtype> > PythonLayer<Dtype>::PythonBlobVector(
const vector<Blob<Dtype>*>& vec) {
return vector<PyBlob<Dtype> >(vec.begin(), vec.end());
}

template <typename Dtype>
void PythonLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& 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 <typename Dtype>
void PythonLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
try {
layer_.attr("reshape")(PythonBlobVector(bottom), PythonBlobVector(top));
} catch (bp::error_already_set) {
PyErr_Print();
throw;
}
}

template <typename Dtype>
void PythonLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
try {
layer_.attr("forward")(PythonBlobVector(bottom), PythonBlobVector(top));
} catch (bp::error_already_set) {
PyErr_Print();
throw;
}
}

template <typename Dtype>
void PythonLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& 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
12 changes: 10 additions & 2 deletions src/caffe/proto/caffe.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -251,6 +251,7 @@ message LayerParameter {
MVN = 34;
POOLING = 17;
POWER = 26;
PYTHON = 38;
RELU = 18;
SIGMOID = 19;
SIGMOID_CROSS_ENTROPY_LOSS = 27;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down