Repository: chainer/chainer

Issue:
Test N-dimensional convolution link for dtypes of FP16 and FP64
Follows #1279 and #1556.

Since #1295 is now merged to master, we can add test for dtypes of FP16 and FP64 to N-dimensional convolution **LINK**.



Discussion:

Resolving patch:
diff --git a/chainer/links/connection/convolution_nd.py b/chainer/links/connection/convolution_nd.py
--- a/chainer/links/connection/convolution_nd.py
+++ b/chainer/links/connection/convolution_nd.py
@@ -22,16 +22,11 @@ class ConvolutionND(link.Link):
             ``pad=p`` and ``pad=(p, p, ..., p)`` are equivalent.
         initialW: Value used to initialize the filter weight. May be an
             initializer instance or another value that
-            :func:`~chainer.init_weight` helper function can take. This link
-            uses :func:`~chainer.init_weight` to initialize the filter weight
-            and passes the value of ``initialW`` to it as it is.
+            :func:`~chainer.init_weight` helper function can take.
         initial_bias: Value used to initialize the bias vector. May be an
             initializer instance or another value except ``None`` that
             :func:`~chainer.init_weight` helper function can take. If ``None``
-            is given, this link does not use the bias vector. This link uses
-            :func:`~chainer.init_weight` to initialize the bias vector and
-            passes the value of ``initial_bias`` other than ``None`` to it as
-            it is.
+            is given, this link does not use the bias vector.
         use_cudnn (bool): If ``True``, then this link uses cuDNN if available.
             See :func:`~chainer.functions.convolution_nd` for exact conditions
             of cuDNN availability.
@@ -61,15 +56,17 @@ def __init__(self, ndim, in_channels, out_channels, ksize, stride=1, pad=0,
         self.use_cudnn = use_cudnn
         self.cover_all = cover_all
 
+        super(ConvolutionND, self).__init__()
+
         W_shape = (out_channels, in_channels) + ksize
-        super(ConvolutionND, self).__init__(W=W_shape)
-        initializers.init_weight(self.W.data, initialW)
+        initialW = initializers._get_initializer(initialW)
+        self.add_param('W', W_shape, initializer=initialW)
 
         if initial_bias is None:
             self.b = None
         else:
-            self.add_param('b', out_channels)
-            initializers.init_weight(self.b.data, initial_bias)
+            initial_bias = initializers._get_initializer(initial_bias)
+            self.add_param('b', out_channels, initializer=initial_bias)
 
     def __call__(self, x):
         """Applies N-dimensional convolution layer.


Test patch:
diff --git a/tests/chainer_tests/links_tests/connection_tests/test_convolution_nd.py b/tests/chainer_tests/links_tests/connection_tests/test_convolution_nd.py
--- a/tests/chainer_tests/links_tests/connection_tests/test_convolution_nd.py
+++ b/tests/chainer_tests/links_tests/connection_tests/test_convolution_nd.py
@@ -17,6 +17,7 @@
 
 @testing.parameterize(*testing.product({
     'dims': [(5,), (5, 4), (4, 3, 3)],
+    'dtype': [numpy.float16, numpy.float32, numpy.float64]
 }))
 class TestConvolutionND(unittest.TestCase):
 
@@ -28,15 +29,20 @@ def setUp(self):
 
         self.link = convolution_nd.ConvolutionND(
             ndim, 3, 2, self.ksize, stride=self.stride, pad=self.pad,
-            initial_bias=initializers.Uniform(1))
+            initial_bias=initializers.Uniform(scale=1., dtype=self.dtype))
         self.link.cleargrads()
 
         x_shape = (2, 3) + self.dims
-        self.x = numpy.random.uniform(-1, 1, x_shape).astype(numpy.float32)
+        self.x = numpy.random.uniform(-1, 1, x_shape).astype(self.dtype)
         gy_shape = (2, 2) + tuple(
             conv.get_conv_outsize(d, k, s, p) for (d, k, s, p) in zip(
                 self.dims, self.ksize, self.stride, self.pad))
-        self.gy = numpy.random.uniform(-1, 1, gy_shape).astype(numpy.float32)
+        self.gy = numpy.random.uniform(-1, 1, gy_shape).astype(self.dtype)
+
+        self.check_backward_options = {'eps': 1e-2, 'atol': 1e-3, 'rtol': 1e-3}
+        if self.dtype == numpy.float16:
+            self.check_backward_options = {
+                'eps': 2 ** -4, 'atol': 2 ** -4, 'rtol': 2 ** -4}
 
     @attr.gpu
     def test_im2col_consistency(self):
@@ -57,12 +63,12 @@ def test_col2im_consistency(self):
     def check_forward_consistency(self):
         x_cpu = chainer.Variable(self.x)
         y_cpu = self.link(x_cpu)
-        self.assertEqual(y_cpu.data.dtype, numpy.float32)
+        self.assertEqual(y_cpu.data.dtype, self.dtype)
 
         self.link.to_gpu()
         x_gpu = chainer.Variable(cuda.to_gpu(self.x))
         y_gpu = self.link(x_gpu)
-        self.assertEqual(y_gpu.data.dtype, numpy.float32)
+        self.assertEqual(y_gpu.data.dtype, self.dtype)
 
         testing.assert_allclose(y_cpu.data, y_gpu.data.get())
 
@@ -80,7 +86,7 @@ def test_forward_consistency_im2col(self):
     def check_backward(self, x_data, y_grad):
         gradient_check.check_backward(
             self.link, x_data, y_grad, (self.link.W, self.link.b),
-            eps=1e-2, atol=1e-3, rtol=1e-3)
+            **self.check_backward_options)
 
     @condition.retry(3)
     def test_backward_cpu(self):