Repository: chainer/chainer

Issue:
resuming issue of LinearShift
Same issue as #2680

```
import chainer
from chainer import iterators
from chainer import optimizers
from chainer import training
from chainer.training import extensions
from chainer import serializers


class DummyModel(chainer.Chain):

    def __call__(self, x):
        return x


def setup_trainer(iteration):
    model = DummyModel()
    optimizer = optimizers.SGD()
    optimizer.setup(model)

    iterator = iterators.SerialIterator([1, 2, 3], 1)

    updater = training.StandardUpdater(iterator, optimizer)
    trainer = training.Trainer(updater, (iteration, 'iteration'), out='.')

    trainer.extend(extensions.LogReport(trigger=(1, 'iteration')))
    trainer.extend(extensions.observe_lr(), trigger=(1, 'iteration'))
    trainer.extend(
        extensions.PrintReport(['iteration', 'lr']),
        trigger=(1, 'iteration'))

    trainer.extend(
        extensions.LinearShift('lr', (2, 1), (5, 15)),
        trigger=(1, 'iteration'))

    return trainer


trainer = setup_trainer(10)
trainer.run()
serializers.save_npz('tmp', trainer)
# iteration   lr
# 1           2
# 2           2
# 3           2
# 4           2
# 5           2
# 6           2
# 7           1.9
# 8           1.8
# 9           1.7
# 10          1.6

resumed_trainer = setup_trainer(20)
serializers.load_npz('tmp', resumed_trainer)
resumed_trainer.run()
# iteration   lr
# 1           2
# 2           2
# 3           2
# 4           2
# 5           2
# 6           2
# 7           1.9
# 8           1.8
# 9           1.7
# 10          1.6
# 11          1.4 (lr = 1.5 is skipped)
# 12          1.3
# 13          1.2
# 14          1.1
# 15          1
# 16          1
# 17          1
# 18          1
# 19          1
# 20          1
```


Discussion:
I will create a PR shortly.

Resolving patch:
diff --git a/chainer/training/extensions/linear_shift.py b/chainer/training/extensions/linear_shift.py
--- a/chainer/training/extensions/linear_shift.py
+++ b/chainer/training/extensions/linear_shift.py
@@ -36,23 +36,34 @@ def __init__(self, attr, value_range, time_range, optimizer=None):
         self._value_range = value_range
         self._time_range = time_range
         self._optimizer = optimizer
-        self._t = 0
+        self._t = 1
+        self._before_training = True
 
     def __call__(self, trainer):
         optimizer = self._optimizer or trainer.updater.get_optimizer('main')
+
+        if self._before_training:
+            self._before_training = False
+            value = self._compute_value(self._t - 1)
+        else:
+            value = self._compute_value(self._t)
+            self._t += 1
+
+        setattr(optimizer, self._attr, value)
+
+    def serialize(self, serializer):
+        self._t = serializer('_t', self._t)
+
+    def _compute_value(self, t):
         t1, t2 = self._time_range
         v1, v2 = self._value_range
 
-        if self._t <= t1:
+        if t <= t1:
             value = v1
-        elif self._t >= t2:
+        elif t >= t2:
             value = v2
         else:
-            rate = (self._t - t1) / (t2 - t1)
+            rate = (t - t1) / (t2 - t1)
             value = v1 + rate * (v2 - v1)
-        setattr(optimizer, self._attr, value)
 
-        self._t += 1
-
-    def serialize(self, serializer):
-        self._t = serializer('_t', self._t)
+        return value


Test patch:
diff --git a/tests/chainer_tests/training_tests/extensions_tests/test_linear_shift.py b/tests/chainer_tests/training_tests/extensions_tests/test_linear_shift.py
--- a/tests/chainer_tests/training_tests/extensions_tests/test_linear_shift.py
+++ b/tests/chainer_tests/training_tests/extensions_tests/test_linear_shift.py
@@ -1,27 +1,125 @@
+import numpy
 import unittest
 
 import mock
 
+from chainer import serializer
 from chainer import testing
+from chainer import training
 from chainer.training import extensions
 
 
+class DummySerializer(serializer.Serializer):
+
+    def __init__(self, target):
+        super(DummySerializer, self).__init__()
+        self.target = target
+
+    def __getitem__(self, key):
+        raise NotImplementedError
+
+    def __call__(self, key, value):
+        self.target[key] = value
+        return self.target[key]
+
+
+class DummyDeserializer(serializer.Deserializer):
+
+    def __init__(self, target):
+        super(DummyDeserializer, self).__init__()
+        self.target = target
+
+    def __getitem__(self, key):
+        raise NotImplementedError
+
+    def __call__(self, key, value):
+        if value is None:
+            value = self.target[key]
+        elif isinstance(value, numpy.ndarray):
+            numpy.copyto(value, self.target[key])
+        else:
+            value = type(value)(numpy.asarray(self.target[key]))
+        return value
+
+
+def _get_mocked_trainer():
+    trainer = mock.Mock()
+
+    def update():
+        trainer.updater.iteration += 1
+    trainer.updater.iteration = 0
+    trainer.updater.update = update
+
+    trainer.updater.get_optimizer = lambda _: trainer.updater.optimizer
+
+    return trainer
+
+
 class TestLinearShift(unittest.TestCase):
 
     value_range = (2.0, 6.0)
     time_range = (1, 3)
-    expect = [2.0, 2.0, 4.0, 6.0, 6.0]
+    expect = [2.0, 2.0, 2.0, 2.0, 4.0, 4.0, 6.0, 6.0, 6.0, 6.0]
 
     def setUp(self):
-        self.optimizer = mock.MagicMock()
-        self.trainer = mock.MagicMock()
-        self.extension = extensions.LinearShift(
-            'x', self.value_range, self.time_range, self.optimizer)
-
-    def test_call(self):
-        for e in self.expect:
-            self.extension(self.trainer)
-            self.assertEqual(self.optimizer.x, e)
+        self.trainer = _get_mocked_trainer()
+        self.optimizer = self.trainer.updater.get_optimizer('main')
+        self.interval = 2
+        self.trigger = training.get_trigger((self.interval, 'iteration'))
+
+    def _run_trainer(self, extension, expect, optimizer=None):
+        if optimizer is None:
+            optimizer = self.trainer.updater.optimizer
+
+        if extension.invoke_before_training:
+            extension(self.trainer)
+
+        for e in expect:
+            self.trainer.updater.update()
+            self.assertEqual(optimizer.x, e)
+            if self.trigger(self.trainer):
+                extension(self.trainer)
+
+    def test_basic(self):
+        self.trainer.updater.optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range)
+        self._run_trainer(extension, self.expect)
+
+    def test_with_optimizer(self):
+        optimizer = mock.Mock()
+        optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range, optimizer)
+        self._run_trainer(extension, self.expect, optimizer)
+
+    def test_serialize(self):
+        self.trainer.updater.optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range)
+        self._run_trainer(extension, self.expect[:len(self.expect) // 2])
+        target = dict()
+        extension.serialize(DummySerializer(target))
+
+        self.trainer.updater.optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range)
+        extension.serialize(DummyDeserializer(target))
+        self._run_trainer(extension, self.expect[len(self.expect) // 2:])
+
+    def test_serialize_before_first_interval(self):
+        self.trainer.updater.optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range)
+        self._run_trainer(extension, self.expect[:self.interval - 1])
+        target = dict()
+        extension.serialize(DummySerializer(target))
+
+        self.trainer.updater.optimizer.x = 0
+        extension = extensions.LinearShift(
+            'x', self.value_range, self.time_range)
+        extension.serialize(DummyDeserializer(target))
+        self._run_trainer(extension, self.expect[self.interval - 1:])
 
 
 testing.run_module(__name__, __file__)