Repository: chainer/chainer

Issue:
Add debug_print function to Variable
Currently, it is little hard to confirm `Variable` properties one by one. How about adding `Variable.debug_print` method to show required info at once? I'm willing to send a PR if it looks useful.

I've created utility func to show info as below:
- device which variable is on
- volatile flag
- background class of `data` 
- `data.shape`
- `data.dtype`
- `data` statistics (mean and std)
- `grad` ( none, all 0 or mean/std )

```
arr = np.random.randn(5, 3, 5, 5)

vcpu = Variable(arr)
vcpu.debug_print()
# <variable at 0x7fdc22141810>
# - device: CPU
# - volatile: OFF
# - background: <type 'numpy.ndarray'>
# - shape: (5, 3, 5, 5)
# - dtype: float64
# - statistics: mean=-0.0130620493847, std=1.00905897902
# - grad: None

vgpu = Variable(arr)
vgpu.to_gpu()
vgpu.zerograd()
vgpu.debug_print()
# <variable at 0x7fdc22141a50>
# - device: <CUDA Device 0>
# - volatile: OFF
# - background: <type 'cupy.core.core.ndarray'>
# - shape: (5, 3, 5, 5)
# - dtype: float64
# - statistics: mean=-0.0130620493847, std=1.00905897902
# - grad: 0

vcpu.grad = np.random.randn(5, 3, 5, 5)
vcpu.debug_print()
# <variable at 0x7fdc22141810>
# - device: CPU
# - volatile: OFF
# - background: <type 'numpy.ndarray'>
# - shape: (5, 3, 5, 5)
# - dtype: float64
# - statistics: mean=-0.0130620493847, std=1.00905897902
# - grad: mean=0.0081744735799, std=1.01002850276
```



Discussion:
I am in favor of this proposal :+1: .

@sinhrks  If you have already implement this feature, could you send a PR?


Resolving patch:
diff --git a/chainer/variable.py b/chainer/variable.py
--- a/chainer/variable.py
+++ b/chainer/variable.py
@@ -68,6 +68,45 @@ def __repr__(self):
     def __str__(self):
         return self.name or ('<var@%x>' % id(self))
 
+    def debug_print(self):
+        """Display a summary of the stored data and location of the Variable"""
+
+        msg = """{summary}
+- device: {device}
+- volatile: {volatile}
+- backend: {background}
+- shape: {shape}
+- dtype: {dtype}
+- statistics: {stats}
+- grad: {grad}"""
+
+        stats_msg = 'mean={0:.8f}, std={1:.8f}'
+
+        try:
+            device = self.data.device
+        except AttributeError:
+            device = 'CPU'
+
+        with cuda.get_device(self.data) as dev:
+            xp = numpy if int(dev) == -1 else cuda.cupy
+
+            if self.grad is None:
+                grad = None
+            elif xp.all(self.grad == 0):
+                grad = 0
+            else:
+                grad = stats_msg.format(float(xp.mean(self.grad)),
+                                        float(xp.std(self.grad)))
+
+            stats = stats_msg.format(float(xp.mean(self.data)),
+                                     float(xp.std(self.data)))
+
+        return msg.format(summary=repr(self), volatile=self.volatile,
+                          grad=grad, shape=self.data.shape,
+                          background=type(self.data),
+                          dtype=self.data.dtype, device=device,
+                          stats=stats)
+
     def __pos__(self):
         return self
 


Test patch:
diff --git a/tests/chainer_tests/test_variable.py b/tests/chainer_tests/test_variable.py
--- a/tests/chainer_tests/test_variable.py
+++ b/tests/chainer_tests/test_variable.py
@@ -7,6 +7,7 @@
 from chainer import testing
 from chainer.testing import attr
 
+import re
 import six
 
 
@@ -367,6 +368,60 @@ def test_addgrad_gpu_to_another_gpu(self):
         self.check_addgrad(a, b, c)
 
 
+class TestDebugPrint(unittest.TestCase):
+
+    def setUp(self):
+        self.arr = np.random.randn(5, 3, 5, 5).astype(np.float32)
+
+    def check_debug_print(self, v, mean, std):
+        result = v.debug_print()
+        self.assertIn(repr(v), result)
+        self.assertIn('volatile: OFF', result)
+        self.assertIn('dtype: float32', result)
+        # py2.7 on win64 returns shape as long
+        self.assertTrue(re.match(r'- shape: \(5L?, 3L?, 5L?, 5L?\)',
+                        result.splitlines()[4]))
+
+        # no grad
+        msg = 'statistics: mean={mean:.8f}, std={std:.8f}'
+        msg = msg.format(mean=mean, std=std)
+        self.assertIn(msg, result)
+        self.assertIn('grad: None', result)
+
+        # zero grad
+        v.zerograd()
+        result = v.debug_print()
+        self.assertIn('grad: 0', result)
+
+        # add grad
+        v.grad = v.data
+        result = v.debug_print()
+
+        msg = 'grad: mean={mean:.8f}, std={std:.8f}'.format(mean=mean, std=std)
+        self.assertIn(msg, result)
+
+    def test_debug_print_cpu(self):
+        v = chainer.Variable(self.arr)
+        result = v.debug_print()
+        self.assertIn('device: CPU', result)
+        self.assertIn('numpy.ndarray', result)
+
+        self.check_debug_print(v, mean=np.mean(self.arr),
+                               std=np.std(self.arr))
+
+    @attr.gpu
+    def test_debug_print_gpu(self):
+        v = chainer.Variable(self.arr)
+        v.to_gpu(0)
+
+        result = v.debug_print()
+        self.assertIn('device: <CUDA Device 0>', result)
+        self.assertIn('cupy.core.core.ndarray', result)
+
+        self.check_debug_print(v, mean=cuda.cupy.mean(self.arr),
+                               std=cuda.cupy.std(self.arr))
+
+
 class TestVariableSetCreator(unittest.TestCase):
     class MockFunction(object):
         pass