/** * @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 */ #define XBOB_MACHINE_MODULE #include "cleanup.h" #include <xbob.machine/api.h> #include <xbob.io/api.h> #include <xbob.blitz/cppapi.h> #include <bob/machine/Activation.h> #include <boost/bind.hpp> #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; } /** * Maps all elements of arr through function() into retval */ static int apply(boost::function<double (double)> function, PyBlitzArrayObject* z, PyBlitzArrayObject* res) { if (z->ndim == 1) { auto z_ = PyBlitzArrayCxx_AsBlitz<double,1>(z); auto 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 (z->ndim == 2) { auto z_ = PyBlitzArrayCxx_AsBlitz<double,2>(z); auto 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 (z->ndim == 3) { auto z_ = PyBlitzArrayCxx_AsBlitz<double,3>(z); auto 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 (z->ndim == 4) { auto z_ = PyBlitzArrayCxx_AsBlitz<double,4>(z); auto 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, double (bob::machine::Activation::*method) (double) const, PyObject* args, PyObject* kwds) { /* Parses input arguments in a single shot */ static const char* const_kwlist[] = {"z", 0}; static char** kwlist = const_cast<char**>(const_kwlist); PyObject* z = 0; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &z)) return 0; //note: object z is borrowed if (PyBlitzArray_Check(z) || PyArray_Check(z)) { PyBlitzArrayObject* z_converted = 0; if (!PyBlitzArray_Converter(z, &z_converted)) return 0; 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(method, o->base, _1), z_converted, reinterpret_cast<PyBlitzArrayObject*>(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 PyBlitzArray_NUMPY_WRAP(res); } else if (PyNumber_Check(z)) { PyObject* z_float = PyNumber_Float(z); auto z_float_ = make_safe(z_float); auto bound_method = boost::bind(method, o->base, _1); double res_c = bound_method(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, double (bob::machine::Activation::*method) (double) const, 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", i, 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(method, 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 PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(res)); } PyDoc_STRVAR(s_call_str, "f"); PyDoc_STRVAR(s_call_doc, "o.f(z, [res]) -> array | scalar\n\ \n\ Computes the activated value, given an input array or scalar\n\ ``z``, placing results in ``res`` (and returning it).\n\ \n\ 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\n\ error otherwise.\n\ \n\ .. note::\n\ \n\ This method only accepts 64-bit float arrays as input or\n\ output.\n\ \n\ "); 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 (o, &bob::machine::Activation::f, args, kwds); break; case 2: return PyBobMachineActivation_call2 (o, &bob::machine::Activation::f, 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; } PyDoc_STRVAR(s_f_prime_str, "f_prime"); PyDoc_STRVAR(s_f_prime_doc, "o.f_prime(z, [res]) -> array | scalar\n\ \n\ Computes the derivative of the activated value, given an input\n\ array or scalar ``z``, placing results in ``res`` (and returning\n\ it).\n\ \n\ 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\n\ error otherwise.\n\ \n\ .. note::\n\ \n\ This method only accepts 64-bit float arrays as input or\n\ output.\n\ \n\ "); static PyObject* PyBobMachineActivation_f_prime(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 (o, &bob::machine::Activation::f_prime, args, kwds); break; case 2: return PyBobMachineActivation_call2 (o, &bob::machine::Activation::f_prime, 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; } PyDoc_STRVAR(s_f_prime_from_f_str, "f_prime_from_f"); PyDoc_STRVAR(s_f_prime_from_f_doc, "o.f_prime_from_f(a, [res]) -> array | scalar\n\ \n\ Computes the derivative of the activated value, given the\n\ derivative value ``a``, placing results in ``res`` (and returning\n\ it).\n\ \n\ If ``a`` 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 ``a``. It is an\n\ error otherwise.\n\ \n\ .. note::\n\ \n\ This method only accepts 64-bit float arrays as input or\n\ output.\n\ \n\ "); static PyObject* PyBobMachineActivation_f_prime_from_f(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 (o, &bob::machine::Activation::f_prime_from_f, args, kwds); break; case 2: return PyBobMachineActivation_call2 (o, &bob::machine::Activation::f_prime_from_f, 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; } PyDoc_STRVAR(s_unique_id_str, "unique_identifier"); PyDoc_STRVAR(s_unique_id_doc, "o.unique_identifier() -> str\n\ \n\ Returns a unique (string) identifier, used by this class\n\ in connection with the Activation registry.\n\ \n\ "); static PyObject* PyBobMachineActivation_UniqueIdentifier (PyBobMachineActivationObject* o) { return Py_BuildValue("s", o->base->unique_identifier().c_str()); } PyDoc_STRVAR(s_load_str, "load"); PyDoc_STRVAR(s_load_doc, "o.load(f) -> None\n\ \n\ Loads itself from a :py:class:`xbob.io.HDF5File`\n\ \n\ "); static PyObject* PyBobMachineActivation_Load(PyBobMachineActivationObject* o, PyObject* f) { if (!PyBobIoHDF5File_Check(f)) { PyErr_Format(PyExc_TypeError, "Activation function cannot load itself from `%s', only from an HDF5 file", f->ob_type->tp_name); return 0; } auto h5f = reinterpret_cast<PyBobIoHDF5FileObject*>(f); try { o->base->load(*h5f->f); } catch (std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return 0; } catch (...) { PyErr_Format(PyExc_RuntimeError, "cannot read data from file `%s' (at group `%s'): unknown exception caught", h5f->f->filename().c_str(), h5f->f->cwd().c_str()); return 0; } Py_RETURN_NONE; } PyDoc_STRVAR(s_save_str, "save"); PyDoc_STRVAR(s_save_doc, "o.save(f) -> None\n\ \n\ Loads itself from a :py:class:`xbob.io.HDF5File`\n\ \n\ "); static PyObject* PyBobMachineActivation_Save(PyBobMachineActivationObject* o, PyObject* f) { if (!PyBobIoHDF5File_Check(f)) { PyErr_Format(PyExc_TypeError, "Activation function cannot write itself to `%s', only to an HDF5 file", f->ob_type->tp_name); return 0; } auto h5f = reinterpret_cast<PyBobIoHDF5FileObject*>(f); try { o->base->save(*h5f->f); } catch (std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return 0; } catch (...) { PyErr_Format(PyExc_RuntimeError, "cannot write data to file `%s' (at group `%s'): unknown exception caught", h5f->f->filename().c_str(), h5f->f->cwd().c_str()); return 0; } Py_RETURN_NONE; } static PyMethodDef PyBobMachineActivation_methods[] = { { s_call_str, (PyCFunction)PyBobMachineActivation_call, METH_VARARGS|METH_KEYWORDS, s_call_doc }, { s_f_prime_str, (PyCFunction)PyBobMachineActivation_f_prime, METH_VARARGS|METH_KEYWORDS, s_f_prime_doc }, { s_f_prime_from_f_str, (PyCFunction)PyBobMachineActivation_f_prime_from_f, METH_VARARGS|METH_KEYWORDS, s_f_prime_from_f_doc }, { s_unique_id_str, (PyCFunction)PyBobMachineActivation_UniqueIdentifier, METH_NOARGS, s_unique_id_doc }, { s_load_str, (PyCFunction)PyBobMachineActivation_Load, METH_O, s_load_doc }, { s_save_str, (PyCFunction)PyBobMachineActivation_Save, METH_O, s_save_doc }, {0} /* Sentinel */ }; 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; } auto other_ = reinterpret_cast<PyBobMachineActivationObject*>(other); switch (op) { case Py_EQ: if (self->base->str() == other_->base->str()) Py_RETURN_TRUE; Py_RETURN_FALSE; break; case Py_NE: if (self->base->str() != other_->base->str()) Py_RETURN_TRUE; Py_RETURN_FALSE; break; default: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } } static PyObject* PyBobMachineActivation_Str (PyBobMachineActivationObject* o) { return Py_BuildValue("s", o->base->str().c_str()); } 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 */ (ternaryfunc)PyBobMachineActivation_call, /* tp_call */ (reprfunc)PyBobMachineActivation_Str, /* 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 */ };