Repository: chainer/chainer

Issue:
Add extensions as trainer argument
In general in Chainer, the parts of the trainer build on one another by calling earlier parts as arguments, i.e. ``iterator = SerialIterator(train, 100)``.

``chainer.trainer`` does currently doesn't allow this structure with regard to extentions, instead requiring them to be called afterwards as arguments to ``trainer.extend()``. To allow for a similar structure, a new argument ``extend`` could be added to the ``trainer`` _init_ call.

```python
from chainer.training import extensions

trainer = chainer.trainer(updater, (50, 'epoch'), out='result', extend=[
    extensions.Evaluator(tester, model, device=gpu_id),
    extensions.dump_graph('main/loss'),
    extensions.snapshot(trigger=(20, 'epoch')),
...])
```


Discussion:

Resolving patch:
diff --git a/chainer/training/trainer.py b/chainer/training/trainer.py
--- a/chainer/training/trainer.py
+++ b/chainer/training/trainer.py
@@ -117,6 +117,8 @@ class Trainer(object):
             update the models.
         stop_trigger: Trigger that determines when to stop the training loop.
             If it is not callable, it is passed to :class:`IntervalTrigger`.
+        out: Output directory.
+        extensions: Extensions registered to the trainer.
 
     Attributes:
         updater: The updater object for this trainer.
@@ -130,7 +132,8 @@ class Trainer(object):
 
     """
 
-    def __init__(self, updater, stop_trigger=None, out='result'):
+    def __init__(self, updater, stop_trigger=None, out='result',
+                 extensions=[]):
         self.updater = updater
         self.stop_trigger = trigger_module.get_trigger(stop_trigger)
         self.observation = {}
@@ -151,6 +154,8 @@ def __init__(self, updater, stop_trigger=None, out='result'):
         self._final_elapsed_time = None
 
         updater.connect_trainer(self)
+        for ext in extensions:
+            self.extend(ext)
 
     @property
     def elapsed_time(self):


Test patch:
diff --git a/chainer/testing/training.py b/chainer/testing/training.py
--- a/chainer/testing/training.py
+++ b/chainer/testing/training.py
@@ -6,7 +6,7 @@
 
 
 def get_trainer_with_mock_updater(
-        stop_trigger=(10, 'iteration'), iter_per_epoch=10):
+        stop_trigger=(10, 'iteration'), iter_per_epoch=10, extensions=[]):
     """Returns a :class:`~chainer.training.Trainer` object with mock updater.
 
     The returned trainer can be used for testing the trainer itself and the
@@ -17,6 +17,7 @@ def get_trainer_with_mock_updater(
     Args:
         stop_trigger: Stop trigger of the trainer.
         iter_per_epoch: The number of iterations per epoch.
+        extensions: Extensions registered to the trainer.
 
     Returns:
         Trainer object with a mock updater.
@@ -41,5 +42,5 @@ def update():
             / iter_per_epoch
 
     updater.update = update
-    trainer = training.Trainer(updater, stop_trigger)
+    trainer = training.Trainer(updater, stop_trigger, extensions=extensions)
     return trainer
diff --git a/tests/chainer_tests/testing_tests/test_training.py b/tests/chainer_tests/testing_tests/test_training.py
--- a/tests/chainer_tests/testing_tests/test_training.py
+++ b/tests/chainer_tests/testing_tests/test_training.py
@@ -6,15 +6,21 @@
 from chainer import testing
 
 
+def _dummy_extension(trainer):
+    pass
+
+
 @testing.parameterize(*testing.product({
     'stop_trigger': [(5, 'iteration'), (5, 'epoch')],
     'iter_per_epoch': [0.5, 1, 1.5, 5],
+    'extensions': [[], [_dummy_extension]]
 }))
 class TestGetTrainerWithMockUpdater(unittest.TestCase):
 
     def setUp(self):
         self.trainer = testing.get_trainer_with_mock_updater(
-            self.stop_trigger, self.iter_per_epoch)
+            self.stop_trigger, self.iter_per_epoch,
+            extensions=self.extensions)
 
     def test_run(self):
         iteration = [0]
@@ -36,6 +42,8 @@ def check(trainer):
                 trainer.updater.previous_epoch_detail,
                 (iteration[0] - 1) / self.iter_per_epoch)
 
+        self.assertEqual(len(self.extensions), len(self.trainer._extensions))
+
         self.trainer.extend(check)
         self.trainer.run()