From c49cd71d6eea5dfb41f8571cea4c23074be24a37 Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.anjos@idiap.ch> Date: Mon, 13 Jan 2014 17:07:16 +0100 Subject: [PATCH] Base Activation class fully implemented with all details from original --- setup.py | 2 +- xbob/machine/activation.cpp | 355 +++++++++++++++++++----- xbob/machine/include/xbob.machine/api.h | 9 +- 3 files changed, 289 insertions(+), 77 deletions(-) diff --git a/setup.py b/setup.py index 0f9ce08..ecb1f92 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ setup( ), Extension("xbob.machine._library", [ - #"xbob/machine/activation.cpp", + "xbob/machine/activation.cpp", "xbob/machine/main.cpp", ], packages = packages, diff --git a/xbob/machine/activation.cpp b/xbob/machine/activation.cpp index a06e7f1..7acb1fa 100644 --- a/xbob/machine/activation.cpp +++ b/xbob/machine/activation.cpp @@ -7,8 +7,13 @@ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland */ -#include <cleanup.h> +#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> @@ -45,56 +50,32 @@ static int PyBobMachineActivation_init(PyBobMachineActivationObject* self, PyObj } -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); + 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 (array->ndim == 2) { - blitz::Array<double,2>* z_ = PyBlitzArrayCxx_AsBlitz<double,2>(z); - blitz::Array<double,2>* res_ = PyBlitzArrayCxx_AsBlitz<double,2>(res); + 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 (array->ndim == 3) { - blitz::Array<double,3>* z_ = PyBlitzArrayCxx_AsBlitz<double,3>(z); - blitz::Array<double,3>* res_ = PyBlitzArrayCxx_AsBlitz<double,3>(res); + 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) @@ -102,9 +83,9 @@ static int apply(boost::function<double (double)> function, 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); + 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) @@ -118,14 +99,18 @@ static int apply(boost::function<double (double)> function, } 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; - //protects acquired resources through this scope - auto z_ = make_safe(z); + //note: object z is borrowed if (PyBlitzArray_Check(z) || PyArray_Check(z)) { @@ -149,7 +134,8 @@ static PyObject* PyBobMachineActivation_call1(PyBobMachineActivationObject* o, auto res_ = make_safe(res); // processes the data - int ok = apply(boost::bind(&bob::machine::Activation::f, *(o->base), _1), z_converted, res); + 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)"); @@ -165,7 +151,8 @@ static PyObject* PyBobMachineActivation_call1(PyBobMachineActivationObject* o, PyObject* z_float = PyNumber_Float(z); auto z_float_ = make_safe(z_float); - double res_c = o->base->f(PyFloat_AsDouble(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); } @@ -176,6 +163,7 @@ static PyObject* PyBobMachineActivation_call1(PyBobMachineActivationObject* o, } static PyObject* PyBobMachineActivation_call2(PyBobMachineActivationObject* o, + double (bob::machine::Activation::*method) (double) const, PyObject* args, PyObject* kwds) { /* Parses input arguments in a single shot */ @@ -217,14 +205,14 @@ static PyObject* PyBobMachineActivation_call2(PyBobMachineActivationObject* o, 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]); + 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(&bob::machine::Activation::f, *(o->base), _1), z, res); + 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)"); @@ -232,10 +220,34 @@ static PyObject* PyBobMachineActivation_call2(PyBobMachineActivationObject* o, } Py_INCREF(res); - return res; + return 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) { @@ -244,11 +256,117 @@ static PyObject* PyBobMachineActivation_call(PyBobMachineActivationObject* o, switch (nargs) { case 1: - return PyBobMachineActivation_call1(self, args, kwds); + 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(self, args, kwds); + return PyBobMachineActivation_call2 + (o, &bob::machine::Activation::f_prime_from_f, args, kwds); break; default: @@ -261,6 +379,87 @@ static PyObject* PyBobMachineActivation_call(PyBobMachineActivationObject* o, } +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, @@ -268,8 +467,38 @@ static PyMethodDef PyBobMachineActivation_methods[] = { 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 */ -} +}; static int PyBobMachineActivation_Check(PyObject* o) { return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobMachineActivation_Type)); @@ -287,11 +516,11 @@ static PyObject* PyBobMachineActivation_RichCompare (PyBobMachineActivationObjec switch (op) { case Py_EQ: - if (*(self->base) == *(other_->base)) Py_RETURN_TRUE; + if (self->base->str() == other_->base->str()) Py_RETURN_TRUE; Py_RETURN_FALSE; break; case Py_NE: - if (*(self->base) != *(other_->base)) Py_RETURN_TRUE; + if (self->base->str() != other_->base->str()) Py_RETURN_TRUE; Py_RETURN_FALSE; break; default: @@ -301,6 +530,10 @@ static PyObject* PyBobMachineActivation_RichCompare (PyBobMachineActivationObjec } +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 */ @@ -317,8 +550,8 @@ PyTypeObject PyBobMachineActivation_Type = { 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ + (ternaryfunc)PyBobMachineActivation_call, /* tp_call */ + (reprfunc)PyBobMachineActivation_Str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ @@ -342,23 +575,3 @@ PyTypeObject PyBobMachineActivation_Type = { 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 26cc62a..4cecbcd 100644 --- a/xbob/machine/include/xbob.machine/api.h +++ b/xbob/machine/include/xbob.machine/api.h @@ -10,7 +10,6 @@ #include <Python.h> #include <xbob.machine/config.h> -#include <bob/config.h> #include <bob/machine/Activation.h> #define XBOB_MACHINE_MODULE_PREFIX xbob.machine @@ -31,14 +30,14 @@ * Bindings for xbob.io.Activation * ***********************************/ -/* Type definition for PyBobIoFileObject */ +/* Type definition for PyBobMachineActivationObject */ typedef struct { PyObject_HEAD /* Type-specific fields go here. */ bob::machine::Activation* base; -} PyBobMachineActivation; +} PyBobMachineActivationObject; #define PyBobMachineActivation_Type_NUM 1 #define PyBobMachineActivation_Type_TYPE PyTypeObject @@ -48,7 +47,7 @@ typedef struct { #ifdef XBOB_MACHINE_MODULE - /* This section is used when compiling `xbob.core.random' itself */ + /* This section is used when compiling `xbob.machine' itself */ /************** * Versioning * @@ -98,7 +97,7 @@ typedef struct { * Bindings for xbob.machine.Activation * ****************************************/ -# define PyBobMachineActivation_Type (*(PyBobMachineActivation_Type_TYPE *)PyXbobIo_API[PyBobMachineActivation_Type_NUM]) +# define PyBobMachineActivation_Type (*(PyBobMachineActivation_Type_TYPE *)PyXbobMachine_API[PyBobMachineActivation_Type_NUM]) # if !defined(NO_IMPORT_ARRAY) -- GitLab