From a5cb1e0b756414d21290d78bb8b8c88e31ab407f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 18:09:42 +0900 Subject: [PATCH 1/4] add Conv2DActiv --- chainercv/links/__init__.py | 2 + chainercv/links/connection/__init__.py | 1 + chainercv/links/connection/conv_2d_activ.py | 73 +++++++++++ docs/source/reference/links.rst | 7 + docs/source/reference/links/connection.rst | 9 ++ .../connection_tests/test_conv_2d_activ.py | 120 ++++++++++++++++++ 6 files changed, 212 insertions(+) create mode 100644 chainercv/links/connection/__init__.py create mode 100644 chainercv/links/connection/conv_2d_activ.py create mode 100644 docs/source/reference/links/connection.rst create mode 100644 tests/links_tests/connection_tests/test_conv_2d_activ.py diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index a454cd6969..befbcb2f1f 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -1,3 +1,5 @@ +from chainercv.links.connection.conv_2d_activ import Conv2DActiv + from chainercv.links.model.pixelwise_softmax_classifier import PixelwiseSoftmaxClassifier # NOQA from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA diff --git a/chainercv/links/connection/__init__.py b/chainercv/links/connection/__init__.py new file mode 100644 index 0000000000..215e41b6a6 --- /dev/null +++ b/chainercv/links/connection/__init__.py @@ -0,0 +1 @@ +from chainercv.links.connection.conv_2d_activ import Conv2DActiv # NOQA diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py new file mode 100644 index 0000000000..57d3d339f0 --- /dev/null +++ b/chainercv/links/connection/conv_2d_activ.py @@ -0,0 +1,73 @@ +import chainer +from chainer.functions import relu +from chainer.links import Convolution2D + + +class Conv2DActiv(chainer.Chain): + """Convolution2D --> Activation + + This is a chain that does two-dimensional convolution + and applies an activation. + + The arguments are the same as those of + :class:`chainer.links.Convolution2D` + except for :obj:`activ`. + + Example: + + There are sevaral ways to initialize a :class:`Conv2DActiv`. + + 1. Give the first three arguments explicitly: + + >>> l = Conv2DActiv(5, 10, 3) + + 2. Omit :obj:`in_channels` or fill it with :obj:`None`: + + In these ways, attributes are initialized at runtime based on + the channel size of the input. + + >>> l = Conv2DActiv(None, 10, 3) + >>> l = Conv2DActiv(10, 3) + + Args: + in_channels (int or None): Number of channels of input arrays. + If :obj:`None`, parameter initialization will be deferred until the + first forward data pass at which time the size will be determined. + out_channels (int): Number of channels of output arrays. + ksize (int or pair of ints): Size of filters (a.k.a. kernels). + :obj:`ksize=k` and :obj:`ksize=(k, k)` are equivalent. + stride (int or pair of ints): Stride of filter applications. + :obj:`stride=s` and :obj:`stride=(s, s)` are equivalent. + pad (int or pair of ints): Spatial padding width for input arrays. + :obj:`pad=p` and :obj:`pad=(p, p)` are equivalent. + nobias (bool): If :obj:`True`, + then this link does not use the bias term. + initialW (4-D array): Initial weight value. If :obj:`None`, the default + initializer is used. + May also be a callable that takes :obj:`numpy.ndarray` or + :obj:`cupy.ndarray` and edits its value. + initial_bias (1-D array): Initial bias value. If :obj:`None`, the bias + is set to 0. + May also be a callable that takes :obj:`numpy.ndarray` or + :obj:`cupy.ndarray` and edits its value. + activ (callable): An activation function. The default value is + :func:`chainer.functions.relu`. + + """ + + def __init__(self, in_channels, out_channels, ksize=None, + stride=1, pad=0, nobias=False, initialW=None, + initial_bias=None, activ=relu): + if ksize is None: + out_channels, ksize, in_channels = in_channels, out_channels, None + + self.activ = activ + super(Conv2DActiv, self).__init__() + with self.init_scope(): + self.conv = Convolution2D( + in_channels, out_channels, ksize, stride, pad, + nobias, initialW, initial_bias) + + def __call__(self, x): + h = self.conv(x) + return self.activ(h) diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 1d6f645349..5a7136ad97 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -34,3 +34,10 @@ Classifiers .. toctree:: links/classifier + + +Connection +---------- + +.. toctree:: + links/connection diff --git a/docs/source/reference/links/connection.rst b/docs/source/reference/links/connection.rst new file mode 100644 index 0000000000..f95e956e0d --- /dev/null +++ b/docs/source/reference/links/connection.rst @@ -0,0 +1,9 @@ +Connection +========== + +.. module:: chainercv.links.connection + + +Conv2DActiv +----------- +.. autoclass:: Conv2DActiv diff --git a/tests/links_tests/connection_tests/test_conv_2d_activ.py b/tests/links_tests/connection_tests/test_conv_2d_activ.py new file mode 100644 index 0000000000..ad061a736f --- /dev/null +++ b/tests/links_tests/connection_tests/test_conv_2d_activ.py @@ -0,0 +1,120 @@ +import unittest + +import numpy as np + +import chainer +from chainer import cuda +from chainer import testing +from chainer.testing import attr + +from chainercv.links import Conv2DActiv + + +def _add_one(x): + return x + 1 + + +@testing.parameterize( + {'args_style': 'explicit'}, + {'args_style': 'None'}, + {'args_style': 'omit'} +) +class TestConv2DActivForward(unittest.TestCase): + + in_channels = 1 + out_channels = 1 + ksize = 3 + stride = 1 + pad = 1 + + def setUp(self): + self.x = np.random.uniform( + -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) + + # Convolution is the identity function. + initialW = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], + dtype=np.float32).reshape(1, 1, 3, 3) + initial_bias = 0 + if self.args_style == 'explicit': + self.l = Conv2DActiv( + self.in_channels, self.out_channels, self.ksize, + self.stride, self.pad, + initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + elif self.args_style == 'None': + self.l = Conv2DActiv( + None, self.out_channels, self.ksize, self.stride, self.pad, + initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + elif self.args_style == 'omit': + self.l = Conv2DActiv( + self.out_channels, self.ksize, stride=self.stride, + pad=self.pad, initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + + def check_forward(self, x_data): + x = chainer.Variable(x_data) + y = self.l(x) + + self.assertIsInstance(y, chainer.Variable) + self.assertIsInstance(y.data, self.l.xp.ndarray) + + np.testing.assert_almost_equal( + cuda.to_cpu(y.data), cuda.to_cpu(x_data) + 1) + + def test_forward_cpu(self): + self.check_forward(self.x) + + @attr.gpu + def test_forward_gpu(self): + self.l.to_gpu() + self.check_forward(cuda.to_gpu(self.x)) + + +@testing.parameterize( + {'args_style': 'explicit'}, + {'args_style': 'None'}, + {'args_style': 'omit'} +) +class TestConv2DActivBackward(unittest.TestCase): + + in_channels = 3 + out_channels = 5 + ksize = 3 + stride = 1 + pad = 1 + + def setUp(self): + self.x = np.random.uniform( + -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) + self.gy = np.random.uniform( + -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) + + if self.args_style == 'explicit': + self.l = Conv2DActiv( + self.in_channels, self.out_channels, self.ksize, + self.stride, self.pad) + elif self.args_style == 'None': + self.l = Conv2DActiv( + None, self.out_channels, self.ksize, self.stride, self.pad) + elif self.args_style == 'omit': + self.l = Conv2DActiv( + self.out_channels, self.ksize, stride=self.stride, + pad=self.pad) + + def check_backward(self, x_data, y_grad): + x = chainer.Variable(x_data) + y = self.l(x) + y.grad = y_grad + y.backward() + + def test_backward_cpu(self): + self.check_backward(self.x, self.gy) + + @attr.gpu + def test_backward_gpu(self): + self.l.to_gpu() + self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.gy)) + + +testing.run_module(__name__, __file__) From 1041be33df6430a8dc3ca62a5c0630cc2cc3fe59 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 19:24:07 +0900 Subject: [PATCH 2/4] merge TestConv2DActivForward and TestConv2DActivBackward --- .../connection_tests/test_conv_2d_activ.py | 64 ++++++------------- 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/tests/links_tests/connection_tests/test_conv_2d_activ.py b/tests/links_tests/connection_tests/test_conv_2d_activ.py index ad061a736f..3baeeb37cf 100644 --- a/tests/links_tests/connection_tests/test_conv_2d_activ.py +++ b/tests/links_tests/connection_tests/test_conv_2d_activ.py @@ -4,6 +4,7 @@ import chainer from chainer import cuda +from chainer.functions import relu from chainer import testing from chainer.testing import attr @@ -14,12 +15,11 @@ def _add_one(x): return x + 1 -@testing.parameterize( - {'args_style': 'explicit'}, - {'args_style': 'None'}, - {'args_style': 'omit'} -) -class TestConv2DActivForward(unittest.TestCase): +@testing.parameterize(*testing.product({ + 'args_style': ['explicit', 'None', 'omit'], + 'activ': ['relu', 'add_one'] +})) +class TestConv2DActiv(unittest.TestCase): in_channels = 1 out_channels = 1 @@ -28,8 +28,14 @@ class TestConv2DActivForward(unittest.TestCase): pad = 1 def setUp(self): + if self.activ == 'relu': + activ = relu + elif self.activ == 'add_one': + activ = _add_one self.x = np.random.uniform( -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) + self.gy = np.random.uniform( + -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) # Convolution is the identity function. initialW = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], @@ -40,17 +46,17 @@ def setUp(self): self.in_channels, self.out_channels, self.ksize, self.stride, self.pad, initialW=initialW, initial_bias=initial_bias, - activ=_add_one) + activ=activ) elif self.args_style == 'None': self.l = Conv2DActiv( None, self.out_channels, self.ksize, self.stride, self.pad, initialW=initialW, initial_bias=initial_bias, - activ=_add_one) + activ=activ) elif self.args_style == 'omit': self.l = Conv2DActiv( self.out_channels, self.ksize, stride=self.stride, pad=self.pad, initialW=initialW, initial_bias=initial_bias, - activ=_add_one) + activ=activ) def check_forward(self, x_data): x = chainer.Variable(x_data) @@ -59,8 +65,12 @@ def check_forward(self, x_data): self.assertIsInstance(y, chainer.Variable) self.assertIsInstance(y.data, self.l.xp.ndarray) - np.testing.assert_almost_equal( - cuda.to_cpu(y.data), cuda.to_cpu(x_data) + 1) + if self.activ == 'relu': + np.testing.assert_almost_equal( + cuda.to_cpu(y.data), np.maximum(cuda.to_cpu(x_data), 0)) + elif self.activ == 'add_one': + np.testing.assert_almost_equal( + cuda.to_cpu(y.data), cuda.to_cpu(x_data) + 1) def test_forward_cpu(self): self.check_forward(self.x) @@ -70,38 +80,6 @@ def test_forward_gpu(self): self.l.to_gpu() self.check_forward(cuda.to_gpu(self.x)) - -@testing.parameterize( - {'args_style': 'explicit'}, - {'args_style': 'None'}, - {'args_style': 'omit'} -) -class TestConv2DActivBackward(unittest.TestCase): - - in_channels = 3 - out_channels = 5 - ksize = 3 - stride = 1 - pad = 1 - - def setUp(self): - self.x = np.random.uniform( - -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) - self.gy = np.random.uniform( - -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) - - if self.args_style == 'explicit': - self.l = Conv2DActiv( - self.in_channels, self.out_channels, self.ksize, - self.stride, self.pad) - elif self.args_style == 'None': - self.l = Conv2DActiv( - None, self.out_channels, self.ksize, self.stride, self.pad) - elif self.args_style == 'omit': - self.l = Conv2DActiv( - self.out_channels, self.ksize, stride=self.stride, - pad=self.pad) - def check_backward(self, x_data, y_grad): x = chainer.Variable(x_data) y = self.l(x) From 6dc5db3089a0f7df09635c219efab170342da387 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 19:25:18 +0900 Subject: [PATCH 3/4] change doc of conv_2d_activ --- chainercv/links/connection/conv_2d_activ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 57d3d339f0..baec848104 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -26,8 +26,8 @@ class Conv2DActiv(chainer.Chain): In these ways, attributes are initialized at runtime based on the channel size of the input. - >>> l = Conv2DActiv(None, 10, 3) >>> l = Conv2DActiv(10, 3) + >>> l = Conv2DActiv(None, 10, 3) Args: in_channels (int or None): Number of channels of input arrays. From 11d4c40bf4092ca323cce6fb871d0ae148a98c72 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 19:26:20 +0900 Subject: [PATCH 4/4] add NOQA --- chainercv/links/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index befbcb2f1f..c49e6d6b54 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -1,4 +1,4 @@ -from chainercv.links.connection.conv_2d_activ import Conv2DActiv +from chainercv.links.connection.conv_2d_activ import Conv2DActiv # NOQA from chainercv.links.model.pixelwise_softmax_classifier import PixelwiseSoftmaxClassifier # NOQA from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA