def test_same_padding_corr(self): for ishape in [[10], [11]]: inputs = np.zeros(ishape, dtype=np.float32) inputs[len(inputs) // 2] = 1 for kshape in [[4], [5]]: kernel = np.zeros(kshape, dtype=np.float32) kernel[len(kernel) // 2] = 1 outputs = tf.nn.convolution(tf.reshape(inputs, (1, 1, -1, 1)), tf.reshape(kernel, (1, -1, 1, 1)), padding="VALID", data_format="NHWC") outputs = np.squeeze(outputs.numpy()) pos_inp = np.squeeze(np.nonzero(inputs > .5)) pos_out = np.squeeze(np.nonzero(outputs > .5)) padding = padding_ops.same_padding_for_kernel(kshape, True) self.assertEqual(padding[0][0], pos_inp - pos_out)
def test_same_padding_corr(self): for ishape in [[10], [11]]: inputs = np.zeros(ishape, dtype=np.float32) inputs[len(inputs) // 2] = 1 for kshape in [[4], [5]]: kernel = np.zeros(kshape, dtype=np.float32) kernel[len(kernel) // 2] = 1 outputs = tf.nn.convolution( tf.reshape(inputs, (1, 1, -1, 1)), tf.reshape(kernel, (1, -1, 1, 1)), padding="VALID", data_format="NHWC") with self.test_session() as sess: outputs = np.squeeze(sess.run(outputs)) pos_inp = np.squeeze(np.nonzero(inputs)) pos_out = np.squeeze(np.nonzero(outputs)) padding = padding_ops.same_padding_for_kernel(kshape, True) self.assertEqual(padding[0][0], pos_inp - pos_out)
def test_same_padding_conv(self): for ishape in [[10], [11]]: inputs = np.zeros(ishape, dtype=np.float32) inputs[len(inputs) // 2] = 1 for kshape in [[4], [5]]: kernel = np.zeros(kshape, dtype=np.float32) kernel[len(kernel) // 2] = 1 outputs = tf.nn.conv2d_transpose(tf.reshape(inputs, (1, 1, -1, 1)), tf.reshape(kernel, (1, -1, 1, 1)), (1, 1, ishape[0] + kshape[0] - 1, 1), strides=(1, 1, 1, 1), padding="VALID", data_format="NHWC") outputs = outputs[:, :, (kshape[0] - 1):-(kshape[0] - 1), :] with self.test_session() as sess: outputs = np.squeeze(sess.run(outputs)) pos_inp = np.squeeze(np.nonzero(inputs)) pos_out = np.squeeze(np.nonzero(outputs)) padding = padding_ops.same_padding_for_kernel(kshape, False) self.assertEqual(padding[0][0], pos_inp - pos_out)
def test_same_padding_conv(self): for ishape in [[10], [11]]: inputs = np.zeros(ishape, dtype=np.float32) inputs[len(inputs) // 2] = 1 for kshape in [[4], [5]]: kernel = np.zeros(kshape, dtype=np.float32) kernel[len(kernel) // 2] = 1 outputs = tf.nn.conv2d_transpose( tf.reshape(inputs, (1, 1, -1, 1)), tf.reshape(kernel, (1, -1, 1, 1)), (1, 1, ishape[0] + kshape[0] - 1, 1), strides=(1, 1, 1, 1), padding="VALID", data_format="NHWC") outputs = outputs[:, :, (kshape[0] - 1):-(kshape[0] - 1), :] with self.test_session() as sess: outputs = np.squeeze(sess.run(outputs)) pos_inp = np.squeeze(np.nonzero(inputs)) pos_out = np.squeeze(np.nonzero(outputs)) padding = padding_ops.same_padding_for_kernel(kshape, False) self.assertEqual(padding[0][0], pos_inp - pos_out)
def call(self, inputs): inputs = tf.convert_to_tensor(inputs) outputs = inputs # Not for all possible combinations of (`kernel_support`, `corr`, # `strides_up`, `strides_down`) TF ops exist. We implement some additional # combinations by manipulating the kernels and toggling `corr`. kernel = self.kernel corr = self.corr # If a convolution with no upsampling is desired, we flip the kernels and # use cross correlation to implement it, provided the kernels are odd-length # in every dimension (with even-length kernels, the boundary handling # would have to change). if (not corr and all(s == 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = True slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] # Similarly, we can implement a cross correlation using convolutions. # However, we do this only if upsampling is requested, as we are potentially # wasting computation in the boundaries whenever we call the transpose ops. elif (corr and any(s != 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = False slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] # Compute amount of necessary padding, and determine whether to use built-in # padding or to pre-pad with a separate op. if self.padding == "valid" or all(s == 1 for s in self.kernel_support): prepadding = self._rank * [[0, 0]] else: # same_* padding = padding_ops.same_padding_for_kernel( self.kernel_support, corr, self.strides_up) # Pre-pad and then use built-in valid padding mode. outputs = tf.pad(outputs, self._padded_tuple(padding, (0, 0)), self._pad_mode) prepadding = padding # Compute the convolution/correlation. if corr and all(s == 1 for s in self.strides_up): outputs = self._correlate_down_valid(outputs, kernel) elif not corr: outputs = self._up_convolve_transpose_valid( outputs, kernel, prepadding) else: self._raise_notimplemented() # Now, add bias if requested. if self.bias is not None: bias = self.bias if self.data_format == "channels_first": # As of Mar 2017, direct addition is significantly slower than # bias_add when computing gradients. if self._rank == 1: # tf.nn.bias_add does not accept a 1D input tensor. outputs = tf.expand_dims(outputs, 2) outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") outputs = tf.squeeze(outputs, [2]) elif self._rank == 2: outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") elif self._rank >= 3: shape = tf.shape(outputs) outputs = tf.reshape(outputs, tf.concat([shape[:3], [-1]], axis=0)) outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") outputs = tf.reshape(outputs, shape) else: outputs = tf.nn.bias_add(outputs, bias) # Finally, pass through activation function if requested. if self.activation is not None: outputs = self.activation(outputs) # pylint:disable=not-callable # Aid shape inference, for some reason shape info is not always available. if not tf.executing_eagerly(): outputs.set_shape(self.compute_output_shape(inputs.shape)) return outputs
def call(self, inputs) -> tf.Tensor: if inputs.shape.rank != self._rank + 2: raise ValueError(f"Input tensor must have rank {self._rank + 2}, " f"received shape {inputs.shape}.") outputs = inputs # Not for all possible combinations of (`kernel_support`, `corr`, # `strides_up`, `strides_down`) TF ops exist. We implement some additional # combinations by manipulating the kernels and toggling `corr`. kernel = self.kernel corr = self.corr # If a convolution with no upsampling is desired, we flip the kernels and # use cross correlation to implement it, provided the kernels are odd-length # in every dimension (with even-length kernels, the boundary handling # would have to change). if (not corr and all(s == 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = True slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] # Similarly, we can implement a cross correlation using convolutions. # However, we do this only if upsampling is requested, as we are potentially # wasting computation in the boundaries whenever we call the transpose ops. elif (corr and any(s != 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = False slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] # Compute amount of necessary padding, and determine whether to use built-in # padding or to pre-pad with a separate op. if self.padding == "valid": padding = prepadding = self._rank * ((0, 0), ) else: # same_* padding = padding_ops.same_padding_for_kernel( self.kernel_support, corr, self.strides_up) if (self.padding == "same_zeros" and not self.channel_separable and 1 <= self._rank <= 2 and self.use_explicit): # Don't pre-pad and use built-in EXPLICIT mode. prepadding = self._rank * ((0, 0), ) else: # Pre-pad and then use built-in valid padding mode. outputs = tf.pad(outputs, self._padded_tuple(padding, (0, 0)), self._pad_mode) prepadding = padding padding = self._rank * ((0, 0), ) # Compute the convolution/correlation. Prefer EXPLICIT padding ops where # possible, but don't use them to implement VALID padding. if (corr and all(s == 1 for s in self.strides_up) and not self.channel_separable and 1 <= self._rank <= 2 and not all(p[0] == p[1] == 0 for p in padding)): outputs = self._correlate_down_explicit(outputs, kernel, padding) elif (corr and all(s == 1 for s in self.strides_up) and all(p[0] == p[1] == 0 for p in padding)): outputs = self._correlate_down_valid(outputs, kernel) elif (not corr and not self.channel_separable and 1 <= self._rank <= 2 and self.use_explicit): outputs = self._up_convolve_transpose_explicit( outputs, kernel, prepadding) elif not corr: outputs = self._up_convolve_transpose_valid( outputs, kernel, prepadding) else: self._raise_notimplemented() # Now, add bias if requested. if self.use_bias: bias = self.bias if self.data_format == "channels_first": # As of Mar 2017, direct addition is significantly slower than # bias_add when computing gradients. if self._rank == 1: # tf.nn.bias_add does not accept a 1D input tensor. outputs = tf.expand_dims(outputs, 2) outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") outputs = tf.squeeze(outputs, [2]) elif self._rank == 2: outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") elif self._rank >= 3: shape = tf.shape(outputs) outputs = tf.reshape(outputs, tf.concat([shape[:3], [-1]], axis=0)) outputs = tf.nn.bias_add(outputs, bias, data_format="NCHW") outputs = tf.reshape(outputs, shape) else: outputs = tf.nn.bias_add(outputs, bias) # Finally, pass through activation function if requested. if self.activation is not None: outputs = self.activation(outputs) return outputs
def call(self, inputs): inputs = ops.convert_to_tensor(inputs, dtype=self.dtype) input_shape = array_ops.shape(inputs) outputs = inputs # First, perform any requested padding. if self.padding in ("same_zeros", "same_reflect"): padding = padding_ops.same_padding_for_kernel( self.kernel_support, self.corr, self.strides_up) if self.data_format == "channels_last": padding = [[0, 0]] + list(padding) + [[0, 0]] else: padding = [[0, 0], [0, 0]] + list(padding) outputs = array_ops.pad(outputs, padding, self._pad_mode) # Now, perform valid convolutions/correlations. # Not for all possible combinations of (`kernel_support`, `corr`, # `strides_up`, `strides_down`) TF ops exist. We implement some additional # combinations by manipulating the kernels and toggling `corr`. kernel = self.kernel corr = self.corr # If a convolution with no upsampling is desired, we flip the kernels and # use cross correlation to implement it, provided the kernels are odd-length # in every dimension (with even-length kernels, the boundary handling # would have to change, so we'll throw an error instead). if (not corr and all(s == 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = True slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] # Similarly, we can implement a cross correlation with no downsampling using # convolutions. However, we do this only if upsampling is requested, as we # are wasting computation in the boundaries whenever we call the transpose # convolution ops. if (corr and all(s == 1 for s in self.strides_down) and any(s != 1 for s in self.strides_up) and all(s % 2 == 1 for s in self.kernel_support)): corr = False slices = self._rank * (slice(None, None, -1), ) + 2 * (slice(None), ) kernel = kernel[slices] data_format = utils.convert_data_format(self.data_format, self._rank + 2) if (corr and self.channel_separable and self._rank == 2 and all(s == 1 for s in self.strides_up) and all(s == self.strides_down[0] for s in self.strides_down)): # `nn.depthwise_conv2d_native` performs channel-separable correlations # followed by optional downsampling. outputs = nn.depthwise_conv2d_native(outputs, kernel, strides=self._pad_strides( self.strides_down), padding="VALID", data_format=data_format) elif (corr and all(s == 1 for s in self.strides_up) and not self.channel_separable): # `nn.convolution` performs correlations followed by optional # downsampling. outputs = nn.convolution(outputs, kernel, strides=self.strides_down, padding="VALID", data_format=data_format) elif (not corr and all(s == 1 for s in self.strides_down) and ((not self.channel_separable and 1 <= self._rank <= 3) or (self.channel_separable and self.filters == 1 and self._rank == 2 and all(s == self.strides_up[0] for s in self.strides_up)))): # `nn.conv?d_transpose` perform convolutions, preceded by optional # upsampling. Generally, they increase the spatial support of their # inputs, so in order to implement 'valid', we need to crop their outputs. # Transpose convolutions expect the output and input channels in reversed # order. We implement this by swapping those dimensions of the kernel. # For channel separable convolutions, we can't currently perform anything # other than one filter per channel, so the last dimension needs to be of # length one. Since this happens to be the format that the op expects it, # we can skip the transpose in that case. if not self.channel_separable: kernel = array_ops.transpose( kernel, list(range(self._rank)) + [self._rank + 1, self._rank]) # Compute shape of temporary. pad_shape = array_ops.shape(outputs) temp_shape = [pad_shape[0]] + (self._rank + 1) * [None] if self.data_format == "channels_last": spatial_axes = range(1, self._rank + 1) if self.channel_separable: temp_shape[-1] = input_shape[-1] else: temp_shape[-1] = self.filters else: spatial_axes = range(2, self._rank + 2) if self.channel_separable: temp_shape[1] = input_shape[1] else: temp_shape[1] = self.filters if self.extra_pad_end: get_length = lambda l, s, k: l * s + (k - 1) else: get_length = lambda l, s, k: l * s + (k - s) for i, a in enumerate(spatial_axes): temp_shape[a] = get_length(pad_shape[a], self.strides_up[i], self.kernel_support[i]) # Compute convolution. if self._rank == 1 and not self.channel_separable: # There's no 1D transpose convolution op, so we insert an extra # dimension and use 2D. extradim = { "channels_first": 2, "channels_last": 1 }[self.data_format] strides = self._pad_strides(self.strides_up) temp = array_ops.squeeze( nn.conv2d_transpose( array_ops.expand_dims(outputs, extradim), array_ops.expand_dims(kernel, 0), temp_shape[:extradim] + [1] + temp_shape[extradim:], strides=strides[:extradim] + (1, ) + strides[extradim:], padding="VALID", data_format=data_format.replace("W", "HW")), [extradim]) elif self._rank == 2 and self.channel_separable: temp = nn.depthwise_conv2d_native_backprop_input( temp_shape, kernel, outputs, strides=self._pad_strides(self.strides_up), padding="VALID", data_format=data_format) elif self._rank == 2 and not self.channel_separable: temp = nn.conv2d_transpose(outputs, kernel, temp_shape, strides=self._pad_strides( self.strides_up), padding="VALID", data_format=data_format) elif self._rank == 3 and not self.channel_separable: temp = nn.conv3d_transpose(outputs, kernel, temp_shape, strides=self._pad_strides( self.strides_up), padding="VALID", data_format=data_format) else: assert False # Should never reach this. # Perform crop. slices = [slice(None)] * (self._rank + 2) if self.padding == "valid": # Take `kernel_support - 1` samples away from both sides. This leaves # just samples computed without padding. for i, a in enumerate(spatial_axes): slices[a] = slice( self.kernel_support[i] - 1, None if self.kernel_support[i] == 1 else 1 - self.kernel_support[i]) else: # Take `kernel_support // 2` plus the padding away from beginning, and # crop end to input length multiplied by upsampling factor. for i, a in enumerate(spatial_axes): offset = padding[a][0] * self.strides_up[i] offset += self.kernel_support[i] // 2 length = get_length(input_shape[a], self.strides_up[i], offset + 1) slices[a] = slice(offset, length) outputs = temp[slices] else: raise NotImplementedError( "The provided combination of SignalConv arguments is not currently " "implemented (kernel_support={}, corr={}, strides_down={}, " "strides_up={}, channel_separable={}, filters={}). " "Try using odd-length kernels or turning off separability?". format(self.kernel_support, self.corr, self.strides_down, self.strides_up, self.channel_separable, self.filters)) # Now, add bias if requested. if self.bias is not None: if self.data_format == "channels_first": # As of Mar 2017, direct addition is significantly slower than # bias_add when computing gradients. if self._rank == 1: # nn.bias_add does not accept a 1D input tensor. outputs = array_ops.expand_dims(outputs, 2) outputs = nn.bias_add(outputs, self.bias, data_format="NCHW") outputs = array_ops.squeeze(outputs, [2]) elif self._rank == 2: outputs = nn.bias_add(outputs, self.bias, data_format="NCHW") elif self._rank >= 3: shape = array_ops.shape(outputs) outputs = array_ops.reshape(outputs, shape[:3] + [-1]) outputs = nn.bias_add(outputs, self.bias, data_format="NCHW") outputs = array_ops.reshape(outputs, shape) else: outputs = nn.bias_add(outputs, self.bias) # Finally, pass through activation function if requested. if self.activation is not None: outputs = self.activation(outputs) # pylint:disable=not-callable # Aid shape inference, for some reason shape info is not always available. if not context.executing_eagerly(): outputs.set_shape(self.compute_output_shape(inputs.shape)) return outputs