diff --git a/xbob/machine/activation.cpp b/xbob/machine/activation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a06e7f1a274d87d07150cf9ea9c5aa2706181ef5 --- /dev/null +++ b/xbob/machine/activation.cpp @@ -0,0 +1,364 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Fri 10 Jan 2014 14:26:25 CET + * + * @brief Python bindings for the machine activation + * + * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland + */ + +#include <cleanup.h> +#include <bob/machine/Activation.h> +#include <boost/function.hpp> +#include <structmember.h> + +/******************************************* + * Implementation of Activation base class * + *******************************************/ + +PyDoc_STRVAR(s_activation_str, XBOB_EXT_MODULE_PREFIX ".Activation"); + +PyDoc_STRVAR(s_activation_doc, +"Base class for activation functions (actually, *functors*).\n\ +\n\ +.. warning::\n\ +\n\ + You cannot instantiate an object of this type directly, you must\n\ + use it through one of the inherited types.\n\ +\n\ +.. warning::\n\ +\n\ + You cannot create classes in Python that derive from this one and\n\ + expect them to work fine with the C++ code, as no hook is\n\ + implemented as of this time to allow for this. You must create\n\ + a class that inherits from the C++\n\ + :cpp:type:`bob::machine::Activation` in C++ and then bind it to\n\ + Python like we have done for the classes available in these\n\ + bindings.\n\ +\n\ +"); + +static int PyBobMachineActivation_init(PyBobMachineActivationObject* self, PyObject*, PyObject*) { + + PyErr_Format(PyExc_NotImplementedError, "cannot initialize object of base type `%s' - use one of the inherited classes", s_activation_str); + return -1; + +} + +PyDoc_STRVAR(s_call_str, "f"); +PyDoc_STRVAR(s_call_doc, +"o.f(z, [res]) -> array | scalar + +Computes the activated value, given an input array or scalar\n\ +``z``, placing results in ``res`` (and returning it). + +If ``z`` is an array, then you can pass another array in ``res``\n\ +to store the results and, in this case, we won't allocate a new\n\ +one for that purpose. This can be a speed-up in certain scenarios.\n\ +Note this does not work for scalars as it makes little sense to\n\ +avoid scalar allocation at this level.\n\ +\n\ +If you decide to pass an array in ``res``, note this array should\n\ +have the exact same dimensions as the input array ``z``. It is an +error otherwise.\n\ +\n\ +.. note::\n\ +\n\ + This method only accepts 64-bit float arrays as input or\n\ + output.\n\ +\n\ +"); + +/** + * Maps all elements of arr through function() into retval + */ +static int apply(boost::function<double (double)> function, + PyBlitzArrayObject* z, PyBlitzArrayObject* res) { + + if (arr->ndim == 1) { + blitz::Array<double,1>* z_ = PyBlitzArrayCxx_AsBlitz<double,1>(z); + blitz::Array<double,1>* res_ = PyBlitzArrayCxx_AsBlitz<double,1>(res); + for (int k=0; k<z_->extent(0); ++k) + res_->operator()(k) = function(z_->operator()(k)); + return 1; + } + + else if (array->ndim == 2) { + blitz::Array<double,2>* z_ = PyBlitzArrayCxx_AsBlitz<double,2>(z); + blitz::Array<double,2>* res_ = PyBlitzArrayCxx_AsBlitz<double,2>(res); + for (int k=0; k<z_->extent(0); ++k) + for (int l=0; l<z_->extent(1); ++l) + res_->operator()(k,l) = function(z_->operator()(k,l)); + return 1; + } + + else if (array->ndim == 3) { + blitz::Array<double,3>* z_ = PyBlitzArrayCxx_AsBlitz<double,3>(z); + blitz::Array<double,3>* res_ = PyBlitzArrayCxx_AsBlitz<double,3>(res); + for (int k=0; k<z_->extent(0); ++k) + for (int l=0; l<z_->extent(1); ++l) + for (int m=0; m<z_->extent(2); ++m) + res_->operator()(k,l,m) = function(z_->operator()(k,l,m)); + return 1; + } + + else if (array->ndim == 4) { + blitz::Array<double,4>* z_ = PyBlitzArrayCxx_AsBlitz<double,4>(z); + blitz::Array<double,4>* res_ = PyBlitzArrayCxx_AsBlitz<double,4>(res); + for (int k=0; k<z_->extent(0); ++k) + for (int l=0; l<z_->extent(1); ++l) + for (int m=0; m<z_->extent(2); ++m) + for (int n=0; n<z_->extent(3); ++n) + res_->operator()(k,l,m,n) = function(z_->operator()(k,l,m,n)); + return 1; + } + + return 0; + +} + +static PyObject* PyBobMachineActivation_call1(PyBobMachineActivationObject* o, + PyObject* args, PyObject* kwds) { + + PyObject* z = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &z)) return 0; + + //protects acquired resources through this scope + auto z_ = make_safe(z); + + if (PyBlitzArray_Check(z) || PyArray_Check(z)) { + + PyBlitzArrayObject* z_converted = 0; + PyBlitzArray_Converter(z, &z_converted); + auto z_converted_ = make_safe(z_converted); + + if (z_converted->type_num != NPY_FLOAT64) { + PyErr_SetString(PyExc_TypeError, "Activation function only supports 64-bit float arrays for input array `z'"); + return 0; + } + + if (z_converted->ndim < 1 || z_converted->ndim > 4) { + PyErr_Format(PyExc_TypeError, "Activation function only accepts 1, 2, 3 or 4-dimensional arrays (not %" PY_FORMAT_SIZE_T "dD arrays)", z_converted->ndim); + return 0; + } + + // creates output array + PyObject* res = PyBlitzArray_SimpleNew(NPY_FLOAT64, z_converted->ndim, + z_converted->shape); + auto res_ = make_safe(res); + + // processes the data + int ok = apply(boost::bind(&bob::machine::Activation::f, *(o->base), _1), z_converted, res); + + if (!ok) { + PyErr_SetString(PyExc_RuntimeError, "unexpected error occurred applying C++ activation function to input array (DEBUG ME)"); + return 0; + } + + Py_INCREF(res); + return res; + + } + + else if (PyNumber_Check(z)) { + + PyObject* z_float = PyNumber_Float(z); + auto z_float_ = make_safe(z_float); + double res_c = o->base->f(PyFloat_AsDouble(z_float); + return PyFloat_FromDouble(res_c); + + } + + PyErr_Format(PyExc_TypeError, "Activation function is not capable to process input objects of type `%s'", z->ob_type->tp_name); + return 0; + +} + +static PyObject* PyBobMachineActivation_call2(PyBobMachineActivationObject* o, + PyObject* args, PyObject* kwds) { + + /* Parses input arguments in a single shot */ + static const char* const_kwlist[] = {"z", "res", 0}; + static char** kwlist = const_cast<char**>(const_kwlist); + + PyBlitzArrayObject* z = 0; + PyBlitzArrayObject* res = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O&", kwlist, + &PyBlitzArray_Converter, &z, + &PyBlitzArray_OutputConverter, &res + )) return 0; + + //protects acquired resources through this scope + auto z_ = make_safe(z); + auto res_ = make_safe(res); + + if (z->type_num != NPY_FLOAT64) { + PyErr_SetString(PyExc_TypeError, "Activation function only supports 64-bit float arrays for input array `z'"); + return 0; + } + + if (res->type_num != NPY_FLOAT64) { + PyErr_SetString(PyExc_TypeError, "Activation function only supports 64-bit float arrays for output array `res'"); + return 0; + } + + if (z->ndim < 1 || z->ndim > 4) { + PyErr_Format(PyExc_TypeError, "Activation function only accepts 1, 2, 3 or 4-dimensional arrays (not %" PY_FORMAT_SIZE_T "dD arrays)", z->ndim); + return 0; + } + + if (z->ndim != res->ndim) { + PyErr_Format(PyExc_RuntimeError, "Input and output arrays should have matching number of dimensions, but input array `z' has %" PY_FORMAT_SIZE_T "d dimensions while output array `res' has %" PY_FORMAT_SIZE_T "d dimensions", z->ndim, res->ndim); + return 0; + } + + for (Py_ssize_t i=0; i<z->ndim; ++i) { + + if (z->shape[i] != res->shape[i]) { + PyErr_Format(PyExc_RuntimeError, "Input and output arrays should have matching sizes, but dimension %" PY_FORMAT_SIZE_T "d of input array `z' has %" PY_FORMAT_SIZE_T "d positions while output array `res' has %" PY_FORMAT_SIZE_T "d positions", z->shape[i], res->shape[i]); + return 0; + } + + } + + //at this point all checks are done, we can proceed into calling C++ + int ok = apply(boost::bind(&bob::machine::Activation::f, *(o->base), _1), z, res); + + if (!ok) { + PyErr_SetString(PyExc_RuntimeError, "unexpected error occurred applying C++ activation function to input array (DEBUG ME)"); + return 0; + } + + Py_INCREF(res); + return res; + +} + +static PyObject* PyBobMachineActivation_call(PyBobMachineActivationObject* o, + PyObject* args, PyObject* kwds) { + + Py_ssize_t nargs = args?PyTuple_Size(args):0 + kwds?PyDict_Size(kwds):0; + + switch (nargs) { + + case 1: + return PyBobMachineActivation_call1(self, args, kwds); + break; + + case 2: + return PyBobMachineActivation_call2(self, args, kwds); + break; + + default: + + PyErr_Format(PyExc_RuntimeError, "number of arguments mismatch - %s requires 1 or 2 arguments, but you provided %" PY_FORMAT_SIZE_T "d (see help)", s_call_str, nargs); + + } + + return 0; + +} + +static PyMethodDef PyBobMachineActivation_methods[] = { + { + s_call_str, + (PyCFunction)PyBobMachineActivation_call, + METH_VARARGS|METH_KEYWORDS, + s_call_doc + }, + {0} /* Sentinel */ +} + +static int PyBobMachineActivation_Check(PyObject* o) { + return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobMachineActivation_Type)); +} + +static PyObject* PyBobMachineActivation_RichCompare (PyBobMachineActivationObject* self, PyObject* other, int op) { + + if (!PyBobMachineActivation_Check(other)) { + PyErr_Format(PyExc_TypeError, "cannot compare `%s' with `%s'", + s_activation_str, other->ob_type->tp_name); + return 0; + } + + PyBobMachineActivationObject* other_ = reinterpret_cast<PyBobMachineActivationObject*>(other); + + switch (op) { + case Py_EQ: + if (*(self->base) == *(other_->base)) Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + case Py_NE: + if (*(self->base) != *(other_->base)) Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + default: + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + +} + +PyTypeObject PyBobMachineActivation_Type = { + PyObject_HEAD_INIT(0) + 0, /* ob_size */ + s_activation_str, /* tp_name */ + sizeof(PyBobMachineActivationObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + s_activation_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + (richcmpfunc)PyBobMachineActivation_RichCompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PyBobMachineActivation_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyBobMachineActivation_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +/** + .def("__call__", &activation_f_ndarray_1, (arg("self"), arg("z"), arg("res")), "Computes the activated value, given an input array ``z``, placing results in ``res`` (and returning it)") + .def("__call__", &activation_f_ndarray_2, (arg("self"), arg("z")), "Computes the activated value, given an input array ``z``. Returns a newly allocated array with the same size as ``z``") + .def("__call__", &bob::machine::Activation::f, (arg("self"), arg("z")), "Computes the activated value, given an input ``z``") + .def("f_prime", &activation_f_prime_ndarray_1, (arg("self"), arg("z"), arg("res")), "Computes the derivative of the activated value, placing results in ``res`` (and returning it)") + .def("f_prime", &activation_f_prime_ndarray_2, (arg("self"), arg("z")), "Computes the derivative of the activated value, given an input array ``z``. Returns a newly allocated array with the same size as ``z``") + .def("f_prime", &bob::machine::Activation::f_prime, (arg("self"), arg("z")), "Computes the derivative of the activated value.") + .def("f_prime_from_f", &activation_f_prime_from_f_ndarray_1, (arg("self"), arg("a"), arg("res")), "Computes the derivative of the activated value, given **the activated value** ``a``, placing results in ``res`` (and returning it)") + .def("f_prime_from_f", &activation_f_prime_from_f_ndarray_2, (arg("self"), arg("z")), "Computes the derivative of the activated value, given **the activated value** ``a``. Returns a newly allocated array with the same size as ``a`` with the answer.") + .def("f_prime_from_f", &bob::machine::Activation::f_prime_from_f, (arg("self"), arg("a")), "Computes the derivative of the activation value, given **the activated value** ``a``.") + .def("save", &bob::machine::Activation::save, (arg("self"), arg("h5f")), + "Saves itself to a :py:class:`bob.io.HDF5File`") + .def("load", &bob::machine::Activation::load, (arg("self"), arg("h5f")), + "Loads itself from a :py:class:`bob.io.HDF5File`") + .def("unique_identifier", + &bob::machine::Activation::unique_identifier, (arg("self")), + "Returns a unique identifier, used by this class in connection to the Activation registry.") + .def("__str__", &bob::machine::Activation::str) +**/ diff --git a/xbob/machine/include/xbob.machine/api.h b/xbob/machine/include/xbob.machine/api.h index c951ade48ca1b9ace5b37a5877682f09a47f089b..26cc62ab442e7c08936214620c1c8a598c937fe7 100644 --- a/xbob/machine/include/xbob.machine/api.h +++ b/xbob/machine/include/xbob.machine/api.h @@ -36,7 +36,7 @@ typedef struct { PyObject_HEAD /* Type-specific fields go here. */ - boost::shared_ptr<bob::machine::Activation> o; + bob::machine::Activation* base; } PyBobMachineActivation;