diff --git a/setup.py b/setup.py
index 106d48bc8de3db523331320ee4b99823463f526d..5bd87830c3445f1e735e0e8c5eb9577d76913763 100644
--- a/setup.py
+++ b/setup.py
@@ -63,6 +63,7 @@ setup(
         ),
       Extension("xbob.learn.mlp._library",
         [
+          "xbob/learn/mlp/rprop.cpp",
           "xbob/learn/mlp/backprop.cpp",
           "xbob/learn/mlp/trainer.cpp",
           "xbob/learn/mlp/cxx/machine.cpp",
diff --git a/xbob/learn/mlp/backprop.cpp b/xbob/learn/mlp/backprop.cpp
index f71ee7ce2e1c05b9cc0108e28fb894fa2d286d91..6f77625ceb07fbbc36b9a0f5bc0978128e4275f4 100644
--- a/xbob/learn/mlp/backprop.cpp
+++ b/xbob/learn/mlp/backprop.cpp
@@ -470,15 +470,14 @@ static PyObject* PyBobLearnMLPBackProp_train
 
 }
 
-/**
-PyDoc_STRVAR(s_set_derivative_str, "set_derivative");
-PyDoc_STRVAR(s_set_derivative_doc,
-    "Sets the cost derivative w.r.t. the **weights** for a given layer.");
+PyDoc_STRVAR(s_set_previous_derivative_str, "set_previous_derivative");
+PyDoc_STRVAR(s_set_previous_derivative_doc,
+    "Sets the previous cost derivative for a given weight layer (index).");
 
-static PyObject* PyBobLearnMLPBackProp_setDerivativeOnLayer
-(PyBobLearnMLPBackPropObject* self, PyObject* args, PyObject* kwds) {
+static PyObject* PyBobLearnMLPRProp_setPreviousDerivativeOnLayer
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
 
-  /* Parses input arguments in a single shot *\/
+  /* Parses input arguments in a single shot */
   static const char* const_kwlist[] = {"array", "layer", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
@@ -489,19 +488,20 @@ static PyObject* PyBobLearnMLPBackProp_setDerivativeOnLayer
         &PyBlitzArray_Converter, &array, &layer)) return 0;
 
   if (array->type_num != NPY_FLOAT64 || array->ndim != 2) {
-    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 2D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 2D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_previous_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
     return 0;
   }
 
   try {
-    self->cxx->setDerivative(*PyBlitzArrayCxx_AsBlitz<double,2>(array), layer);
+    self->cxx->setPreviousDerivative(*PyBlitzArrayCxx_AsBlitz<double,2>(array),
+        layer);
   }
   catch (std::exception& ex) {
     PyErr_SetString(PyExc_RuntimeError, ex.what());
     return 0;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot set derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
+    PyErr_Format(PyExc_RuntimeError, "cannot set previous derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
     return 0;
   }
 
@@ -509,14 +509,14 @@ static PyObject* PyBobLearnMLPBackProp_setDerivativeOnLayer
 
 }
 
-PyDoc_STRVAR(s_set_bias_derivative_str, "set_bias_derivative");
-PyDoc_STRVAR(s_set_bias_derivative_doc,
-    "Sets the cost derivative w.r.t. the **biases** for a given layer.");
+PyDoc_STRVAR(s_set_previous_bias_derivative_str, "set_previous_bias_derivative");
+PyDoc_STRVAR(s_set_previous_bias_derivative_doc,
+    "Sets the cost bias derivative for a given bias layer (index).");
 
-static PyObject* PyBobLearnMLPBackProp_setBiasDerivativeOnLayer
-(PyBobLearnMLPBackPropObject* self, PyObject* args, PyObject* kwds) {
+static PyObject* PyBobLearnMLPRProp_setPreviousBiasDerivativeOnLayer
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
 
-  /* Parses input arguments in a single shot *\/
+  /* Parses input arguments in a single shot */
   static const char* const_kwlist[] = {"array", "layer", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
@@ -527,26 +527,25 @@ static PyObject* PyBobLearnMLPBackProp_setBiasDerivativeOnLayer
         &PyBlitzArray_Converter, &array, &layer)) return 0;
 
   if (array->type_num != NPY_FLOAT64 || array->ndim != 1) {
-    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 1D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_bias_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 1D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_previous_bias_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
     return 0;
   }
 
   try {
-    self->cxx->setBiasDerivative(*PyBlitzArrayCxx_AsBlitz<double,1>(array), layer);
+    self->cxx->setPreviousBiasDerivative(*PyBlitzArrayCxx_AsBlitz<double,1>(array), layer);
   }
   catch (std::exception& ex) {
     PyErr_SetString(PyExc_RuntimeError, ex.what());
     return 0;
   }
   catch (...) {
-    PyErr_Format(PyExc_RuntimeError, "cannot set bias derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
+    PyErr_Format(PyExc_RuntimeError, "cannot set previous bias derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
     return 0;
   }
 
   Py_RETURN_NONE;
 
 }
-**/
 
 static PyMethodDef PyBobLearnMLPBackProp_methods[] = {
   {
@@ -561,20 +560,18 @@ static PyMethodDef PyBobLearnMLPBackProp_methods[] = {
     METH_VARARGS|METH_KEYWORDS,
     s_train_doc,
   },
-  /**
   {
-    s_set_derivative_str,
-    (PyCFunction)PyBobLearnMLPBackProp_setDerivativeOnLayer,
+    s_set_previous_derivative_str,
+    (PyCFunction)PyBobLearnMLPRProp_setPreviousDerivativeOnLayer,
     METH_VARARGS|METH_KEYWORDS,
-    s_set_derivative_doc,
+    s_set_previous_derivative_doc,
   },
   {
-    s_set_bias_derivative_str,
-    (PyCFunction)PyBobLearnMLPBackProp_setBiasDerivativeOnLayer,
+    s_set_previous_bias_derivative_str,
+    (PyCFunction)PyBobLearnMLPRProp_setPreviousBiasDerivativeOnLayer,
     METH_VARARGS|METH_KEYWORDS,
-    s_set_bias_derivative_doc,
+    s_set_previous_bias_derivative_doc,
   },
-  **/
   {0} /* Sentinel */
 };
 
diff --git a/xbob/learn/mlp/include/xbob.learn.mlp/api.h b/xbob/learn/mlp/include/xbob.learn.mlp/api.h
index b6358d0c9a4bcce99b7954dfeb7886a8722e12d2..b27a12381275e822331d5ced037ab813cce4d4d3 100644
--- a/xbob/learn/mlp/include/xbob.learn.mlp/api.h
+++ b/xbob/learn/mlp/include/xbob.learn.mlp/api.h
@@ -17,6 +17,7 @@
 #include "shuffler.h"
 #include "trainer.h"
 #include "backprop.h"
+#include "rprop.h"
 
 #define XBOB_LEARN_MLP_MODULE_PREFIX xbob.learn.mlp
 #define XBOB_LEARN_MLP_MODULE_NAME _library
@@ -45,6 +46,8 @@ enum _PyBobLearnMLP_ENUM {
   PyBobLearnMLPTrainer_Check_NUM,
   PyBobLearnMLPBackProp_Type_NUM,
   PyBobLearnMLPBackProp_Check_NUM,
+  PyBobLearnMLPRProp_Type_NUM,
+  PyBobLearnMLPRProp_Check_NUM,
   // Total number of C API pointers
   PyXbobLearnMLP_API_pointers
 };
@@ -130,6 +133,16 @@ typedef struct {
 #define PyBobLearnMLPBackProp_Check_RET int
 #define PyBobLearnMLPBackProp_Check_PROTO (PyObject* o)
 
+typedef struct {
+  PyBobLearnMLPTrainerObject parent;
+  bob::learn::mlp::RProp* cxx;
+} PyBobLearnMLPRPropObject;
+
+#define PyBobLearnMLPRProp_Type_TYPE PyTypeObject
+
+#define PyBobLearnMLPRProp_Check_RET int
+#define PyBobLearnMLPRProp_Check_PROTO (PyObject* o)
+
 #ifdef XBOB_LEARN_MLP_MODULE
 
   /* This section is used when compiling `xbob.learn.mlp' itself */
@@ -182,6 +195,10 @@ typedef struct {
 
   PyBobLearnMLPBackProp_Check_RET PyBobLearnMLPBackProp_Check PyBobLearnMLPBackProp_Check_PROTO;
 
+  extern PyBobLearnMLPRProp_Type_TYPE PyBobLearnMLPRProp_Type;
+
+  PyBobLearnMLPRProp_Check_RET PyBobLearnMLPRProp_Check PyBobLearnMLPRProp_Check_PROTO;
+
 #else
 
   /* This section is used in modules that use `xbob.learn.mlp's' C-API */
@@ -256,6 +273,10 @@ typedef struct {
 
 # define PyBobLearnMLPBackProp_Check (*(PyBobLearnMLPBackProp_Check_RET (*)PyBobLearnMLPBackProp_Check_PROTO) PyXbobLearnMLP_API[PyBobLearnMLPBackProp_Check_NUM])
 
+# define PyBobLearnMLPRProp_Type (*(PyBobLearnMLPRProp_Type_TYPE *)PyXbobLearnMLP_API[PyBobLearnMLPRProp_Type_NUM])
+
+# define PyBobLearnMLPRProp_Check (*(PyBobLearnMLPRProp_Check_RET (*)PyBobLearnMLPRProp_Check_PROTO) PyXbobLearnMLP_API[PyBobLearnMLPRProp_Check_NUM])
+
 # if !defined(NO_IMPORT_ARRAY)
 
   /**
diff --git a/xbob/learn/mlp/main.cpp b/xbob/learn/mlp/main.cpp
index 37c1461e50718ffd442f95a7ac071426cb8109f2..2d6c902942b8792e4b2847046964a1e7fb7072b2 100644
--- a/xbob/learn/mlp/main.cpp
+++ b/xbob/learn/mlp/main.cpp
@@ -59,6 +59,9 @@ static PyObject* create_module (void) {
   PyBobLearnMLPBackProp_Type.tp_base = &PyBobLearnMLPTrainer_Type;
   if (PyType_Ready(&PyBobLearnMLPBackProp_Type) < 0) return 0;
 
+  PyBobLearnMLPRProp_Type.tp_base = &PyBobLearnMLPTrainer_Type;
+  if (PyType_Ready(&PyBobLearnMLPRProp_Type) < 0) return 0;
+
 # if PY_VERSION_HEX >= 0x03000000
   PyObject* m = PyModule_Create(&module_definition);
 # else
@@ -93,6 +96,9 @@ static PyObject* create_module (void) {
   Py_INCREF(&PyBobLearnMLPBackProp_Type);
   if (PyModule_AddObject(m, "BackProp", (PyObject *)&PyBobLearnMLPBackProp_Type) < 0) return 0;
 
+  Py_INCREF(&PyBobLearnMLPRProp_Type);
+  if (PyModule_AddObject(m, "RProp", (PyObject *)&PyBobLearnMLPRProp_Type) < 0) return 0;
+
   static void* PyXbobLearnMLP_API[PyXbobLearnMLP_API_pointers];
 
   /* exhaustive list of C APIs */
@@ -141,6 +147,14 @@ static PyObject* create_module (void) {
 
   PyXbobLearnMLP_API[PyBobLearnMLPTrainer_Check_NUM] = (void *)&PyBobLearnMLPTrainer_Check;
 
+  PyXbobLearnMLP_API[PyBobLearnMLPBackProp_Type_NUM] = (void *)&PyBobLearnMLPBackProp_Type;
+
+  PyXbobLearnMLP_API[PyBobLearnMLPBackProp_Check_NUM] = (void *)&PyBobLearnMLPBackProp_Check;
+
+  PyXbobLearnMLP_API[PyBobLearnMLPRProp_Type_NUM] = (void *)&PyBobLearnMLPRProp_Type;
+
+  PyXbobLearnMLP_API[PyBobLearnMLPRProp_Check_NUM] = (void *)&PyBobLearnMLPRProp_Check;
+
 #if PY_VERSION_HEX >= 0x02070000
 
   /* defines the PyCapsule */
diff --git a/xbob/learn/mlp/rprop.cpp b/xbob/learn/mlp/rprop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..939b9192672a1c0ff090dce1a559bca6e011c675
--- /dev/null
+++ b/xbob/learn/mlp/rprop.cpp
@@ -0,0 +1,800 @@
+/**
+ * @author Andre Anjos <andre.anjos@idiap.ch>
+ * @date Tue  6 May 12:32:39 2014 CEST
+ *
+ * @brief Bindings for an MLP
+ *
+ * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
+ */
+
+#define XBOB_LEARN_MLP_MODULE
+#include <xbob.blitz/cppapi.h>
+#include <xbob.blitz/cleanup.h>
+#include <xbob.learn.mlp/api.h>
+#include <structmember.h>
+
+#include "utils.h"
+
+/**************************************
+ * Implementation of RProp trainer *
+ **************************************/
+
+PyDoc_STRVAR(s_trainer_str, XBOB_EXT_MODULE_PREFIX ".RProp");
+
+PyDoc_STRVAR(s_trainer_doc,
+"RProp(batch_size, cost, [trainer, [train_biases]]) -> new RProp\n\
+\n\
+RProp(other) -> new RProp\n\
+\n\
+Sets an MLP to perform discrimination based on *RProp: A Direct\n\
+Adaptive Method for Faster Backpropagation Learning: The RPROP\n\
+Algorithm, by Martin Riedmiller and Heinrich Braun on IEEE\n\
+International Conference on Neural Networks, pp. 586--591, 1993.*\n\
+\n\
+To create a new trainer, either pass the batch-size, cost functor,\n\
+machine and a biases-training flag or another trainer you'd like\n\
+the parameters copied from.\n\
+\n\
+.. note::\n\
+   \n\
+   RProp **is** a \"batch\" training algorithm. Do not try to set\n\
+   batch_size to a value which is too low.\n\
+\n\
+Keyword parameters:\n\
+\n\
+batch_size, int\n\
+   The size of each batch used for the forward and backward steps.\n\
+   If you set this to ``1``, then you are implementing stochastic\n\
+   training.\n\
+   \n\
+   .. note::\n\
+   \n\
+      This setting affects the convergence.\n\
+\n\
+cost, :py:class:`xbob.learn.mlp.Cost`\n\
+   An object that can calculate the cost at every iteration.\n\
+\n\
+machine, :py:class:`xbob.learn.mlp.Machine`\n\
+   This parameter that will be used as a basis for this trainer's\n\
+   internal properties (cache sizes, for instance).\n\
+\n\
+train_biases, bool\n\
+   A boolean indicating if we should train the biases weights (set\n\
+   it to ``True``) or not (set it to ``False``).\n\
+\n\
+other, :py:class:`xbob.learn.mlp.Trainer`\n\
+   Another trainer from which this new copy will get its properties\n\
+   from. If you use this constructor than a new (deep) copy of the\n\
+   trainer is created.\n\
+\n\
+");
+
+static int PyBobLearnMLPRProp_init_discrete
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {
+    "batch_size",
+    "cost",
+    "machine",
+    "train_biases",
+    0
+  };
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  Py_ssize_t batch_size = 0;
+  PyBobLearnCostObject* cost = 0;
+  PyBobLearnMLPMachineObject* machine = 0;
+  PyObject* train_biases = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "nO!|O!O", kwlist,
+        &batch_size,
+        &PyBobLearnCost_Type, &cost,
+        &PyBobLearnMLPMachine_Type, &machine,
+        &train_biases)) return -1;
+
+  try {
+    if (machine && train_biases) {
+      self->cxx = new bob::learn::mlp::RProp(batch_size, cost->cxx,
+          *machine->cxx, PyObject_IsTrue(train_biases));
+    }
+    else if (machine) {
+      self->cxx = new bob::learn::mlp::RProp(batch_size, cost->cxx,
+          *machine->cxx);
+    }
+    else if (train_biases) {
+      PyErr_Format(PyExc_RuntimeError, "cannot provide a flag for `train_biases' and do not provide a `machine' upon initialisation of type `%s'", Py_TYPE(self)->tp_name);
+      return -1;
+    }
+    else {
+      self->cxx = new bob::learn::mlp::RProp(batch_size, cost->cxx);
+    }
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot create new object of type `%s' - unknown exception thrown", Py_TYPE(self)->tp_name);
+    return -1;
+  }
+
+  self->parent.cxx = self->cxx;
+
+  return 0;
+
+}
+
+static int PyBobLearnMLPRProp_init_copy
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"other", 0};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyObject* other = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!", kwlist,
+        &PyBobLearnMLPRProp_Type, &other)) return -1;
+
+  auto copy = reinterpret_cast<PyBobLearnMLPRPropObject*>(other);
+
+  try {
+    self->cxx = new bob::learn::mlp::RProp(*(copy->cxx));
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot create new object of type `%s' - unknown exception thrown", Py_TYPE(self)->tp_name);
+    return -1;
+  }
+
+  self->parent.cxx = self->cxx;
+
+  return 0;
+
+}
+
+static int PyBobLearnMLPRProp_init(PyBobLearnMLPRPropObject* self,
+    PyObject* args, PyObject* kwds) {
+
+  Py_ssize_t nargs = (args?PyTuple_Size(args):0) + (kwds?PyDict_Size(kwds):0);
+
+  switch (nargs) {
+
+    case 1:
+
+      return PyBobLearnMLPRProp_init_copy(self, args, kwds);
+
+    default:
+
+      return PyBobLearnMLPRProp_init_discrete(self, args, kwds);
+  }
+
+  return -1;
+
+}
+
+static void PyBobLearnMLPRProp_delete
+(PyBobLearnMLPRPropObject* self) {
+
+  self->parent.cxx = 0;
+  delete self->cxx;
+  Py_TYPE(self)->tp_free((PyObject*)self);
+
+}
+
+int PyBobLearnMLPRProp_Check(PyObject* o) {
+  return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobLearnMLPRProp_Type));
+}
+
+static PyObject* PyBobLearnMLPRProp_new
+(PyTypeObject* type, PyObject*, PyObject*) {
+
+  /* Allocates the python object itself */
+  PyBobLearnMLPRPropObject* self = (PyBobLearnMLPRPropObject*)type->tp_alloc(type, 0);
+
+  self->cxx = 0;
+  self->parent.cxx = 0;
+
+  return reinterpret_cast<PyObject*>(self);
+
+}
+
+PyDoc_STRVAR(s_eta_minus_str, "eta_minus");
+PyDoc_STRVAR(s_eta_minus_doc,
+"Learning de-enforcement parameter (defaults to ``0.5``)"
+);
+
+static PyObject* PyBobLearnMLPRProp_getEtaMinus
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return Py_BuildValue("d", self->cxx->getEtaMinus());
+}
+
+static int PyBobLearnMLPRProp_setEtaMinus
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  double value = PyFloat_AsDouble(o);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setEtaMinus(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_eta_plus_str, "eta_plus");
+PyDoc_STRVAR(s_eta_plus_doc,
+"Learning enforcement parameter (defaults to ``1.2``)"
+);
+
+static PyObject* PyBobLearnMLPRProp_getEtaPlus
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return Py_BuildValue("d", self->cxx->getEtaPlus());
+}
+
+static int PyBobLearnMLPRProp_setEtaPlus
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  double value = PyFloat_AsDouble(o);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setEtaPlus(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_delta_zero_str, "delta_zero");
+PyDoc_STRVAR(s_delta_zero_doc,
+"Initial weight update (defaults to ``0.1``)"
+);
+
+static PyObject* PyBobLearnMLPRProp_getDeltaZero
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return Py_BuildValue("d", self->cxx->getDeltaZero());
+}
+
+static int PyBobLearnMLPRProp_setDeltaZero
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  double value = PyFloat_AsDouble(o);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setDeltaZero(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_delta_min_str, "delta_min");
+PyDoc_STRVAR(s_delta_min_doc,
+"Minimal weight update (defaults to :math:`10^{-6}`)"
+);
+
+static PyObject* PyBobLearnMLPRProp_getDeltaMin
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return Py_BuildValue("d", self->cxx->getDeltaMin());
+}
+
+static int PyBobLearnMLPRProp_setDeltaMin
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  double value = PyFloat_AsDouble(o);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setDeltaMin(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_delta_max_str, "delta_max");
+PyDoc_STRVAR(s_delta_max_doc,
+"Maximal weight update (defaults to ``50.0``)"
+);
+
+static PyObject* PyBobLearnMLPRProp_getDeltaMax
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return Py_BuildValue("d", self->cxx->getDeltaMax());
+}
+
+static int PyBobLearnMLPRProp_setDeltaMax
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  double value = PyFloat_AsDouble(o);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setDeltaMax(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_previous_derivatives_str, "previous_derivatives");
+PyDoc_STRVAR(s_previous_derivatives_doc,
+"The derivatives of the cost w.r.t. to the specific\n\
+**weights** of the network, from the previous training step.\n\
+The derivatives are arranged to match the organization\n\
+of weights of the machine being trained.");
+
+static PyObject* PyBobLearnMLPRProp_getPreviousDerivatives
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return convert_vector<2>(self->cxx->getPreviousDerivatives());
+}
+
+static int PyBobLearnMLPRProp_setPreviousDerivatives
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,2>> bzvec;
+  int retval = convert_tuple<2>((PyObject*)self, s_previous_derivatives_str,
+      o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setPreviousDerivatives(bzvec);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot reset `%s' of %s: unknown exception caught", Py_TYPE(self)->tp_name, s_previous_derivatives_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_previous_bias_derivatives_str, "previous_bias_derivatives");
+PyDoc_STRVAR(s_previous_bias_derivatives_doc,
+"The derivatives of the cost w.r.t. to the specific\n\
+**biases** of the network, from the previous training step.\n\
+The derivatives are arranged to match the organization\n\
+of weights of the machine being trained.");
+
+static PyObject* PyBobLearnMLPRProp_getPreviousBiasDerivatives
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return convert_vector<1>(self->cxx->getPreviousBiasDerivatives());
+}
+
+static int PyBobLearnMLPRProp_setPreviousBiasDerivatives
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,1>> bzvec;
+  int retval = convert_tuple<1>((PyObject*)self,
+      s_previous_bias_derivatives_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setPreviousBiasDerivatives(bzvec);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot reset `%s' of %s: unknown exception caught", Py_TYPE(self)->tp_name, s_previous_bias_derivatives_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_deltas_str, "deltas");
+PyDoc_STRVAR(s_deltas_doc,
+"Current settings for the weight update (:math:`\\Delta_{ij}(t)`)");
+
+static PyObject* PyBobLearnMLPRProp_getDeltas
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return convert_vector<2>(self->cxx->getDeltas());
+}
+
+static int PyBobLearnMLPRProp_setDeltas
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,2>> bzvec;
+  int retval = convert_tuple<2>((PyObject*)self, s_deltas_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setDeltas(bzvec);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot reset `%s' of %s: unknown exception caught", Py_TYPE(self)->tp_name, s_deltas_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_bias_deltas_str, "bias_deltas");
+PyDoc_STRVAR(s_bias_deltas_doc,
+"Current settings for the bias update (:math:`\\Delta_{ij}(t)`)");
+
+static PyObject* PyBobLearnMLPRProp_getBiasDeltas
+(PyBobLearnMLPRPropObject* self, void* /*closure*/) {
+  return convert_vector<1>(self->cxx->getBiasDeltas());
+}
+
+static int PyBobLearnMLPRProp_setBiasDeltas
+(PyBobLearnMLPRPropObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,1>> bzvec;
+  int retval = convert_tuple<1>((PyObject*)self, s_bias_deltas_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setBiasDeltas(bzvec);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return -1;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot reset `%s' of %s: unknown exception caught", Py_TYPE(self)->tp_name, s_bias_deltas_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+static PyGetSetDef PyBobLearnMLPRProp_getseters[] = {
+    {
+      s_eta_minus_str,
+      (getter)PyBobLearnMLPRProp_getEtaMinus,
+      (setter)PyBobLearnMLPRProp_setEtaMinus,
+      s_eta_minus_doc,
+      0
+    },
+    {
+      s_eta_plus_str,
+      (getter)PyBobLearnMLPRProp_getEtaPlus,
+      (setter)PyBobLearnMLPRProp_setEtaPlus,
+      s_eta_plus_doc,
+      0
+    },
+    {
+      s_delta_zero_str,
+      (getter)PyBobLearnMLPRProp_getDeltaZero,
+      (setter)PyBobLearnMLPRProp_setDeltaZero,
+      s_delta_zero_doc,
+      0
+    },
+    {
+      s_delta_min_str,
+      (getter)PyBobLearnMLPRProp_getDeltaMin,
+      (setter)PyBobLearnMLPRProp_setDeltaMin,
+      s_delta_min_doc,
+      0
+    },
+    {
+      s_delta_max_str,
+      (getter)PyBobLearnMLPRProp_getDeltaMax,
+      (setter)PyBobLearnMLPRProp_setDeltaMax,
+      s_delta_max_doc,
+      0
+    },
+    {
+      s_previous_derivatives_str,
+      (getter)PyBobLearnMLPRProp_getPreviousDerivatives,
+      (setter)PyBobLearnMLPRProp_setPreviousDerivatives,
+      s_previous_derivatives_doc,
+      0
+    },
+    {
+      s_previous_bias_derivatives_str,
+      (getter)PyBobLearnMLPRProp_getPreviousBiasDerivatives,
+      (setter)PyBobLearnMLPRProp_setPreviousBiasDerivatives,
+      s_previous_bias_derivatives_doc,
+      0
+    },
+    {
+      s_deltas_str,
+      (getter)PyBobLearnMLPRProp_getDeltas,
+      (setter)PyBobLearnMLPRProp_setDeltas,
+      s_deltas_doc,
+      0
+    },
+    {
+      s_bias_deltas_str,
+      (getter)PyBobLearnMLPRProp_getBiasDeltas,
+      (setter)PyBobLearnMLPRProp_setBiasDeltas,
+      s_bias_deltas_doc,
+      0
+    },
+    {0}  /* Sentinel */
+};
+
+PyDoc_STRVAR(s_reset_str, "reset");
+PyDoc_STRVAR(s_reset_doc,
+"Re-initializes the whole training apparatus to start training\n\
+a new machine. This will effectively reset previous derivatives\n\
+to zero.");
+
+static PyObject* PyBobLearnMLPRProp_reset (PyBobLearnMLPRPropObject* self) {
+
+  self->cxx->reset();
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_train_str, "train");
+PyDoc_STRVAR(s_train_doc,
+"o.train(machine, input, target) -> None\n\
+\n\
+Trains the MLP to perform discrimination using RProp\n\
+\n\
+Resilient Back-propagation (R-Prop) is an efficient algorithm for\n\
+gradient descent with local adpatation of the weight updates, which\n\
+adapts to the behaviour of the chosen error function.\n\
+\n\
+Concretely, this executes the following update rule for the weights\n\
+(and biases, optionally) and respective :math:`\\Delta`'s (the\n\
+current weight updates):\n\
+\n\
+.. math::\n\
+   \n\
+   \\Delta_{ij}(t) &= \\left\\{\n\
+     \\begin{array}{l l}\n\
+     \\text{min}(\\eta^+\\cdot\\Delta_{ij}(t-1), \\Delta_{\\text{max}}) & \\text{ if } \\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t-1)\\cdot\\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t) > 0\\\\\n\
+     \\max(\\eta^-\\cdot\\Delta_{ij}(t-1), \\Delta_{\\text{min}}) & \\text{ if } \\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t-1)\\cdot\\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t) < 0\\\\\n\
+     \\Delta_{ij}(t-1) & \\text{ otherwise}\n\
+     \\end{array}\n\
+   \\right. \\\\\n\
+   \\Delta_{ij}w(t) &= \\left\\{\n\
+     \\begin{array}{l l}\n\
+     -\\Delta_{ij}(t) & \\text{ if } \\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t) > 0\\\\\n\
+     +\\Delta_{ij}(t) & \\text{ if } \\sum_{i=1}^{N}\\frac{\\partial J(x_i; \\theta)}{\\partial \\theta_j}(t) < 0\\\\\n\
+     0 & \\text{ otherwise}\n\
+     \\end{array}\n\
+   \\right. \\\\\n\
+   w_{ij}(t+1) &= w_{ij}(t) + \\Delta_{ij}(t)\n\
+\n\
+The following parameters are set *by default* and suggested by the article:\n\
+\n\
+.. math::\n\
+   \n\
+   0 < \\eta^- &< 1 < \\eta^+\\\\\n\
+   \\eta^- &= 0.5\\\\\n\
+   \\eta^+ &= 1.2\\\\\n\
+   \\Delta_{0} &= 0.1\\\\\n\
+   \\Delta_{\\text{min}} &= 10^{-6}\\\\\n\
+   \\Delta_{\\text{max}} &= 50.0\n\
+\n\
+The training is executed outside the machine context, but uses all the\n\
+current machine layout. The given machine is updated with new weights\n\
+and biases at the end of the training that is performed a single time.\n\
+Iterate as much as you want to refine the training.\n\
+\n\
+The machine given as input is checked for compatibility with the\n\
+current initialized settings. If the two are not compatible, an\n\
+exception is thrown.\n\
+\n\
+.. note::\n\
+\n\
+   In RProp, training is done in batches. You should set the batch\n\
+   size adequately at class initialization or use setBatchSize().\n\
+\n\
+.. note::\n\
+\n\
+   The machine is not initialized randomly at each call to this\n\
+   method. It is your task to call\n\
+   :py:meth:`xbob.learn.mlp.Machine.randomize` once at the machine\n\
+   you want to train and then call this method as many times as you\n\
+   think are necessary. This design allows for a training criteria\n\
+   to be encoded outside the scope of this trainer and to this type\n\
+   to focus only on applying the training when requested to.\n\
+\n\
+Keyword arguments:\n\
+\n\
+machine, :py:class:`xbob.learn.mlp.Machine`\n\
+   The machine that will be trained. You must have called\n\
+   :py:meth:`xbob.learn.mlp.Trainer.initialize` which a similarly\n\
+   configured machine before being able to call this method, or an\n\
+   exception may be thrown.\n\
+\n\
+input, array-like, 2D with ``float64`` as data type\n\
+   A 2D :py:class:`numpy.ndarray` with 64-bit floats containing the\n\
+   input data for the MLP to which this training step will be based\n\
+   on. The matrix should be organized so each input (example) lies on\n\
+   a single row of ``input``.\n\
+\n\
+target, array-like, 2D with ``float64`` as data type\n\
+   A 2D :py:class:`numpy.ndarray` with 64-bit floats containing the\n\
+   target data for the MLP to which this training step will be based\n\
+   on. The matrix should be organized so each target lies on a single\n\
+   row of ``target``, matching each input example in ``input``.\n\
+\n\
+");
+
+static PyObject* PyBobLearnMLPRProp_train
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"machine", "input", "target", 0};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBobLearnMLPMachineObject* machine = 0;
+  PyBlitzArrayObject* input = 0;
+  PyBlitzArrayObject* target = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O&O&", kwlist,
+        &PyBobLearnMLPMachine_Type, &machine,
+        &PyBlitzArray_Converter, &input,
+        &PyBlitzArray_Converter, &target)) return 0;
+
+  if (input->type_num != NPY_FLOAT64 || input->ndim != 2) {
+    PyErr_Format(PyExc_TypeError, "`%s' only supports 2D 64-bit float arrays for input array `input'", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  if (target->type_num != NPY_FLOAT64 || target->ndim != 2) {
+    PyErr_Format(PyExc_TypeError, "`%s' only supports 2D 64-bit float arrays for input array `target'", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  try {
+    self->cxx->train(*machine->cxx,
+        *PyBlitzArrayCxx_AsBlitz<double,2>(input),
+        *PyBlitzArrayCxx_AsBlitz<double,2>(target)
+        );
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot perform training-step for `%s': unknown exception caught", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_set_previous_derivative_str, "set_previous_derivative");
+PyDoc_STRVAR(s_set_previous_derivative_doc,
+    "Sets the previous cost derivative for a given weight layer (index).");
+
+static PyObject* PyBobLearnMLPRProp_setPreviousDerivativeOnLayer
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer", 0};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBlitzArrayObject* array = 0;
+  Py_ssize_t layer = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&n", kwlist,
+        &PyBlitzArray_Converter, &array, &layer)) return 0;
+
+  if (array->type_num != NPY_FLOAT64 || array->ndim != 2) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 2D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_previous_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setPreviousDerivative(*PyBlitzArrayCxx_AsBlitz<double,2>(array),
+        layer);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot set previous derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_set_previous_bias_derivative_str, "set_previous_bias_derivative");
+PyDoc_STRVAR(s_set_previous_bias_derivative_doc,
+    "Sets the cost bias derivative for a given bias layer (index).");
+
+static PyObject* PyBobLearnMLPRProp_setPreviousBiasDerivativeOnLayer
+(PyBobLearnMLPRPropObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer", 0};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBlitzArrayObject* array = 0;
+  Py_ssize_t layer = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&n", kwlist,
+        &PyBlitzArray_Converter, &array, &layer)) return 0;
+
+  if (array->type_num != NPY_FLOAT64 || array->ndim != 1) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 1D 64-bit float arrays for argument `array' (or any other object coercible to that), but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions and with type `%s' which is not compatible - check your input", Py_TYPE(self)->tp_name, s_set_previous_bias_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setPreviousBiasDerivative(*PyBlitzArrayCxx_AsBlitz<double,1>(array), layer);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot set previous bias derivative at layer %" PY_FORMAT_SIZE_T "d for `%s': unknown exception caught", layer, Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+static PyMethodDef PyBobLearnMLPRProp_methods[] = {
+  {
+    s_reset_str,
+    (PyCFunction)PyBobLearnMLPRProp_reset,
+    METH_NOARGS,
+    s_reset_doc,
+  },
+  {
+    s_train_str,
+    (PyCFunction)PyBobLearnMLPRProp_train,
+    METH_VARARGS|METH_KEYWORDS,
+    s_train_doc,
+  },
+  {
+    s_set_previous_derivative_str,
+    (PyCFunction)PyBobLearnMLPRProp_setPreviousDerivativeOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_previous_derivative_doc,
+  },
+  {
+    s_set_previous_bias_derivative_str,
+    (PyCFunction)PyBobLearnMLPRProp_setPreviousBiasDerivativeOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_previous_bias_derivative_doc,
+  },
+  {0} /* Sentinel */
+};
+
+PyTypeObject PyBobLearnMLPRProp_Type = {
+    PyVarObject_HEAD_INIT(0, 0)
+    s_trainer_str,                            /* tp_name */
+    sizeof(PyBobLearnMLPRPropObject),         /* tp_basicsize */
+    0,                                        /* tp_itemsize */
+    (destructor)PyBobLearnMLPRProp_delete,    /* tp_dealloc */
+    0,                                        /* tp_print */
+    0,                                        /* tp_getattr */
+    0,                                        /* tp_setattr */
+    0,                                        /* tp_compare */
+    0, //(reprfunc)PyBobLearnMLPRProp_Repr,   /* tp_repr */
+    0,                                        /* tp_as_number */
+    0,                                        /* tp_as_sequence */
+    0,                                        /* tp_as_mapping */
+    0,                                        /* tp_hash */
+    0,                                        /* tp_call */
+    0, //(reprfunc)PyBobLearnMLPRProp_Repr,   /* tp_str */
+    0,                                        /* tp_getattro */
+    0,                                        /* tp_setattro */
+    0,                                        /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+    s_trainer_doc,                            /* tp_doc */
+    0,                                        /* tp_traverse */
+    0,                                        /* tp_clear */
+    0,                                        /* tp_richcompare */
+    0,                                        /* tp_weaklistoffset */
+    0,                                        /* tp_iter */
+    0,                                        /* tp_iternext */
+    PyBobLearnMLPRProp_methods,               /* tp_methods */
+    0,                                        /* tp_members */
+    PyBobLearnMLPRProp_getseters,             /* tp_getset */
+    0,                                        /* tp_base */
+    0,                                        /* tp_dict */
+    0,                                        /* tp_descr_get */
+    0,                                        /* tp_descr_set */
+    0,                                        /* tp_dictoffset */
+    (initproc)PyBobLearnMLPRProp_init,        /* tp_init */
+    0,                                        /* tp_alloc */
+    PyBobLearnMLPRProp_new,                   /* tp_new */
+};