Repository: chainer/chainer

Issue:
`L.Deconvolution2D` lacks `dilate` option
although `F.deconvolution_2d` already supports it.


Discussion:

Resolving patch:
diff --git a/chainer/links/connection/deconvolution_2d.py b/chainer/links/connection/deconvolution_2d.py
--- a/chainer/links/connection/deconvolution_2d.py
+++ b/chainer/links/connection/deconvolution_2d.py
@@ -10,7 +10,7 @@
 
 class Deconvolution2D(link.Link):
 
-    """__init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, nobias=False, outsize=None, initialW=None, initial_bias=None, *, groups=1)
+    """__init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, nobias=False, outsize=None, initialW=None, initial_bias=None, *, dilate=1, groups=1)
 
     Two dimensional deconvolution function.
 
@@ -45,6 +45,9 @@ class Deconvolution2D(link.Link):
         initial_bias (:ref:`initializer <initializer>`): Initializer to
             initialize the bias. If ``None``, the bias will be initialized to
             zero. When it is :class:`numpy.ndarray`, its ``ndim`` should be 1.
+        dilate (:class:`int` or :class:`tuple` of :class:`int` s):
+            Dilation factor of filter applications.
+            ``dilate=d`` and ``dilate=(d, d)`` are equivalent.
         groups (int): The number of groups to use grouped deconvolution. The
             default is one, where grouped deconvolution is not used.
 
@@ -125,8 +128,8 @@ def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0,
                  **kwargs):
         super(Deconvolution2D, self).__init__()
 
-        groups, = argument.parse_kwargs(
-            kwargs, ('groups', 1),
+        dilate, groups, = argument.parse_kwargs(
+            kwargs, ('dilate', 1), ('groups', 1),
             deterministic="deterministic argument is not supported anymore. "
             "Use chainer.using_config('cudnn_deterministic', value) "
             "context where value is either `True` or `False`.")
@@ -137,6 +140,7 @@ def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0,
         self.ksize = ksize
         self.stride = _pair(stride)
         self.pad = _pair(pad)
+        self.dilate = _pair(dilate)
         self.outsize = (None, None) if outsize is None else outsize
         self.out_channels = out_channels
         self.groups = int(groups)
@@ -173,7 +177,7 @@ def forward(self, x):
             self._initialize_params(x.shape[1])
         return deconvolution_2d.deconvolution_2d(
             x, self.W, self.b, self.stride, self.pad, self.outsize,
-            groups=self.groups)
+            dilate=self.dilate, groups=self.groups)
 
 
 def _pair(x):


Test patch:
diff --git a/tests/chainer_tests/links_tests/connection_tests/test_deconvolution_2d.py b/tests/chainer_tests/links_tests/connection_tests/test_deconvolution_2d.py
--- a/tests/chainer_tests/links_tests/connection_tests/test_deconvolution_2d.py
+++ b/tests/chainer_tests/links_tests/connection_tests/test_deconvolution_2d.py
@@ -21,20 +21,22 @@ def _pair(x):
 @parameterize(
     *testing.product({
         'nobias': [True, False],
-        'use_cudnn': ['always', 'never']
+        'use_cudnn': ['always', 'never'],
+        'dilate': [1, 2],
+        'groups': [1, 3],
     })
 )
 class TestDeconvolution2D(unittest.TestCase):
 
     def setUp(self):
         in_channels = 3
-        out_channels = 2
+        out_channels = 6
         ksize = 3
         stride = 2
         pad = 1
         self.link = L.Deconvolution2D(
-            in_channels, out_channels, ksize,
-            stride=stride, pad=pad, nobias=self.nobias)
+            in_channels, out_channels, ksize, stride=stride, pad=pad,
+            nobias=self.nobias, dilate=self.dilate, groups=self.groups)
         self.link.W.data[...] = numpy.random.uniform(
             -1, 1, self.link.W.data.shape).astype(numpy.float32)
         if not self.nobias:
@@ -46,8 +48,8 @@ def setUp(self):
         N = 2
         h, w = 3, 2
         kh, kw = _pair(ksize)
-        out_h = conv.get_deconv_outsize(h, kh, stride, pad)
-        out_w = conv.get_deconv_outsize(w, kw, stride, pad)
+        out_h = conv.get_deconv_outsize(h, kh, stride, pad, d=self.dilate)
+        out_w = conv.get_deconv_outsize(w, kw, stride, pad, d=self.dilate)
         self.gy = numpy.random.uniform(
             -1, 1, (N, out_channels, out_h, out_w)).astype(numpy.float32)
         self.x = numpy.random.uniform(