Repository: chainer/chainer

Issue:
chainer.reporter.Summary does not properly deserialize float values from NpzDeserializer
Hi, I've noticed that ```chainer.reporter.Summary.serialize``` convert all float values into int if it is called with ```chainer.serializers.NpzDeserializer``` because the deserializer convert the deserialized values into the same types as the original values and ```._x``` and ```._x2``` are initialized as ```0```, which are integer values.

Apparently, this problem will be fixed if you make them initialized as ```0.0```, but I'm not sure whether it is applicable. What do you think?

* Conditions
  - Platform: Linux-4.4.0-97-generic-x86_64-with-Ubuntu-16.04-xenial
  - Chainer: 5.0.0rc1
  - NumPy: 1.15.2
  - CuPy:
    - CuPy Version          : 5.0.0rc1
    - CUDA Root             : /usr/local/cuda
    - CUDA Build Version    : 9000
    - CUDA Driver Version   : 9000
    - CUDA Runtime Version  : 9000
    - cuDNN Build Version   : 7300
    - cuDNN Version         : 7300
    - NCCL Build Version    : 2213

* Code to reproduce
```python
import tempfile

import chainer

summary = chainer.reporter.Summary()
summary.add(0.5)
print('before:', summary.compute_mean())
with tempfile.NamedTemporaryFile() as npz:
    chainer.serializers.save_npz(npz, summary)
    npz.flush()
    summary = chainer.reporter.Summary()
    chainer.serializers.load_npz(npz.name, summary)
print('after:', summary.compute_mean())
```
* Output
```
before: 0.5
after: 0.0
```


Discussion:

Resolving patch:
diff --git a/chainer/reporter.py b/chainer/reporter.py
--- a/chainer/reporter.py
+++ b/chainer/reporter.py
@@ -264,8 +264,8 @@ class Summary(object):
     """
 
     def __init__(self):
-        self._x = 0
-        self._x2 = 0
+        self._x = 0.0
+        self._x2 = 0.0
         self._n = 0
 
     def add(self, value, weight=1):


Test patch:
diff --git a/chainer/testing/backend.py b/chainer/testing/backend.py
--- a/chainer/testing/backend.py
+++ b/chainer/testing/backend.py
@@ -91,6 +91,22 @@ def get_pytest_marks(self):
         assert all(callable(_) for _ in marks)
         return marks
 
+    def _get_single_array(self, np_array):
+        if np_array is None:
+            return None
+        if self.use_cuda:
+            return chainer.backend.cuda.to_gpu(np_array)
+        if self.use_ideep:
+            return np_array
+        return np_array
+
+    def get_array(self, np_array):
+        if isinstance(np_array, tuple):
+            return tuple([self._get_single_array(a) for a in np_array])
+        if isinstance(np_array, list):
+            return [self._get_single_array(a) for a in np_array]
+        return self._get_single_array(np_array)
+
 
 def _wrap_backend_test_method(impl, param, method_name):
     backend_config = BackendConfig(param)
diff --git a/tests/chainer_tests/test_reporter.py b/tests/chainer_tests/test_reporter.py
--- a/tests/chainer_tests/test_reporter.py
+++ b/tests/chainer_tests/test_reporter.py
@@ -10,6 +10,7 @@
 from chainer import functions
 from chainer import testing
 from chainer.testing import attr
+from chainer.testing import backend
 
 
 class TestReporter(unittest.TestCase):
@@ -163,27 +164,17 @@ def test_report_scope(self):
         self.assertNotIn('x', reporter.observation)
 
 
+@backend.inject_backend_tests(
+    ['test_basic', 'test_serialize_array_float', 'test_serialize_array_int'],
+    [{}, {'use_cuda': True}])
 class TestSummary(unittest.TestCase):
 
     def setUp(self):
         self.summary = chainer.reporter.Summary()
 
-    def test_numpy(self):
-        self.summary.add(numpy.array(1, 'f'))
-        self.summary.add(numpy.array(-2, 'f'))
-
-        mean = self.summary.compute_mean()
-        testing.assert_allclose(mean, numpy.array(-0.5, 'f'))
-
-        mean, std = self.summary.make_statistics()
-        testing.assert_allclose(mean, numpy.array(-0.5, 'f'))
-        testing.assert_allclose(std, numpy.array(1.5, 'f'))
-
-    @attr.gpu
-    def test_cupy(self):
-        xp = cuda.cupy
-        self.summary.add(xp.array(1, 'f'))
-        self.summary.add(xp.array(-2, 'f'))
+    def test_basic(self, backend_config):
+        self.summary.add(backend_config.get_array(numpy.array(1, 'f')))
+        self.summary.add(backend_config.get_array(numpy.array(-2, 'f')))
 
         mean = self.summary.compute_mean()
         testing.assert_allclose(mean, numpy.array(-0.5, 'f'))
@@ -202,7 +193,7 @@ def test_int(self):
 
         mean, std = self.summary.make_statistics()
         testing.assert_allclose(mean, 2)
-        testing.assert_allclose(std, numpy.sqrt(2 / 3))
+        testing.assert_allclose(std, numpy.sqrt(2. / 3.))
 
     def test_float(self):
         self.summary.add(1.)
@@ -225,37 +216,47 @@ def test_weight(self):
         val = (1 * 0.5 + 2 * 0.4 + 3 * 0.3) / (0.5 + 0.4 + 0.3)
         testing.assert_allclose(mean, val)
 
-    def test_serialize(self):
-        self.summary.add(1.)
-        self.summary.add(2.)
+    def check_serialize(self, value1, value2, value3):
+        xp = chainer.backend.get_array_module(value1, value2, value3)
+        self.summary.add(value1)
+        self.summary.add(value2)
 
         summary = chainer.reporter.Summary()
         testing.save_and_load_npz(self.summary, summary)
-        summary.add(3.)
+        summary.add(value3)
 
-        mean = summary.compute_mean()
-        testing.assert_allclose(mean, 2.)
-
-        mean, std = summary.make_statistics()
-        testing.assert_allclose(mean, 2.)
-        testing.assert_allclose(std, numpy.sqrt(2. / 3.))
-
-    @attr.gpu
-    def test_serialize_cupy(self):
-        xp = cuda.cupy
-        self.summary.add(xp.array(1, 'f'))
-        self.summary.add(xp.array(2, 'f'))
-
-        summary = chainer.reporter.Summary()
-        testing.save_and_load_npz(self.summary, summary)
-        summary.add(xp.array(3, 'f'))
+        expected_mean = (value1 + value2 + value3) / 3.
+        expected_std = xp.sqrt(
+            (value1**2 + value2**2 + value3**2) / 3. - expected_mean**2)
 
         mean = summary.compute_mean()
-        testing.assert_allclose(mean, 2.)
+        testing.assert_allclose(mean, expected_mean)
 
         mean, std = summary.make_statistics()
-        testing.assert_allclose(mean, 2.)
-        testing.assert_allclose(std, numpy.sqrt(2. / 3.))
+        testing.assert_allclose(mean, expected_mean)
+        testing.assert_allclose(std, expected_std)
+
+    def test_serialize_array_float(self, backend_config):
+        self.check_serialize(
+            backend_config.get_array(numpy.array(1.5, numpy.float32)),
+            backend_config.get_array(numpy.array(2.0, numpy.float32)),
+            # sum of the above two is non-integer
+            backend_config.get_array(numpy.array(3.5, numpy.float32)))
+
+    def test_serialize_array_int(self, backend_config):
+        self.check_serialize(
+            backend_config.get_array(numpy.array(1, numpy.int32)),
+            backend_config.get_array(numpy.array(-2, numpy.int32)),
+            backend_config.get_array(numpy.array(2, numpy.int32)))
+
+    def test_serialize_scalar_float(self):
+        self.check_serialize(
+            1.5, 2.0,
+            # sum of the above two is non-integer
+            3.5)
+
+    def test_serialize_scalar_int(self):
+        self.check_serialize(1, -2, 2)
 
     def test_serialize_backward_compat(self):
         with tempfile.NamedTemporaryFile(delete=False) as f:
@@ -284,7 +285,7 @@ def check(self, summary, data):
         mean = summary.compute_mean()
         self.assertEqual(set(mean.keys()), set(data.keys()))
         for name in data.keys():
-            m = sum(data[name]) / len(data[name])
+            m = sum(data[name]) / float(len(data[name]))
             testing.assert_allclose(mean[name], m)
 
         stats = summary.make_statistics()
@@ -292,9 +293,10 @@ def check(self, summary, data):
             set(stats.keys()),
             set(data.keys()).union(name + '.std' for name in data.keys()))
         for name in data.keys():
-            m = sum(data[name]) / len(data[name])
+            m = sum(data[name]) / float(len(data[name]))
             s = numpy.sqrt(
-                sum(x * x for x in data[name]) / len(data[name]) - m * m)
+                sum(x * x for x in data[name]) / float(len(data[name]))
+                - m * m)
             testing.assert_allclose(stats[name], m)
             testing.assert_allclose(stats[name + '.std'], s)