From 128dbd510c042737b246111fccf2aa5d97b8268f Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Fri, 9 May 2014 14:34:24 +0200
Subject: [PATCH] Full implementation of the base Trainer class

---
 MANIFEST.in                                 |   1 -
 xbob/learn/mlp/cost.cpp                     |  25 +-
 xbob/learn/mlp/include/xbob.learn.mlp/api.h |  16 +-
 xbob/learn/mlp/machine.cpp                  |  11 -
 xbob/learn/mlp/main.cpp                     |   4 +-
 xbob/learn/mlp/test_backprop.py             |  58 +-
 xbob/learn/mlp/test_rprop.py                |  60 +-
 xbob/learn/mlp/trainer.cpp                  | 784 +++++++++++++++++++-
 8 files changed, 872 insertions(+), 87 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index c18b3a3..09ed020 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,3 @@
 include LICENSE README.rst bootstrap.py buildout.cfg
 recursive-include doc conf.py *.rst
 recursive-include xbob *.cpp *.h
-recursive-include xbob/learn/mlp/data *.*
diff --git a/xbob/learn/mlp/cost.cpp b/xbob/learn/mlp/cost.cpp
index fb20033..5172316 100644
--- a/xbob/learn/mlp/cost.cpp
+++ b/xbob/learn/mlp/cost.cpp
@@ -441,6 +441,27 @@ static PyMethodDef PyBobLearnCost_methods[] = {
   {0} /* Sentinel */
 };
 
+static PyObject* PyBobLearnCost_new (PyTypeObject* type, PyObject*, PyObject*) {
+
+  /* Allocates the python object itself */
+  PyBobLearnCostObject* self = (PyBobLearnCostObject*)type->tp_alloc(type, 0);
+
+  self->cxx.reset();
+
+  return reinterpret_cast<PyObject*>(self);
+
+}
+
+PyObject* PyBobLearnCost_NewFromCost (boost::shared_ptr<bob::learn::mlp::Cost> p) {
+
+  PyBobLearnCostObject* retval = (PyBobLearnCostObject*)PyBobLearnCost_new(&PyBobLearnCost_Type, 0, 0);
+
+  retval->cxx = p;
+
+  return reinterpret_cast<PyObject*>(retval);
+
+}
+
 PyTypeObject PyBobLearnCost_Type = {
     PyVarObject_HEAD_INIT(0, 0)
     s_cost_str,                               /* tp_name */
@@ -477,7 +498,9 @@ PyTypeObject PyBobLearnCost_Type = {
     0,                                        /* tp_descr_get */
     0,                                        /* tp_descr_set */
     0,                                        /* tp_dictoffset */
-    (initproc)PyBobLearnCost_init             /* tp_init */
+    (initproc)PyBobLearnCost_init,            /* tp_init */
+    0,                                        /* tp_alloc */
+    PyBobLearnCost_new,                       /* tp_new */
 };
 
 PyDoc_STRVAR(s_squareerror_str, XBOB_EXT_MODULE_PREFIX ".SquareError");
diff --git a/xbob/learn/mlp/include/xbob.learn.mlp/api.h b/xbob/learn/mlp/include/xbob.learn.mlp/api.h
index d6ac3a8..41aa518 100644
--- a/xbob/learn/mlp/include/xbob.learn.mlp/api.h
+++ b/xbob/learn/mlp/include/xbob.learn.mlp/api.h
@@ -30,10 +30,10 @@ enum _PyBobLearnMLP_ENUM {
   // Bindings for xbob.learn.mlp.Machine
   PyBobLearnMLPMachine_Type_NUM,
   PyBobLearnMLPMachine_Check_NUM,
-  PyBobLearnMLPMachine_NewFromSize_NUM,
   // Bindings for xbob.learn.mlp.Cost and variants
   PyBobLearnCost_Type_NUM,
   PyBobLearnCost_Check_NUM,
+  PyBobLearnCost_NewFromCost_NUM,
   PyBobLearnSquareError_Type_NUM,
   PyBobLearnCrossEntropyLoss_Type_NUM,
   // Bindings for xbob.learn.mlp.DataShuffler
@@ -66,9 +66,6 @@ typedef struct {
 #define PyBobLearnMLPMachine_Check_RET int
 #define PyBobLearnMLPMachine_Check_PROTO (PyObject* o)
 
-#define PyBobLearnMLPMachine_NewFromSize_RET PyObject*
-#define PyBobLearnMLPMachine_NewFromSize_PROTO (Py_ssize_t i, Py_ssize_t o)
-
 typedef struct {
   PyObject_HEAD
   boost::shared_ptr<bob::learn::mlp::Cost> cxx;
@@ -79,6 +76,9 @@ typedef struct {
 #define PyBobLearnCost_Check_RET int
 #define PyBobLearnCost_Check_PROTO (PyObject* o)
 
+#define PyBobLearnCost_NewFromCost_RET PyObject*
+#define PyBobLearnCost_NewFromCost_PROTO (boost::shared_ptr<bob::learn::mlp::Cost>)
+
 typedef struct {
   PyBobLearnCostObject parent;
   boost::shared_ptr<bob::learn::mlp::SquareError> cxx;
@@ -135,8 +135,6 @@ typedef struct {
 
   PyBobLearnMLPMachine_Check_RET PyBobLearnMLPMachine_Check PyBobLearnMLPMachine_Check_PROTO;
 
-  PyBobLearnMLPMachine_NewFromSize_RET PyBobLearnMLPMachine_NewFromSize PyBobLearnMLPMachine_NewFromSize_PROTO;
-
   /************************************
    * Bindings for xbob.learn.mlp.Cost *
    ************************************/
@@ -149,6 +147,8 @@ typedef struct {
 
   extern PyBobLearnCrossEntropyLoss_Type_TYPE PyBobLearnCrossEntropyLoss_Type;
 
+  PyBobLearnCost_NewFromCost_RET PyBobLearnCost_NewFromCost PyBobLearnCost_NewFromCost_PROTO;
+
   /********************************************
    * Bindings for xbob.learn.mlp.DataShuffler *
    ********************************************/
@@ -205,8 +205,6 @@ typedef struct {
 
 # define PyBobLearnMLPMachine_Check (*(PyBobLearnMLPMachine_Check_RET (*)PyBobLearnMLPMachine_Check_PROTO) PyXbobLearnMLP_API[PyBobLearnMLPMachine_Check_NUM])
 
-# define PyBobLearnMLPMachine_NewFromSize (*(PyBobLearnMLPMachine_NewFromSize_RET (*)PyBobLearnMLPMachine_NewFromSize_PROTO) PyXbobLearnMLP_API[PyBobLearnMLPMachine_NewFromSize_NUM])
-
   /************************************
    * Bindings for xbob.learn.mlp.Cost *
    ************************************/
@@ -215,6 +213,8 @@ typedef struct {
 
 # define PyBobLearnCost_Check (*(PyBobLearnCost_Check_RET (*)PyBobLearnCost_Check_PROTO) PyXbobLearnMLP_API[PyBobLearnCost_Check_NUM])
 
+# define PyBobLearnCost_NewFromCost (*(PyBobLearnCost_NewFromCost_RET (*)PyBobLearnCost_NewFromCost_PROTO) PyXbobLearnMLP_API[PyBobLearnCost_NewFromCost_NUM])
+
 # define PyBobLearnSquareError_Type (*(PyBobLearnSquareError_Type_TYPE *)PyXbobLearnMLP_API[PyBobLearnSquareError_Type_NUM])
 
 # define PyBobLearnCrossEntropyLoss_Type (*(PyBobLearnCrossEntropyLoss_Type_TYPE *)PyXbobLearnMLP_API[PyBobLearnCrossEntropyLoss_Type_NUM])
diff --git a/xbob/learn/mlp/machine.cpp b/xbob/learn/mlp/machine.cpp
index d2eb4f5..d4ab00d 100644
--- a/xbob/learn/mlp/machine.cpp
+++ b/xbob/learn/mlp/machine.cpp
@@ -1036,17 +1036,6 @@ static PyObject* PyBobLearnMLPMachine_new
 
 }
 
-PyObject* PyBobLearnMLPMachine_NewFromSize
-(Py_ssize_t input, Py_ssize_t output) {
-
-  PyBobLearnMLPMachineObject* retval = (PyBobLearnMLPMachineObject*)PyBobLearnMLPMachine_new(&PyBobLearnMLPMachine_Type, 0, 0);
-
-  retval->cxx = new bob::learn::mlp::Machine(input, output);
-
-  return reinterpret_cast<PyObject*>(retval);
-
-}
-
 PyTypeObject PyBobLearnMLPMachine_Type = {
     PyVarObject_HEAD_INIT(0, 0)
     s_machine_str,                                 /* tp_name */
diff --git a/xbob/learn/mlp/main.cpp b/xbob/learn/mlp/main.cpp
index da1b2ad..bca1be8 100644
--- a/xbob/learn/mlp/main.cpp
+++ b/xbob/learn/mlp/main.cpp
@@ -105,8 +105,6 @@ static PyObject* create_module (void) {
 
   PyXbobLearnMLP_API[PyBobLearnMLPMachine_Check_NUM] = (void *)&PyBobLearnMLPMachine_Check;
 
-  PyXbobLearnMLP_API[PyBobLearnMLPMachine_NewFromSize_NUM] = (void *)&PyBobLearnMLPMachine_NewFromSize;
-
   /************************************
    * Bindings for xbob.learn.mlp.Cost *
    ************************************/
@@ -115,6 +113,8 @@ static PyObject* create_module (void) {
 
   PyXbobLearnMLP_API[PyBobLearnCost_Check_NUM] = (void *)&PyBobLearnCost_Check;
 
+  PyXbobLearnMLP_API[PyBobLearnCost_NewFromCost_NUM] = (void *)&PyBobLearnCost_NewFromCost;
+
   PyXbobLearnMLP_API[PyBobLearnSquareError_Type_NUM] = (void *)&PyBobLearnSquareError_Type;
 
   PyXbobLearnMLP_API[PyBobLearnCrossEntropyLoss_Type_NUM] = (void *)&PyBobLearnCrossEntropyLoss_Type;
diff --git a/xbob/learn/mlp/test_backprop.py b/xbob/learn/mlp/test_backprop.py
index 3673e12..d801036 100644
--- a/xbob/learn/mlp/test_backprop.py
+++ b/xbob/learn/mlp/test_backprop.py
@@ -9,17 +9,17 @@
 """
 
 import numpy
+from xbob.learn.activation import HyperbolicTangent, Logistic, Identity
 
-from .. import MLPBaseTrainer, MLPBackPropTrainer, CrossEntropyLoss, SquareError
-from ...machine import HyperbolicTangentActivation, LogisticActivation, IdentityActivation, MLP
+from . import Machine, Trainer, CrossEntropyLoss, SquareError, BackProp
 
-class PythonBackProp(MLPBaseTrainer):
+class PythonBackProp(Trainer):
   """A simple version of the vanilla BackProp algorithm written in Python
   """
 
-  def __init__(self, batch_size, cost, machine, train_biases, 
+  def __init__(self, batch_size, cost, machine, train_biases,
       learning_rate=0.1, momentum=0.0):
-    
+
     super(PythonBackProp, self).__init__(batch_size, cost, machine, train_biases)
     self.previous_derivatives = [numpy.zeros(k.shape, dtype=float) for k in machine.weights]
     self.previous_bias_derivatives = [numpy.zeros(k.shape, dtype=float) for k in machine.biases]
@@ -52,8 +52,8 @@ class PythonBackProp(MLPBaseTrainer):
 def check_training(machine, cost, bias_training, batch_size, learning_rate,
     momentum):
 
-  python_machine = MLP(machine)
-  
+  python_machine = Machine(machine)
+
   X = numpy.random.rand(batch_size, machine.weights[0].shape[0])
   T = numpy.zeros((batch_size, machine.weights[-1].shape[1]))
 
@@ -92,11 +92,11 @@ def check_training(machine, cost, bias_training, batch_size, learning_rate,
   assert numpy.alltrue(machine.input_divide == python_machine.input_divide)
 
 def test_2in_1out_nobias():
-  
-  machine = MLP((2, 1))
+
+  machine = Machine((2, 1))
   machine.randomize()
-  machine.hidden_activation = LogisticActivation()
-  machine.output_activation = LogisticActivation()
+  machine.hidden_activation = Logistic()
+  machine.output_activation = Logistic()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -107,10 +107,10 @@ def test_2in_1out_nobias():
 
 def test_1in_2out_nobias():
 
-  machine = MLP((1, 2))
+  machine = Machine((1, 2))
   machine.randomize()
-  machine.hidden_activation = LogisticActivation()
-  machine.output_activation = LogisticActivation()
+  machine.hidden_activation = Logistic()
+  machine.output_activation = Logistic()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -121,10 +121,10 @@ def test_1in_2out_nobias():
 
 def test_2in_3_1out_nobias():
 
-  machine = MLP((2, 3, 1))
+  machine = Machine((2, 3, 1))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -135,10 +135,10 @@ def test_2in_3_1out_nobias():
 
 def test_100in_10_10_5out_nobias():
 
-  machine = MLP((100, 10, 10, 5))
+  machine = Machine((100, 10, 10, 5))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -149,10 +149,10 @@ def test_100in_10_10_5out_nobias():
 
 def test_2in_3_1out():
 
-  machine = MLP((2, 3, 1))
+  machine = Machine((2, 3, 1))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
@@ -162,10 +162,10 @@ def test_2in_3_1out():
 
 def test_20in_10_5_3out():
 
-  machine = MLP((20, 10, 5, 3))
+  machine = Machine((20, 10, 5, 3))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
@@ -175,10 +175,10 @@ def test_20in_10_5_3out():
 
 def test_20in_10_5_3out_with_momentum():
 
-  machine = MLP((20, 10, 5, 3))
+  machine = Machine((20, 10, 5, 3))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
diff --git a/xbob/learn/mlp/test_rprop.py b/xbob/learn/mlp/test_rprop.py
index 16aae8a..cb8a23e 100644
--- a/xbob/learn/mlp/test_rprop.py
+++ b/xbob/learn/mlp/test_rprop.py
@@ -3,15 +3,15 @@
 # Andre Anjos <andre.anjos@idiap.ch>
 # Thu Jul 14 18:53:07 2011 +0200
 #
-# Copyright (C) 2011-2013 Idiap Research Institute, Martigny, Switzerland
+# Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 
 """Tests for RProp MLP training.
 """
 
 import numpy
+from xbob.learn.activation import HyperbolicTangent, Logistic, Identity
 
-from .. import MLPBaseTrainer, MLPRPropTrainer, CrossEntropyLoss, SquareError
-from ...machine import HyperbolicTangentActivation, LogisticActivation, IdentityActivation, MLP
+from . import Machine, Trainer, CrossEntropyLoss, SquareError, RProp
 
 def sign(x):
   """A handy sign function"""
@@ -19,12 +19,12 @@ def sign(x):
   if (x < 0) : return -1
   return +1
 
-class PythonRProp(MLPBaseTrainer):
+class PythonRProp(Trainer):
   """A simple version of the R-Prop algorithm written in Python
   """
 
   def __init__(self, batch_size, cost, machine, train_biases):
-    
+
     super(PythonRProp, self).__init__(batch_size, cost, machine, train_biases)
 
     # some constants for RProp
@@ -94,13 +94,13 @@ class PythonRProp(MLPBaseTrainer):
 
 def check_training(machine, cost, bias_training, batch_size):
 
-  python_machine = MLP(machine)
-  
+  python_machine = Machine(machine)
+
   X = numpy.random.rand(batch_size, machine.weights[0].shape[0])
   T = numpy.zeros((batch_size, machine.weights[-1].shape[1]))
 
   python_trainer = PythonRProp(batch_size, cost, machine, bias_training)
-  cxx_trainer = MLPRPropTrainer(batch_size, cost, machine, bias_training)
+  cxx_trainer = RProp(batch_size, cost, machine, bias_training)
 
   # checks previous state matches
   for k,D in enumerate(cxx_trainer.deltas):
@@ -139,11 +139,11 @@ def check_training(machine, cost, bias_training, batch_size):
   assert numpy.alltrue(machine.input_divide == python_machine.input_divide)
 
 def test_2in_1out_nobias():
-  
-  machine = MLP((2, 1))
+
+  machine = Machine((2, 1))
   machine.randomize()
-  machine.hidden_activation = LogisticActivation()
-  machine.output_activation = LogisticActivation()
+  machine.hidden_activation = Logistic()
+  machine.output_activation = Logistic()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -154,10 +154,10 @@ def test_2in_1out_nobias():
 
 def test_1in_2out_nobias():
 
-  machine = MLP((1, 2))
+  machine = Machine((1, 2))
   machine.randomize()
-  machine.hidden_activation = LogisticActivation()
-  machine.output_activation = LogisticActivation()
+  machine.hidden_activation = Logistic()
+  machine.output_activation = Logistic()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -168,10 +168,10 @@ def test_1in_2out_nobias():
 
 def test_2in_3_1out_nobias():
 
-  machine = MLP((2, 3, 1))
+  machine = Machine((2, 3, 1))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -182,10 +182,10 @@ def test_2in_3_1out_nobias():
 
 def test_100in_10_10_5out_nobias():
 
-  machine = MLP((100, 10, 10, 5))
+  machine = Machine((100, 10, 10, 5))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
   machine.biases = 0
 
   BATCH_SIZE = 10
@@ -196,10 +196,10 @@ def test_100in_10_10_5out_nobias():
 
 def test_2in_3_1out():
 
-  machine = MLP((2, 3, 1))
+  machine = Machine((2, 3, 1))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
@@ -209,10 +209,10 @@ def test_2in_3_1out():
 
 def test_20in_10_5_3out():
 
-  machine = MLP((20, 10, 5, 3))
+  machine = Machine((20, 10, 5, 3))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
@@ -222,10 +222,10 @@ def test_20in_10_5_3out():
 
 def test_20in_10_5_3out_with_momentum():
 
-  machine = MLP((20, 10, 5, 3))
+  machine = Machine((20, 10, 5, 3))
   machine.randomize()
-  machine.hidden_activation = HyperbolicTangentActivation()
-  machine.output_activation = HyperbolicTangentActivation()
+  machine.hidden_activation = HyperbolicTangent()
+  machine.output_activation = HyperbolicTangent()
 
   BATCH_SIZE = 10
   cost = SquareError(machine.output_activation)
diff --git a/xbob/learn/mlp/trainer.cpp b/xbob/learn/mlp/trainer.cpp
index a3e2a4a..9a2ebbc 100644
--- a/xbob/learn/mlp/trainer.cpp
+++ b/xbob/learn/mlp/trainer.cpp
@@ -30,9 +30,37 @@ trainers and re-use the base infrastructured provided by this\n\
 module, such as the computation of partial derivatives (using\n\
 the :py:meth:`backward_step` method).\n\
 \n\
-To create a new trainer, either pass another trainer you'd like\n\
-the parameters copied from or pass the batch-size, cost functor,\n\
-machine and a biases-training flag.\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\
+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 PyBobLearnMLPTrainer_init_discrete
@@ -163,6 +191,752 @@ static PyObject* PyBobLearnMLPTrainer_new
 
 }
 
+PyDoc_STRVAR(s_batch_size_str, "batch_size");
+PyDoc_STRVAR(s_batch_size_doc,
+"How many examples should be fed each time through the network\n\
+for testing or training. This number reflects the internal sizes\n\
+of structures setup to accomodate the input and the output of\n\
+the network.");
+
+static PyObject* PyBobLearnMLPTrainer_getBatchSize
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return Py_BuildValue("n", self->cxx->getBatchSize());
+}
+
+static int PyBobLearnMLPTrainer_setBatchSize
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  Py_ssize_t value = PyNumber_AsSsize_t(o, PyExc_OverflowError);
+  if (PyErr_Occurred()) return -1;
+  self->cxx->setBatchSize(value);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_cost_object_str, "cost_object");
+PyDoc_STRVAR(s_cost_object_doc,
+"An object, derived from :py:class:`xbob.learn.mlp.Cost` (e.g.\n\
+:py:class:`xbob.learn.mlp.SquareError` or \n\
+:py:class:`bob.trainer.CrossEntropyLoss`), that is used to evaluate\n\
+the cost (a.k.a. *loss*) and the derivatives given the input, the\n\
+target and the MLP structure.");
+
+static PyObject* PyBobLearnMLPTrainer_getCost
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return PyBobLearnCost_NewFromCost(self->cxx->getCost());
+}
+
+static int PyBobLearnMLPTrainer_setCost
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  if (!PyBobLearnCost_Check(o)) {
+    PyErr_Format(PyExc_TypeError, "%s.cost requires an object of type `Cost' (or an inherited type), not `%s'", Py_TYPE(self)->tp_name, Py_TYPE(o)->tp_name);
+    return -1;
+  }
+
+  auto py = reinterpret_cast<PyBobLearnCostObject*>(o);
+  self->cxx->setCost(py->cxx);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_train_biases_str, "train_biases");
+PyDoc_STRVAR(s_train_biases_doc,
+"A flag, indicating if this trainer will adjust the biases\n\
+of the network");
+
+static PyObject* PyBobLearnMLPTrainer_getTrainBiases
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  if (self->cxx->getTrainBiases()) Py_RETURN_TRUE;
+  Py_RETURN_FALSE;
+}
+
+static int PyBobLearnMLPTrainer_setTrainBiases
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+  self->cxx->setTrainBiases(PyObject_IsTrue(o));
+  return -1;
+}
+
+template <int N>
+PyObject* convert_vector(const std::vector<blitz::Array<double,N>>& v) {
+  PyObject* retval = PyTuple_New(v.size());
+  auto retval_ = make_safe(retval);
+  if (!retval) return 0;
+  for (int k=0; k<v.size(); ++k) {
+    auto arr = PyBlitzArrayCxx_NewFromConstArray(v[k]);
+    if (!arr) return 0;
+    PyTuple_SET_ITEM(retval, k, arr);
+  }
+  Py_INCREF(retval);
+  return retval;
+}
+
+template <int N>
+int convert_tuple(PyBobLearnMLPTrainerObject* self, const char* attr,
+    PyObject* o, std::vector<blitz::Array<double,N>>& seq) {
+
+  if (!PyIter_Check(o) && !PySequence_Check(o)) {
+    PyErr_Format(PyExc_TypeError, "setting attribute `%s' of `%s' requires an iterable, but you passed `%s' which does not implement the iterator protocol", Py_TYPE(self)->tp_name, attr, Py_TYPE(o)->tp_name);
+    return -1;
+  }
+
+  /* Checks and converts all entries */
+  std::vector<boost::shared_ptr<PyBlitzArrayObject>> seq_;
+
+  PyObject* iterator = PyObject_GetIter(o);
+  if (!iterator) return -1;
+  auto iterator_ = make_safe(iterator);
+
+  while (PyObject* item = PyIter_Next(iterator)) {
+    auto item_ = make_safe(item);
+
+    PyBlitzArrayObject* bz = 0;
+
+    if (!PyBlitzArray_Converter(item, &bz)) {
+      PyErr_Format(PyExc_TypeError, "`%s' (while setting `%s') could not convert object of type `%s' at position %" PY_FORMAT_SIZE_T "d of input sequence into an array - check your input", Py_TYPE(self)->tp_name, attr, Py_TYPE(item)->tp_name, seq.size());
+      return -1;
+    }
+
+    if (bz->ndim != N || bz->type_num != NPY_FLOAT64) {
+      PyErr_Format(PyExc_TypeError, "`%s' only supports 2D 64-bit float arrays for attribute `%s' (or any other object coercible to that), but at position %" PY_FORMAT_SIZE_T "d I have found 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, attr, seq.size(), bz->ndim, PyBlitzArray_TypenumAsString(bz->type_num));
+      Py_DECREF(bz);
+      return -1;
+    }
+
+    seq_.push_back(make_safe(bz)); ///< prevents data deletion
+    seq.push_back(*PyBlitzArrayCxx_AsBlitz<double,N>(bz)); ///< only a view!
+  }
+
+  if (PyErr_Occurred()) return -1;
+
+  return 0;
+}
+
+PyDoc_STRVAR(s_error_str, "error");
+PyDoc_STRVAR(s_error_doc,
+"The error (a.k.a. :math:`\\delta`'s) back-propagated through the\n\
+network, given an input and a target.");
+
+static PyObject* PyBobLearnMLPTrainer_getError
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return convert_vector<2>(self->cxx->getError());
+}
+
+static int PyBobLearnMLPTrainer_setError
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,2>> bzvec;
+  int retval = convert_tuple<2>(self, s_error_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setError(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_error_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_output_str, "output");
+PyDoc_STRVAR(s_output_doc,
+"The outputs of each neuron in the network");
+
+static PyObject* PyBobLearnMLPTrainer_getOutput
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return convert_vector<2>(self->cxx->getOutput());
+}
+
+static int PyBobLearnMLPTrainer_setOutput
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,2>> bzvec;
+  int retval = convert_tuple<2>(self, s_output_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setOutput(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_output_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_derivatives_str, "derivatives");
+PyDoc_STRVAR(s_derivatives_doc,
+"The calculated derivatives of the cost w.r.t. to the specific\n\
+**weights** of the network, organized to match the organization\n\
+of weights of the machine being trained.");
+
+static PyObject* PyBobLearnMLPTrainer_getDerivatives
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return convert_vector<2>(self->cxx->getDerivatives());
+}
+
+static int PyBobLearnMLPTrainer_setDerivatives
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,2>> bzvec;
+  int retval = convert_tuple<2>(self, s_derivatives_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setDerivatives(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_derivatives_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_bias_derivatives_str, "bias_derivatives");
+PyDoc_STRVAR(s_bias_derivatives_doc,
+"The calculated derivatives of the cost w.r.t. to the specific\n\
+**biases** of the network, organized to match the organization\n\
+of biases of the machine being trained.");
+
+static PyObject* PyBobLearnMLPTrainer_getBiasDerivatives
+(PyBobLearnMLPTrainerObject* self, void* /*closure*/) {
+  return convert_vector<1>(self->cxx->getBiasDerivatives());
+}
+
+static int PyBobLearnMLPTrainer_setBiasDerivatives
+(PyBobLearnMLPTrainerObject* self, PyObject* o, void* /*closure*/) {
+
+  std::vector<blitz::Array<double,1>> bzvec;
+  int retval = convert_tuple<1>(self, s_bias_derivatives_str, o, bzvec);
+  if (retval < 0) return retval;
+
+  try {
+    self->cxx->setBiasDerivatives(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_derivatives_str);
+    return -1;
+  }
+
+  return 0;
+
+}
+
+static PyGetSetDef PyBobLearnMLPTrainer_getseters[] = {
+    {
+      s_batch_size_str,
+      (getter)PyBobLearnMLPTrainer_getBatchSize,
+      (setter)PyBobLearnMLPTrainer_setBatchSize,
+      s_batch_size_doc,
+      0
+    },
+    {
+      s_cost_object_str,
+      (getter)PyBobLearnMLPTrainer_getCost,
+      (setter)PyBobLearnMLPTrainer_setCost,
+      s_cost_object_doc,
+      0
+    },
+    {
+      s_train_biases_str,
+      (getter)PyBobLearnMLPTrainer_getTrainBiases,
+      (setter)PyBobLearnMLPTrainer_setTrainBiases,
+      s_train_biases_doc,
+      0
+    },
+    {
+      s_error_str,
+      (getter)PyBobLearnMLPTrainer_getError,
+      (setter)PyBobLearnMLPTrainer_setError,
+      s_error_doc,
+      0
+    },
+    {
+      s_output_str,
+      (getter)PyBobLearnMLPTrainer_getOutput,
+      (setter)PyBobLearnMLPTrainer_setOutput,
+      s_output_doc,
+      0
+    },
+    {
+      s_derivatives_str,
+      (getter)PyBobLearnMLPTrainer_getDerivatives,
+      (setter)PyBobLearnMLPTrainer_setDerivatives,
+      s_derivatives_doc,
+      0
+    },
+    {
+      s_bias_derivatives_str,
+      (getter)PyBobLearnMLPTrainer_getBiasDerivatives,
+      (setter)PyBobLearnMLPTrainer_setBiasDerivatives,
+      s_bias_derivatives_doc,
+      0
+    },
+    {0}  /* Sentinel */
+};
+
+PyDoc_STRVAR(s_is_compatible_str, "is_compatible");
+PyDoc_STRVAR(s_is_compatible_doc, "Checks if a given machine is compatible with inner settings");
+
+static PyObject* PyBobLearnMLPTrainer_isCompatible
+(PyBobLearnMLPTrainerObject* self, PyObject* o) {
+
+  if (!PyBobLearnMLPMachine_Check(o)) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s()' requires a `%s' as input, not `%s'",
+        Py_TYPE(self)->tp_name, s_is_compatible_str,
+        PyBobLearnMLPMachine_Type.tp_name, Py_TYPE(o)->tp_name);
+    return 0;
+  }
+
+  auto machine = reinterpret_cast<PyBobLearnMLPMachineObject*>(o);
+
+  if (self->cxx->isCompatible(*machine->cxx)) Py_RETURN_TRUE;
+  Py_RETURN_FALSE;
+
+}
+
+PyDoc_STRVAR(s_initialize_str, "initialize");
+PyDoc_STRVAR(s_initialize_doc, "Initialize the trainer with the given machine");
+
+static PyObject* PyBobLearnMLPTrainer_initialize
+(PyBobLearnMLPTrainerObject* self, PyObject* o) {
+
+  if (!PyBobLearnMLPMachine_Check(o)) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s()' requires a `%s' as input, not `%s'",
+        Py_TYPE(self)->tp_name, s_initialize_str,
+        PyBobLearnMLPMachine_Type.tp_name, Py_TYPE(o)->tp_name);
+    return 0;
+  }
+
+  auto machine = reinterpret_cast<PyBobLearnMLPMachineObject*>(o);
+
+  try {
+    self->cxx->initialize(*machine->cxx);
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot initialize `%s': unknown exception caught", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_forward_step_str, "forward_step");
+PyDoc_STRVAR(s_forward_step_doc, "Forwards a batch of data through the MLP and updates the internal buffers.");
+
+static PyObject* PyBobLearnMLPTrainer_forwardStep
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"machine", "input"};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBobLearnMLPMachineObject* machine = 0;
+  PyBlitzArrayObject* input = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O&", kwlist,
+        &PyBobLearnMLPMachine_Type, &machine,
+        &PyBlitzArray_Converter, &input)) 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;
+  }
+
+  try {
+    self->cxx->forward_step(*machine->cxx, *PyBlitzArrayCxx_AsBlitz<double,2>(input));
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot perform forward-step for `%s': unknown exception caught", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_backward_step_str, "backward_step");
+PyDoc_STRVAR(s_backward_step_doc,
+"Backwards a batch of data through the MLP and updates the\n\
+internal buffers (errors and derivatives).");
+
+static PyObject* PyBobLearnMLPTrainer_backwardStep
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"machine", "input", "target"};
+  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->backward_step(*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 backward-step for `%s': unknown exception caught", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+PyDoc_STRVAR(s_cost_str, "cost");
+PyDoc_STRVAR(s_cost_doc,
+"o.cost(target, [machine, input]) -> float\n\
+\n\
+Calculates the cost for a given target.\n\
+\n\
+The cost for a given target is defined as the sum of individual\n\
+costs for every output in the current network, averaged over all\n\
+the examples.\n\
+\n\
+You can use this function in two ways. Either by initially calling\n\
+:py:meth:`forward_step` passing ``machine`` and ``input`` and then\n\
+calling this method with just the ``target`` or passing all three\n\
+objects in a single call. With the latter strategy, the\n\
+:py:meth:`forward_step` will be called internally.\n\
+\n\
+This function returns a single scalar, of ``float`` type,\n\
+representing the average cost for all input given the expected\n\
+target.\n\
+");
+
+static PyObject* PyBobLearnMLPTrainer_cost
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"target", "machine", "input"};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBlitzArrayObject* target = 0;
+  PyBobLearnMLPMachineObject* machine = 0;
+  PyBlitzArrayObject* input = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O!O&", kwlist,
+        &PyBlitzArray_Converter, &target,
+        &PyBobLearnMLPMachine_Type, &machine,
+        &PyBlitzArray_Converter, &input)) return 0;
+
+  if ((machine && !input) || (input && !machine)) {
+    PyErr_Format(PyExc_RuntimeError, "`%s.%s' expects that you either provide only the target (after a call to `forward_step') with a given machine and input or target, machine *and* input. You cannot provide a machine and not an input or vice-versa", Py_TYPE(self)->tp_name, s_cost_str);
+    return 0;
+  }
+
+  if (input && (input->type_num != NPY_FLOAT64 || input->ndim != 2)) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 2D 64-bit float arrays for argument `input' (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_cost_str, input->ndim, PyBlitzArray_TypenumAsString(input->type_num));
+    return 0;
+  }
+
+  if (target->type_num != NPY_FLOAT64 || target->ndim != 2) {
+    PyErr_Format(PyExc_TypeError, "`%s.%s' only supports 2D 64-bit float arrays for argument `target' (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 target", Py_TYPE(self)->tp_name, s_cost_str, target->ndim, PyBlitzArray_TypenumAsString(target->type_num));
+    return 0;
+  }
+
+  try {
+    if (machine) {
+      return Py_BuildValue("d", self->cxx->cost(*machine->cxx,
+            *PyBlitzArrayCxx_AsBlitz<double,2>(input),
+            *PyBlitzArrayCxx_AsBlitz<double,2>(target)));
+    }
+    else {
+      return Py_BuildValue("d",
+          self->cxx->cost(*PyBlitzArrayCxx_AsBlitz<double,2>(target)));
+    }
+  }
+  catch (std::exception& ex) {
+    PyErr_SetString(PyExc_RuntimeError, ex.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot estimate cost for `%s': unknown exception caught", Py_TYPE(self)->tp_name);
+    return 0;
+  }
+
+  PyErr_Format(PyExc_RuntimeError, " `%s': unexpected condition - DEBUG ME", Py_TYPE(self)->tp_name);
+  return 0;
+
+}
+
+PyDoc_STRVAR(s_hidden_layers_str, "hidden_layers");
+PyDoc_STRVAR(s_hidden_layers_doc,
+    "The number of hidden layers on the target machine.");
+
+static PyObject* PyBobLearnMLPTrainer_hiddenLayers
+(PyBobLearnMLPTrainerObject* self) {
+  return Py_BuildValue("n", self->cxx->numberOfHiddenLayers());
+}
+
+PyDoc_STRVAR(s_set_error_str, "set_error");
+PyDoc_STRVAR(s_set_error_doc, "Sets the error for a given layer in the network.");
+
+static PyObject* PyBobLearnMLPTrainer_setErrorOnLayer
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer"};
+  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_error_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setError(*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 error 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_output_str, "set_output");
+PyDoc_STRVAR(s_set_output_doc, "Sets the output for a given layer in the network.");
+
+static PyObject* PyBobLearnMLPTrainer_setOutputOnLayer
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer"};
+  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_output_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setOutput(*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 output 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_derivative_str, "set_derivative");
+PyDoc_STRVAR(s_set_derivative_doc,
+    "Sets the cost derivative w.r.t. the **weights** for a given layer.");
+
+static PyObject* PyBobLearnMLPTrainer_setDerivativeOnLayer
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer"};
+  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_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setDerivative(*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);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+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.");
+
+static PyObject* PyBobLearnMLPTrainer_setBiasDerivativeOnLayer
+(PyBobLearnMLPTrainerObject* self, PyObject* args, PyObject* kwds) {
+
+  /* Parses input arguments in a single shot */
+  static const char* const_kwlist[] = {"array", "layer"};
+  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_bias_derivative_str, array->ndim, PyBlitzArray_TypenumAsString(array->type_num));
+    return 0;
+  }
+
+  try {
+    self->cxx->setBiasDerivative(*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);
+    return 0;
+  }
+
+  Py_RETURN_NONE;
+
+}
+
+static PyMethodDef PyBobLearnMLPTrainer_methods[] = {
+  {
+    s_is_compatible_str,
+    (PyCFunction)PyBobLearnMLPTrainer_isCompatible,
+    METH_O,
+    s_is_compatible_doc,
+  },
+  {
+    s_initialize_str,
+    (PyCFunction)PyBobLearnMLPTrainer_initialize,
+    METH_O,
+    s_initialize_doc,
+  },
+  {
+    s_forward_step_str,
+    (PyCFunction)PyBobLearnMLPTrainer_forwardStep,
+    METH_VARARGS|METH_KEYWORDS,
+    s_forward_step_doc,
+  },
+  {
+    s_backward_step_str,
+    (PyCFunction)PyBobLearnMLPTrainer_backwardStep,
+    METH_VARARGS|METH_KEYWORDS,
+    s_backward_step_doc,
+  },
+  {
+    s_cost_str,
+    (PyCFunction)PyBobLearnMLPTrainer_cost,
+    METH_VARARGS|METH_KEYWORDS,
+    s_cost_doc,
+  },
+  {
+    s_hidden_layers_str,
+    (PyCFunction)PyBobLearnMLPTrainer_hiddenLayers,
+    METH_NOARGS,
+    s_hidden_layers_doc,
+  },
+  {
+    s_set_error_str,
+    (PyCFunction)PyBobLearnMLPTrainer_setErrorOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_error_doc,
+  },
+  {
+    s_set_output_str,
+    (PyCFunction)PyBobLearnMLPTrainer_setOutputOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_output_doc,
+  },
+  {
+    s_set_derivative_str,
+    (PyCFunction)PyBobLearnMLPTrainer_setDerivativeOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_derivative_doc,
+  },
+  {
+    s_set_bias_derivative_str,
+    (PyCFunction)PyBobLearnMLPTrainer_setBiasDerivativeOnLayer,
+    METH_VARARGS|METH_KEYWORDS,
+    s_set_bias_derivative_doc,
+  },
+  {0} /* Sentinel */
+};
+
 PyTypeObject PyBobLearnMLPTrainer_Type = {
     PyVarObject_HEAD_INIT(0, 0)
     s_trainer_str,                                 /* tp_name */
@@ -191,9 +965,9 @@ PyTypeObject PyBobLearnMLPTrainer_Type = {
     0,                                             /* tp_weaklistoffset */
     0,                                             /* tp_iter */
     0,                                             /* tp_iternext */
-    0, //PyBobLearnMLPTrainer_methods,                  /* tp_methods */
+    PyBobLearnMLPTrainer_methods,                  /* tp_methods */
     0,                                             /* tp_members */
-    0, //PyBobLearnMLPTrainer_getseters,                /* tp_getset */
+    PyBobLearnMLPTrainer_getseters,                /* tp_getset */
     0,                                             /* tp_base */
     0,                                             /* tp_dict */
     0,                                             /* tp_descr_get */
-- 
GitLab