diff --git a/keras/layers/convolutional.py b/keras/layers/convolutional.py index 7d2575488ff..4a470f1acc7 100644 --- a/keras/layers/convolutional.py +++ b/keras/layers/convolutional.py @@ -651,8 +651,11 @@ class Conv2DTranspose(Conv2D): padding: one of `"valid"` or `"same"` (case-insensitive). output_padding: An integer or tuple/list of 2 integers, specifying the amount of padding along the height and width - of the output tensor. Can be a single integer to specify the - same value for all spatial dimensions. + of the output tensor. + Can be a single integer to specify the same value for all + spatial dimensions. + The amount of output padding along a givern dimension must be + lower than the stride along that same dimension. If set to `None`, defaults to 1 if padding is `"same"` and to 0 if padding is '"valid"'. data_format: A string, @@ -749,9 +752,15 @@ def __init__(self, filters, bias_constraint=bias_constraint, **kwargs) self.input_spec = InputSpec(ndim=4) + self.output_padding = output_padding if self.output_padding is not None: self.output_padding = conv_utils.normalize_tuple(self.output_padding, 2, 'output_padding') + for stride, out_pad in zip(self.strides, self.output_padding): + if out_pad >= stride: + raise ValueError('Stride ' + str(self.strides) + ' must be ' + + 'greater than output padding ' + + str(self.output_padding)) def build(self, input_shape): if len(input_shape) != 4: @@ -901,8 +910,10 @@ class Conv3DTranspose(Conv3D): output_padding: An integer or tuple/list of 3 integers, specifying the amount of padding along the depth, height, and width. - Can be a single integer to specify the same value for - all spatial dimensions. + Can be a single integer to specify the same value for all + spatial dimensions. + The amount of output padding along a givern dimension must be + lower than the stride along that same dimension. If set to `None`, defaults to 1 if padding is `"same"` and to 0 if padding is '"valid"'. data_format: A string, @@ -1001,6 +1012,11 @@ def __init__(self, filters, self.output_padding = output_padding if self.output_padding is not None: self.output_padding = conv_utils.normalize_tuple(self.output_padding, 3, 'output_padding') + for stride, out_pad in zip(self.strides, self.output_padding): + if out_pad >= stride: + raise ValueError('Stride ' + str(self.strides) + ' must be ' + + 'greater than output padding ' + + str(self.output_padding)) def build(self, input_shape): if len(input_shape) != 5: diff --git a/tests/keras/layers/convolutional_test.py b/tests/keras/layers/convolutional_test.py index 943cd12965c..04793ce8f37 100644 --- a/tests/keras/layers/convolutional_test.py +++ b/tests/keras/layers/convolutional_test.py @@ -194,11 +194,13 @@ def test_conv2d_transpose(): num_col = 6 for padding in _convolution_paddings: - for out_padding in [None, (1, 1)]: + for out_padding in [None, (0, 0), (1, 1)]: for strides in [(1, 1), (2, 2)]: if padding == 'same' and strides != (1, 1): continue - layer_test(convolutional.Deconvolution2D, + if strides == (1, 1) and out_padding == (1, 1): + continue + layer_test(convolutional.Conv2DTranspose, kwargs={'filters': filters, 'kernel_size': 3, 'padding': padding, @@ -208,7 +210,7 @@ def test_conv2d_transpose(): input_shape=(num_samples, num_row, num_col, stack_size), fixed_batch_size=True) - layer_test(convolutional.Deconvolution2D, + layer_test(convolutional.Conv2DTranspose, kwargs={'filters': filters, 'kernel_size': 3, 'padding': padding, @@ -230,6 +232,24 @@ def test_conv2d_transpose(): padding=padding, batch_input_shape=(None, None, 5, None))]) + # Test invalid output padding for given stride. Output padding equal + # to stride + with pytest.raises(ValueError): + model = Sequential([convolutional.Conv2DTranspose(filters=filters, + kernel_size=3, + padding=padding, + output_padding=(0, 3), + strides=(1, 3), + batch_input_shape=(None, num_row, num_col, stack_size))]) + # Output padding greater than stride + with pytest.raises(ValueError): + model = Sequential([convolutional.Conv2DTranspose(filters=filters, + kernel_size=3, + padding=padding, + output_padding=(2, 2), + strides=(1, 3), + batch_input_shape=(None, num_row, num_col, stack_size))]) + @keras_test def test_separable_conv_1d(): @@ -461,20 +481,18 @@ def test_convolution_3d(): input_len_dim3 = 8 for padding in _convolution_paddings: - for out_padding in [None, (1, 1, 1)]: - for strides in [(1, 1, 1), (2, 2, 2)]: - if padding == 'same' and strides != (1, 1, 1): - continue + for strides in [(1, 1, 1), (2, 2, 2)]: + if padding == 'same' and strides != (1, 1, 1): + continue - layer_test(convolutional.Convolution3D, - kwargs={'filters': filters, - 'kernel_size': 3, - 'padding': padding, - 'output_padding': out_padding, - 'strides': strides}, - input_shape=(num_samples, - input_len_dim1, input_len_dim2, input_len_dim3, - stack_size)) + layer_test(convolutional.Convolution3D, + kwargs={'filters': filters, + 'kernel_size': 3, + 'padding': padding, + 'strides': strides}, + input_shape=(num_samples, + input_len_dim1, input_len_dim2, input_len_dim3, + stack_size)) layer_test(convolutional.Convolution3D, kwargs={'filters': filters, @@ -501,18 +519,22 @@ def test_conv3d_transpose(): num_col = 6 for padding in _convolution_paddings: - for strides in [(1, 1, 1), (2, 2, 2)]: - for data_format in ['channels_first', 'channels_last']: - if padding == 'same' and strides != (1, 1, 1): - continue - layer_test(convolutional.Conv3DTranspose, - kwargs={'filters': filters, - 'kernel_size': 3, - 'padding': padding, - 'strides': strides, - 'data_format': data_format}, - input_shape=(None, num_depth, num_row, num_col, stack_size), - fixed_batch_size=True) + for out_padding in [None, (0, 0, 0), (1, 1, 1)]: + for strides in [(1, 1, 1), (2, 2, 2)]: + for data_format in ['channels_first', 'channels_last']: + if padding == 'same' and strides != (1, 1, 1): + continue + if strides == (1, 1, 1) and out_padding == (1, 1, 1): + continue + layer_test(convolutional.Conv3DTranspose, + kwargs={'filters': filters, + 'kernel_size': 3, + 'padding': padding, + 'output_padding': out_padding, + 'strides': strides, + 'data_format': data_format}, + input_shape=(None, num_depth, num_row, num_col, stack_size), + fixed_batch_size=True) layer_test(convolutional.Conv3DTranspose, kwargs={'filters': filters, @@ -536,6 +558,24 @@ def test_conv3d_transpose(): padding=padding, batch_input_shape=(None, None, 5, None, None))]) + # Test invalid output padding for given stride. Output padding equal + # to stride + with pytest.raises(ValueError): + model = Sequential([convolutional.Conv3DTranspose(filters=filters, + kernel_size=3, + padding=padding, + output_padding=(0, 3, 3), + strides=(1, 3, 4), + batch_input_shape=(None, num_depth, num_row, num_col, stack_size))]) + # Output padding greater than stride + with pytest.raises(ValueError): + model = Sequential([convolutional.Conv3DTranspose(filters=filters, + kernel_size=3, + padding=padding, + output_padding=(2, 2, 3), + strides=(1, 3, 4), + batch_input_shape=(None, num_depth, num_row, num_col, stack_size))]) + @keras_test def test_maxpooling_3d():