Repository: chainer/chainer

Issue:
Refactor utility link in optimizer_hooks unit tests
`chainer_tests/optimizer_hooks_tests` uses similar dummy links, which can be refactored to reduce repetition.


Discussion:

Resolving patch:
diff --git a/chainer/optimizer_hooks/gradient_hard_clipping.py b/chainer/optimizer_hooks/gradient_hard_clipping.py
--- a/chainer/optimizer_hooks/gradient_hard_clipping.py
+++ b/chainer/optimizer_hooks/gradient_hard_clipping.py
@@ -52,7 +52,7 @@ def __call__(self, rule, param):
             # supports kwarg `out`.
             if xp == backend.chainerx \
                     or isinstance(param.grad, backend.intel64.mdarray):
-                grad[:] = grad.clip(self.lower_bound, self.upper_bound)
+                grad[...] = grad.clip(self.lower_bound, self.upper_bound)
             else:
                 # Save on new object allocation when using numpy and cupy
                 # using kwarg `out`


Test patch:
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_clipping.py b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_clipping.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_clipping.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_clipping.py
@@ -8,6 +8,8 @@
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
 
 _backend_params = [
     # NumPy
@@ -38,21 +40,10 @@ def __init__(self, params):
 class TestGradientClipping(unittest.TestCase):
 
     def setUp(self):
-        num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        params = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params.append(param)
-
-        self.target = SimpleLink(params)
-        self.norm = math.sqrt(sum([g.ravel().dot(g.ravel()) for g in grads]))
+        self.target = utils.ParametersLink.from_param_props(
+            ((2, 3), (2, 0, 1), ()))
+        self.norm = math.sqrt(sum([
+            np.square(param.grad).sum() for param in self.target.params()]))
 
     def check_clipping(self, backend_configs, rate):
         target = self.target
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_hard_clipping.py b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_hard_clipping.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_hard_clipping.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_hard_clipping.py
@@ -2,11 +2,13 @@
 
 import numpy as np
 
-import chainer
 from chainer import optimizer_hooks
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
+
 _backend_params = [
     # NumPy
     {},
@@ -21,35 +23,14 @@
 ]
 
 
-class SimpleLink(chainer.Link):
-
-    def __init__(self, params):
-        super(SimpleLink, self).__init__()
-        with self.init_scope():
-            for i, p in enumerate(params):
-                setattr(self, 'p{}'.format(i), p)
-
-
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 class TestGradientHardClipping(unittest.TestCase):
 
     def setUp(self):
-        num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        params = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params.append(param)
-
-        self.target = SimpleLink(params)
+        self.target = utils.ParametersLink.from_param_props(
+            ((2, 3), (2, 0, 1), ()))
 
     def check_hardclipping(self, backend_configs):
         target = self.target
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_lars.py b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_lars.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_lars.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_lars.py
@@ -7,6 +7,8 @@
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
 
 _backend_params = [
     # NumPy
@@ -22,49 +24,19 @@
 ]
 
 
-class SimpleLink(chainer.Link):
-
-    def __init__(self, params):
-        super(SimpleLink, self).__init__()
-        with self.init_scope():
-            for i, p in enumerate(params):
-                setattr(self, 'p{}'.format(i), p)
-
-
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 class TestGradientLARS(unittest.TestCase):
 
     def setUp(self):
-        num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        params_0 = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params_0.append(param)
-
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32) * 0.0001
-            for _ in range(num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        params_1 = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params_1.append(param)
-
-        self.target = chainer.ChainList(
-            SimpleLink(params_0),
-            SimpleLink(params_1))
+        link1 = utils.ParametersLink.from_param_props(
+            ((2, 3), (2, 0, 1), (0,)))
+        link2 = utils.ParametersLink.from_param_props(
+            ((5, 0, 1), (0,), (7, 3)))
+        for param in link2.params():
+            param.array[...] *= 0.0001
+        self.target = chainer.ChainList(link1, link2)
 
     def check_LARS(self, backend_configs):
         target = self.target
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_noise.py b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_noise.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_gradient_noise.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_gradient_noise.py
@@ -4,12 +4,12 @@
 import mock
 import numpy as np
 
-
-import chainer
 from chainer import optimizer_hooks
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
 
 _backend_params = [
     # NumPy
@@ -25,15 +25,6 @@
 ]
 
 
-class SimpleLink(chainer.Link):
-
-    def __init__(self, params):
-        super(SimpleLink, self).__init__()
-        with self.init_scope():
-            for i, p in enumerate(params):
-                setattr(self, 'p{}'.format(i), p)
-
-
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
@@ -42,20 +33,9 @@ class TestGradientNoise(unittest.TestCase):
     eta = 0.01
 
     def setUp(self):
-        self.num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(self.num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(self.num_params)]
-        params = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params.append(param)
-
-        self.target = SimpleLink(params)
+        self.target = utils.ParametersLink.from_param_props(
+            # TODO(niboshi): Use different shapes
+            ((2, 3), (2, 3), (2, 3)))
 
         self.noise_value = np.random.normal(
             loc=0, scale=np.sqrt(self.eta / np.power(1, 0.55)),
@@ -87,7 +67,7 @@ def test_noise(xp, shape, dtype, hook, opt):
         for expect, param in zip(expects, target.params()):
             testing.assert_allclose(expect, param.array)
 
-        self.assertEqual(noise.call_count, self.num_params)
+        self.assertEqual(noise.call_count, len(tuple(self.target.params())))
 
         calls = []
         for param in target.params():
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_lasso.py b/tests/chainer_tests/optimizer_hooks_tests/test_lasso.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_lasso.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_lasso.py
@@ -2,11 +2,12 @@
 
 import numpy as np
 
-import chainer
 from chainer import optimizer_hooks
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
 
 _backend_params = [
     # NumPy
@@ -22,34 +23,13 @@
 ]
 
 
-class SimpleLink(chainer.Link):
-
-    def __init__(self, params):
-        super(SimpleLink, self).__init__()
-        with self.init_scope():
-            for i, p in enumerate(params):
-                setattr(self, 'p{}'.format(i), p)
-
-
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 class TestLasso(unittest.TestCase):
     def setUp(self):
-        num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(num_params)]
-        params = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params.append(param)
-
-        self.target = SimpleLink(params)
+        self.target = utils.ParametersLink.from_param_props(
+            ((2, 3), (2, 0, 1), ()))
 
     def check_lasso(self, backend_configs):
         target = self.target
@@ -67,7 +47,7 @@ def check_lasso(self, backend_configs):
 
         # Compute using optimizer_hook
         opt = optimizers.SGD(lr=1)
-        opt.setup(self.target)
+        opt.setup(target)
         opt.add_hook(optimizer_hooks.Lasso(decay))
         opt.update()
 
diff --git a/tests/chainer_tests/optimizer_hooks_tests/test_weight_decay.py b/tests/chainer_tests/optimizer_hooks_tests/test_weight_decay.py
--- a/tests/chainer_tests/optimizer_hooks_tests/test_weight_decay.py
+++ b/tests/chainer_tests/optimizer_hooks_tests/test_weight_decay.py
@@ -8,6 +8,8 @@
 from chainer import optimizers
 from chainer import testing
 
+import utils
+
 
 _backend_params = [
     # NumPy
@@ -23,35 +25,14 @@
 ]
 
 
-class SimpleLink(chainer.Link):
-
-    def __init__(self, params):
-        super(SimpleLink, self).__init__()
-        with self.init_scope():
-            for i, p in enumerate(params):
-                setattr(self, 'p{}'.format(i), p)
-
-
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 @testing.backend.inject_backend_tests(None, _backend_params)
 class TestWeightDecay(unittest.TestCase):
 
     def setUp(self):
-        self.num_params = 3
-        arrs = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(self.num_params)]
-        grads = [
-            np.random.uniform(-3, 3, (2, 3)).astype(np.float32)
-            for _ in range(self.num_params)]
-        params = []
-        for arr, grad in zip(arrs, grads):
-            param = chainer.Parameter(arr)
-            param.grad = grad
-            params.append(param)
-
-        self.target = SimpleLink(params)
+        self.target = utils.ParametersLink.from_param_props(
+            ((2, 3), (2, 0, 1), ()))
 
     def check_weight_decay(self, backend_configs):
         target = self.target
diff --git a/tests/chainer_tests/optimizer_hooks_tests/utils.py b/tests/chainer_tests/optimizer_hooks_tests/utils.py
new file mode 100644
--- /dev/null
+++ b/tests/chainer_tests/optimizer_hooks_tests/utils.py
@@ -0,0 +1,39 @@
+import numpy
+
+import chainer
+
+
+class ParametersLink(chainer.Link):
+    '''Link with specific parameters.'''
+
+    def __init__(self, params):
+        super(ParametersLink, self).__init__()
+        with self.init_scope():
+            for i, p in enumerate(params):
+                setattr(self, 'p{}'.format(i), p)
+
+    @staticmethod
+    def from_param_props(shapes, dtypes=numpy.float32):
+        # Creates a ParameterLink from the given parameter properties.
+        assert isinstance(shapes, (tuple, list))
+        assert all(isinstance(s, tuple) for s in shapes)
+
+        n_params = len(shapes)
+
+        if not isinstance(dtypes, (tuple, list)):
+            dtypes = (dtypes,) * n_params
+
+        arrs = [
+            numpy.random.uniform(-3, 3, shape).astype(dtype)
+            for shape, dtype in zip(shapes, dtypes)]
+        grads = [
+            numpy.random.uniform(-3, 3, shape).astype(dtype)
+            for shape, dtype in zip(shapes, dtypes)]
+
+        params = []
+        for arr, grad in zip(arrs, grads):
+            param = chainer.Parameter(arr)
+            param.grad = grad
+            params.append(param)
+
+        return ParametersLink(params)
diff --git a/tests/chainer_tests/test_runnable.py b/tests/chainer_tests/test_runnable.py
--- a/tests/chainer_tests/test_runnable.py
+++ b/tests/chainer_tests/test_runnable.py
@@ -1,5 +1,6 @@
 import io
 import os
+import re
 import unittest
 
 from chainer import testing
@@ -9,9 +10,10 @@ class TestRunnable(unittest.TestCase):
 
     def test_runnable(self):
         cwd = os.path.dirname(__file__)
+        regex = re.compile(r'^test_.*\.py$')
         for dirpath, dirnames, filenames in os.walk(cwd):
             for filename in filenames:
-                if not filename.endswith('.py') or '__init__' in filename:
+                if not regex.match(filename):
                     continue
                 path = os.path.join(dirpath, filename)
                 with io.open(path, encoding='utf-8') as f: