Repository: chainer/chainer

Issue:
Show in which function type-inconsistency between data and grad occurs.
When we set `Variable.grad`, type consistency between `Variable.data` and `Variable.grad` is checked (as we did in #210 ). If this type check fails, it emits error message. But this message does not show in which function this inconsistency occurs. This is not preferable for debugging.



Discussion:

Resolving patch:
diff --git a/chainer/variable.py b/chainer/variable.py
--- a/chainer/variable.py
+++ b/chainer/variable.py
@@ -6,6 +6,34 @@
 from chainer import flag
 
 
+def _check_grad_type(func, x, gx):
+    def make_message(message):
+        if func:
+            detail = '''Function {0} ({1}) has a bug.
+Please report this error to the issue tracker with the stack trace,
+the information of your environment, and your script:
+https://github.com/pfnet/chainer/issues/new.
+'''.format(type(func).__name__, func.label)
+        else:
+            detail = ''
+
+        detail += message
+        return detail
+
+    if not isinstance(gx, type(x.data)):
+        msg = ('Type of data and grad mismatch\n%s != %s' %
+               (type(x.data), type(gx)))
+        raise TypeError(make_message(msg))
+    if gx.dtype != x.data.dtype:
+        msg = ('Dtype of data and grad mismatch\n%s != %s' %
+               (x.data.dtype, gx.dtype))
+        raise TypeError(make_message(msg))
+    if gx.shape != x.data.shape:
+        msg = ('Shape of data and grad mismatch\n%s != %s' %
+               (x.data.shape, gx.shape))
+        raise ValueError(make_message(msg))
+
+
 class Variable(object):
 
     """Array with a structure to keep track of computation.
@@ -141,24 +169,8 @@ def grad(self):
 
     @grad.setter
     def grad(self, g):
-        error_msg = '''
-This error is occured in two cases. The first case is when the user manually
-sets the Variable.grad incorrectly. The second case is when some Function
-implementation has a bug. If you do not manually set the Variable.grad in your
-script, please report this error to the issue tracker with the stack trace,
-the information of your environment, and your script:
-https://github.com/pfnet/chainer/issues/new.
-'''
         if g is not None:
-            if not isinstance(g, type(self.data)):
-                raise TypeError('Type of data and grad mismatch: %s != %s%s'
-                                % (type(self.data), type(g), error_msg))
-            if g.dtype != self.data.dtype:
-                raise TypeError('Dtype of data and grad mismatch: %s != %s%s'
-                                % (self.data.dtype, g.dtype, error_msg))
-            if g.shape != self.data.shape:
-                raise ValueError('Shape of data and grad mismatch: %s != %s%s'
-                                 % (self.data.shape, g.shape, error_msg))
+            _check_grad_type(None, self, g)
         self._grad = g
 
     def to_cpu(self):
@@ -323,6 +335,9 @@ def add_cand(cand):
             for x, gx in zip(func.inputs, gxs):
                 if gx is None:
                     continue
+
+                _check_grad_type(func, x, gx)
+
                 # Accumulate the graident to x. It is a bit tricky to handle
                 # branches and parameter gradient accumulation correctly.
                 with cuda.get_device(gx):


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
@@ -445,4 +445,82 @@ def test_set_creator_gpu(self):
         self.check_set_creator(cuda.to_gpu(self.x))
 
 
+class TestVariableBackwardError(unittest.TestCase):
+
+    def setUp(self):
+        self.x = np.array([1], np.float32)
+
+    def check_type_mismatch(self, x_data):
+        xp = cuda.get_array_module(x_data)
+
+        class DummyFunction(chainer.Function):
+            label = 'dummy_function'
+
+            def forward(self, inputs):
+                return xp.array(1, np.float32),
+
+            def backward(self, inputs, grads):
+                return [1]
+
+        x = chainer.Variable(x_data)
+        y = DummyFunction()(x)
+        with self.assertRaisesRegexp(TypeError, 'dummy_function'):
+            y.backward()
+
+    def test_type_mismatch_cpu(self):
+        self.check_type_mismatch(self.x)
+
+    @attr.gpu
+    def test_type_mismatch_gpu(self):
+        self.check_type_mismatch(cuda.to_gpu(self.x))
+
+    def check_dtype_mismatch(self, x_data):
+        xp = cuda.get_array_module(x_data)
+
+        class DummyFunction(chainer.Function):
+            label = 'dummy_function'
+
+            def forward(self, inputs):
+                return xp.array(1, np.float32),
+
+            def backward(self, inputs, grads):
+                return xp.array([1], np.int32),
+
+        x = chainer.Variable(x_data)
+        y = DummyFunction()(x)
+        with self.assertRaisesRegexp(TypeError, 'dummy_function'):
+            y.backward()
+
+    def test_dtype_mismatch_cpu(self):
+        self.check_dtype_mismatch(self.x)
+
+    @attr.gpu
+    def test_dtype_mismatch_gpu(self):
+        self.check_dtype_mismatch(cuda.to_gpu(self.x))
+
+    def check_shape_mismatch(self, x_data):
+        xp = cuda.get_array_module(x_data)
+
+        class DummyFunction(chainer.Function):
+            label = 'dummy_function'
+
+            def forward(self, inputs):
+                return xp.array(1, np.float32),
+
+            def backward(self, inputs, grads):
+                return xp.array([1, 2], np.float32),
+
+        x = chainer.Variable(x_data)
+        y = DummyFunction()(x)
+        with self.assertRaisesRegexp(ValueError, 'dummy_function'):
+            y.backward()
+
+    def test_shape_mismatch_cpu(self):
+        self.check_shape_mismatch(self.x)
+
+    @attr.gpu
+    def test_shape_mismatch_gpu(self):
+        self.check_shape_mismatch(cuda.to_gpu(self.x))
+
+
 testing.run_module(__name__, __file__)