Repository: pytorch/ignite

Issue:
Add annotations to attach event handlers
See #37 for details

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index ee56c8de..99f6efd0 100644
--- a/README.rst
+++ b/README.rst
@@ -14,6 +14,7 @@ Ignite is a high-level library to help with training neural networks in PyTorch.
     - `Training & Validation History`_
     - `Events & Event Handlers`_
     - `Logging`_
+    - `Metrics`_
 - `Examples`_
 - `How does this compare to Torchnet?`_
 - `Contributing`_
@@ -128,6 +129,16 @@ Ignite uses `python's standard library logging module <https://docs.python.org/2
     logger.addHandler(logging.StreamHandler())
     logger.setLevel(logging.INFO)
 
+Metrics
++++++++
+Ignite supports certain metrics which can be used to classify the performance of a given model. The metrics currently available in :code:`ignite` are:
+
+- :code:`binary_accuracy` : This takes a :code:`History` object (either :code:`training_history` or :code:`validation_history`) and an optional callable transform and computes the binary accuracy which is 1 if the values are equal or 0 otherwise. This is generally used for binary classification tasks
+- :code:`categorical_accuracy` : This is the :code:`binary_accuracy` equivalent for multi-class classification where number of classes are greater than 2.
+- :code:`top_k_categorical_accuracy` : This computes the Top K classification accuracy, which is a popular mode of evaluating models on larger datasets with higher number of classes. The semantics are similar to :code:`categorical_accuracy` except there is an additional argument for the value of :code:`k`
+- :code:`mean_squared_error` : Generally used in regression tasks, this computes the sum of squared deviations between the predicted value and the actual value for a given input datapoint. This function takes a :code:`History` object and an optional callable transform and computes the mean squared error. The square root of this gives the root mean squared error (RMSE).
+- :code:`mean_absolute_error` : This is similar to the :code:`mean_squared_error` function, but instead computes the sum of absolute deviations between the predicted value and the actual value for a given input datapoint.
+
 Examples
 ++++++++
 At present, there is an example of how to use ignite to train a digit classifier on MNIST in `examples/
diff --git a/ignite/trainer/trainer.py b/ignite/trainer/trainer.py
index 6e21dd11..b1a93246 100644
--- a/ignite/trainer/trainer.py
+++ b/ignite/trainer/trainer.py
@@ -103,6 +103,29 @@ class Trainer(object):
         self._event_handlers[event_name].append((handler, args, kwargs))
         self._logger.debug("added handler for event % ", event_name)
 
+    def on(self, event_name, *args, **kwargs):
+        """
+        Decorator shortcut for add_event_handler
+
+        Parameters
+        ----------
+        event_name: enum
+            event from ignite.trainer.TrainingEvents to attach the
+            handler to
+        args:
+            optional args to be passed to `handler`
+        kwargs:
+            optional keyword args to be passed to `handler`
+
+        Returns
+        -------
+        None
+        """
+        def decorator(f):
+            self.add_event_handler(event_name, f, *args, **kwargs)
+            return f
+        return decorator
+
     def _fire_event(self, event_name):
         if event_name in self._event_handlers.keys():
             self._logger.debug("firing handlers for event %s ", event_name)


Test patch:
diff --git a/tests/ignite/trainer/test_trainer.py b/tests/ignite/trainer/test_trainer.py
index 1bb8cda0..e784a897 100644
--- a/tests/ignite/trainer/test_trainer.py
+++ b/tests/ignite/trainer/test_trainer.py
@@ -410,3 +410,32 @@ def test_create_supervised():
     assert loss == approx(17.0)
     assert model.weight.data[0, 0] == approx(1.3)
     assert model.bias.data[0] == approx(0.8)
+
+
+def test_on_decorator():
+    max_epochs = 5
+    num_batches = 3
+    training_data = _create_mock_data_loader(max_epochs, num_batches)
+
+    trainer = Trainer(MagicMock(return_value=1), MagicMock())
+
+    class Counter(object):
+        def __init__(self, count=0):
+            self.count = count
+
+    started_counter = Counter()
+
+    @trainer.on(TrainingEvents.TRAINING_ITERATION_STARTED, started_counter)
+    def handle_training_iteration_started(trainer, started_counter):
+        started_counter.count += 1
+
+    completed_counter = Counter()
+
+    @trainer.on(TrainingEvents.TRAINING_ITERATION_COMPLETED, completed_counter)
+    def handle_training_iteration_completed(trainer, completed_counter):
+        completed_counter.count += 1
+
+    trainer.run(training_data, max_epochs=max_epochs)
+
+    assert started_counter.count == 15
+    assert completed_counter.count == 15