Source code for chainer.testing.unary_math_function_test

import numpy
import unittest

import chainer
from chainer import cuda
from chainer.testing import attr
from chainer.testing import condition


def _make_data_default(shape, dtype):
    x = numpy.random.uniform(-1, 1, shape).astype(dtype)
    gy = numpy.random.uniform(-1, 1, shape).astype(dtype)
    return x, gy


[docs]def unary_math_function_unittest(func, func_expected=None, label_expected=None, make_data=None): """Decorator for testing unary mathematical Chainer functions. This decorator makes test classes test unary mathematical Chainer functions. Tested are forward and backward computations on CPU and GPU across parameterized ``shape`` and ``dtype``. Args: func(~chainer.Function): Chainer function to be tested by the decorated test class. func_expected: Function used to provide expected values for testing forward computation. If not given, a corresponsing numpy function for ``func`` is implicitly picked up by its class name. label_expected(string): String used to test labels of Chainer functions. If not given, the class name of ``func`` lowered is implicitly used. make_data: Function to customize input and gradient data used in the tests. It takes ``shape`` and ``dtype`` as its arguments, and returns a tuple of input and gradient data. By default, uniform destribution ranged ``[-1, 1]`` is used for both. The decorated test class tests forward and backward computations on CPU and GPU across the following :func:`~chainer.testing.parameterize` ed parameters: - shape: rank of zero, and rank of more than zero - dtype: ``numpy.float16``, ``numpy.float32`` and ``numpy.float64`` Additionally, it tests the label of the Chainer function. Chainer functions tested by the test class decorated with the decorator should have the following properties: - Unary, taking one parameter and returning one value - ``dtype`` of input and output are the same - Elementwise operation for the supplied ndarray .. admonition:: Example The following code defines a test class that tests :func:`~chainer.functions.sin` Chainer function, which takes a parameter with ``dtype`` of float and returns a value with the same ``dtype``. .. doctest:: >>> import unittest >>> from chainer import testing >>> from chainer import functions as F >>> >>> @testing.unary_math_function_unittest(F.Sin()) ... class TestSin(unittest.TestCase): ... pass Because the test methods are implicitly injected to ``TestSin`` class by the decorator, it is enough to place ``pass`` in the class definition. Now the test is run with ``nose`` module. .. doctest:: >>> import nose >>> nose.run( ... defaultTest=__name__, argv=['', '-a', '!gpu'], exit=False) True To customize test data, ``make_data`` optional parameter can be used. The following is an example of testing ``sqrt`` Chainer function, which is tested in positive value domain here instead of the default input. .. doctest:: >>> import numpy >>> >>> def make_data(shape, dtype): ... x = numpy.random.uniform(0.1, 1, shape).astype(dtype) ... gy = numpy.random.uniform(-1, 1, shape).astype(dtype) ... return x, gy ... >>> @testing.unary_math_function_unittest(F.Sqrt(), ... make_data=make_data) ... class TestSqrt(unittest.TestCase): ... pass ... >>> nose.run( ... defaultTest=__name__, argv=['', '-a', '!gpu'], exit=False) True ``make_data`` function which returns input and gradient data generated in proper value domains with given ``shape`` and ``dtype`` parameters is defined, then passed to the decorator's ``make_data`` parameter. """ # TODO(takagi) In the future, the Chainer functions that could be tested # with the decorator would be extended as: # # - Multiple input parameters # - Multiple output values # - Other types than float: integer # - Other operators other than analytic math: basic math # Import here to avoid mutual import. from chainer import gradient_check from chainer import testing if func_expected is None: name = func.__class__.__name__.lower() try: func_expected = getattr(numpy, name) except AttributeError: raise ValueError("NumPy has no functions corresponding " "to Chainer function '{}'.".format(name)) if label_expected is None: label_expected = func.__class__.__name__.lower() if make_data is None: make_data = _make_data_default def f(klass): assert issubclass(klass, unittest.TestCase) def setUp(self): self.x, self.gy = make_data(self.shape, self.dtype) if self.dtype == numpy.float16: self.backward_options = { 'eps': 2 ** -4, 'atol': 2 ** -4, 'rtol': 2 ** -4, 'dtype': numpy.float64} else: self.backward_options = {'atol': 1e-4, 'rtol': 1e-4} setattr(klass, "setUp", setUp) def check_forward(self, x_data): x = chainer.Variable(x_data) y = func(x) self.assertEqual(y.data.dtype, x_data.dtype) y_expected = func_expected(cuda.to_cpu(x_data), dtype=x_data.dtype) testing.assert_allclose(y_expected, y.data, atol=1e-4, rtol=1e-4) setattr(klass, "check_forward", check_forward) @condition.retry(3) def test_forward_cpu(self): self.check_forward(self.x) setattr(klass, "test_forward_cpu", test_forward_cpu) @attr.gpu @condition.retry(3) def test_forward_gpu(self): self.check_forward(cuda.to_gpu(self.x)) setattr(klass, "test_forward_gpu", test_forward_gpu) def check_backward(self, x_data, y_grad): gradient_check.check_backward( func, x_data, y_grad, **self.backward_options) setattr(klass, "check_backward", check_backward) @condition.retry(3) def test_backward_cpu(self): self.check_backward(self.x, self.gy) setattr(klass, "test_backward_cpu", test_backward_cpu) @attr.gpu @condition.retry(3) def test_backward_gpu(self): self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.gy)) setattr(klass, "test_backward_gpu", test_backward_gpu) def test_label(self): self.assertEqual(func.label, label_expected) setattr(klass, "test_label", test_label) # Return parameterized class. return testing.parameterize(*testing.product({ 'shape': [(3, 2), ()], 'dtype': [numpy.float16, numpy.float32, numpy.float64] }))(klass) return f