def testAtrousSequence(self): """Tests optimization of sequence of atrous convolutions. See the documentation of with_space_to_batch. """ with self._delay_checks() as add_check: for padding in ["SAME", "VALID"]: for height in range(15, 17): for width in range(15, 17): x_shape = [3, height, width, 2] x = np.random.random_sample(x_shape).astype(np.float32) kernel_sizes = [1, 3] if padding == "SAME" else range( 1, 3) for kernel in kernel_sizes: f_shape = [kernel, kernel, 2, 2] f1 = 1e-2 * np.random.random_sample( f_shape).astype(np.float32) f2 = 1e-2 * np.random.random_sample( f_shape).astype(np.float32) def combined_op(converted_input, num_spatial_dims, padding_arg): # pylint: disable=unused-argument # pylint: disable=cell-var-from-loop result = nn_ops.convolution( input=converted_input, filter=f1, padding=padding) result = nn_ops.convolution(input=result, filter=f2, padding=padding) # pylint: enable=cell-var-from-loop return result for rate_height in range(2, 4): for rate_width in range(2, 4): dilation_rate = [rate_height, rate_width] y1 = nn_ops.convolution( input=x, filter=f1, padding=padding, dilation_rate=dilation_rate) y1 = nn_ops.convolution( input=y1, filter=f2, padding=padding, dilation_rate=dilation_rate) y2 = nn_ops.with_space_to_batch( input=x, dilation_rate=dilation_rate, op=combined_op, padding="VALID") def check(y1_eval, y2_eval): self.assertAllClose(y1_eval, y2_eval, rtol=1e-2, atol=1e-2) add_check(check, y1, y2)
def depthwise_conv2d(input, filter, strides, padding, rate=None, name=None): """Depthwise 2-D convolution. Given an input tensor of shape `[batch, in_height, in_width, in_channels]` and a filter tensor of shape `[filter_height, filter_width, in_channels, channel_multiplier]` containing `in_channels` convolutional filters of depth 1, `depthwise_conv2d` applies a different filter to each input channel (expanding from 1 channel to `channel_multiplier` channels for each), then concatenates the results together. The output has `in_channels * channel_multiplier` channels. In detail, output[b, i, j, k * channel_multiplier + q] = sum_{di, dj} filter[di, dj, k, q] * input[b, strides[1] * i + rate[0] * di, strides[2] * j + rate[1] * dj, k] Must have `strides[0] = strides[3] = 1`. For the most common case of the same horizontal and vertical strides, `strides = [1, stride, stride, 1]`. If any value in `rate` is greater than 1, we perform atrous depthwise convolution, in which case all values in the `strides` tensor must be equal to 1. Args: input: 4-D with shape `[batch, in_height, in_width, in_channels]`. filter: 4-D with shape `[filter_height, filter_width, in_channels, channel_multiplier]`. strides: 1-D of size 4. The stride of the sliding window for each dimension of `input`. padding: A string, either `'VALID'` or `'SAME'`. The padding algorithm. See the @{tf.nn.convolution$comment here} rate: 1-D of size 2. The dilation rate in which we sample input values across the `height` and `width` dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1. name: A name for this operation (optional). Returns: A 4-D `Tensor` of shape `[batch, out_height, out_width, in_channels * channel_multiplier].` """ with ops.name_scope(name, "depthwise", [input, filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") filter = ops.convert_to_tensor(filter, name="filter_in") if rate is None: rate = [1, 1] def op(input_converted, _, padding): return nn_ops.depthwise_conv2d_native( input=input_converted, filter=filter, strides=strides, padding=padding, name=name) return nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(filter), dilation_rate=rate, padding=padding, op=op)
def testAtrousSequence(self): """Tests optimization of sequence of atrous convolutions. See the documentation of with_space_to_batch. """ with self._delay_checks() as add_check: for padding in ["SAME", "VALID"]: for height in range(15, 17): for width in range(15, 17): x_shape = [3, height, width, 2] x = np.random.random_sample(x_shape).astype(np.float32) kernel_sizes = [1, 3] if padding == "SAME" else range(1, 3) for kernel in kernel_sizes: f_shape = [kernel, kernel, 2, 2] f1 = 1e-2 * np.random.random_sample(f_shape).astype(np.float32) f2 = 1e-2 * np.random.random_sample(f_shape).astype(np.float32) def combined_op(converted_input, num_spatial_dims, padding_arg): # pylint: disable=unused-argument # pylint: disable=cell-var-from-loop result = nn_ops.convolution( input=converted_input, filter=f1, padding=padding) result = nn_ops.convolution( input=result, filter=f2, padding=padding) # pylint: enable=cell-var-from-loop return result for rate_height in range(2, 4): for rate_width in range(2, 4): dilation_rate = [rate_height, rate_width] y1 = nn_ops.convolution( input=x, filter=f1, padding=padding, dilation_rate=dilation_rate) y1 = nn_ops.convolution( input=y1, filter=f2, padding=padding, dilation_rate=dilation_rate) y2 = nn_ops.with_space_to_batch( input=x, dilation_rate=dilation_rate, op=combined_op, padding="VALID") def check(y1_eval, y2_eval): self.assertAllClose(y1_eval, y2_eval, rtol=1e-2, atol=1e-2) add_check(check, y1, y2)
def dilatete_trough_time_conv2d(input, filter, strides, padding, rate=None, name=None): """Dilatated 2-D convolution. Given a 4D input tensor and a filter tensor of shape `[filter_height, filter_width, in_channels, channel_output]` Args: input: 4-D with shape according to `data_format`. filter: 4-D with shape `[filter_height, filter_width, in_channels, channel_multiplier]`. strides: 1-D of size 4. The stride of the sliding window for each dimension of `input`. padding: A string, either `'VALID'` or `'SAME'`. The padding algorithm. rate: 1-D of size 2. The dilation rate in which we sample input values across the `height` and `width` dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1. name: A name for this operation (optional). Returns: A 4-D `Tensor` with shape according to `data_format`. E.g., for [batch, out_height, out_width, out_channel].` """ with ops.name_scope(name, "trough_time", [input, filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") filter = ops.convert_to_tensor(filter, name="filter_in") if rate is None: rate = [1, 1] def op(input_converted, _, padding): return tf.nn.conv2d( input=input_converted, filter=filter, strides=strides, padding=padding, name=name) return nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(filter), dilation_rate=rate, padding=padding, op=op)
def separable_conv2d_tf_nn(input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None, data_format=None): """2-D convolution with separable filters. Performs a depthwise convolution that acts separately on channels followed by a pointwise convolution that mixes channels. Note that this is separability between dimensions `[1, 2]` and `3`, not spatial separability between dimensions `1` and `2`. In detail, output[b, i, j, k] = sum_{di, dj, q, r] input[b, strides[1] * i + di, strides[2] * j + dj, q] * depthwise_filter[di, dj, q, r] * pointwise_filter[0, 0, q * channel_multiplier + r, k] `strides` controls the strides for the depthwise convolution only, since the pointwise convolution has implicit strides of `[1, 1, 1, 1]`. Must have `strides[0] = strides[3] = 1`. For the most common case of the same horizontal and vertical strides, `strides = [1, stride, stride, 1]`. If any value in `rate` is greater than 1, we perform atrous depthwise convolution, in which case all values in the `strides` tensor must be equal to 1. Args: input: 4-D `Tensor` with shape according to `data_format`. depthwise_filter: 4-D `Tensor` with shape `[filter_height, filter_width, in_channels, channel_multiplier]`. Contains `in_channels` convolutional filters of depth 1. pointwise_filter: 4-D `Tensor` with shape `[1, 1, channel_multiplier * in_channels, out_channels]`. Pointwise filter to mix channels after `depthwise_filter` has convolved spatially. strides: 1-D of size 4. The strides for the depthwise convolution for each dimension of `input`. padding: A string, either `'VALID'` or `'SAME'`. The padding algorithm. See the @{tf.nn.convolution$comment here} rate: 1-D of size 2. The dilation rate in which we sample input values across the `height` and `width` dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1. name: A name for this operation (optional). data_format: The data format for input. Either "NHWC" (default) or "NCHW". Returns: A 4-D `Tensor` with shape according to 'data_format'. For example, with data_format="NHWC", shape is [batch, out_height, out_width, out_channels]. Raises: ValueError: If channel_multiplier * in_channels > out_channels, which means that the separable convolution is overparameterized. """ with ops.name_scope(name, "separable_conv2d", [input, depthwise_filter, pointwise_filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") depthwise_filter = ops.convert_to_tensor(depthwise_filter, name="depthwise_filter") pointwise_filter = ops.convert_to_tensor(pointwise_filter, name="pointwise_filter") pointwise_filter_shape = pointwise_filter.get_shape().with_rank(4) pointwise_filter_shape[0].assert_is_compatible_with(1) pointwise_filter_shape[1].assert_is_compatible_with(1) channel_multiplier = depthwise_filter.get_shape().with_rank(4)[3] if data_format and data_format == "NCHW": in_channels = input.get_shape().with_rank(4)[1] else: in_channels = input.get_shape().with_rank(4)[3] out_channels = pointwise_filter_shape[3] if rate is None: rate = [1, 1] # If any of channel numbers is unknown, then the comparison below returns # None. See TensorShape.__gt__(). #if channel_multiplier * in_channels > out_channels: # raise ValueError("Refusing to perform an overparameterized separable " # "convolution: channel_multiplier * in_channels = " # "%d * %d = %d > %d = out_channels" % # (channel_multiplier, in_channels, # channel_multiplier * in_channels, out_channels)) # The layout of the ops in the graph are expected to be as follows: # depthwise_conv2d // Conv2D op corresponding to native deptwise conv. # separable_conv2d // Conv2D op corresponding to the pointwise conv. def op(input_converted, _, padding): return nn_ops.depthwise_conv2d_native(input=input_converted, filter=depthwise_filter, strides=strides, padding=padding, data_format=data_format, name="depthwise") depthwise = nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(depthwise_filter), dilation_rate=rate, padding=padding, data_format=data_format, op=op) return nn_ops.conv2d(depthwise, pointwise_filter, [1, 1, 1, 1], padding="VALID", data_format=data_format, name=name)
def separable_conv2d(input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None): """2-D convolution with separable filters. Performs a depthwise convolution that acts separately on channels followed by a pointwise convolution that mixes channels. Note that this is separability between dimensions `[1, 2]` and `3`, not spatial separability between dimensions `1` and `2`. In detail, output[b, i, j, k] = sum_{di, dj, q, r] input[b, strides[1] * i + di, strides[2] * j + dj, q] * depthwise_filter[di, dj, q, r] * pointwise_filter[0, 0, q * channel_multiplier + r, k] `strides` controls the strides for the depthwise convolution only, since the pointwise convolution has implicit strides of `[1, 1, 1, 1]`. Must have `strides[0] = strides[3] = 1`. For the most common case of the same horizontal and vertical strides, `strides = [1, stride, stride, 1]`. If any value in `rate` is greater than 1, we perform atrous depthwise convolution, in which case all values in the `strides` tensor must be equal to 1. Args: input: 4-D `Tensor` with shape `[batch, in_height, in_width, in_channels]`. depthwise_filter: 4-D `Tensor` with shape `[filter_height, filter_width, in_channels, channel_multiplier]`. Contains `in_channels` convolutional filters of depth 1. pointwise_filter: 4-D `Tensor` with shape `[1, 1, channel_multiplier * in_channels, out_channels]`. Pointwise filter to mix channels after `depthwise_filter` has convolved spatially. strides: 1-D of size 4. The strides for the depthwise convolution for each dimension of `input`. padding: A string, either `'VALID'` or `'SAME'`. The padding algorithm. See the [comment here](https://www.tensorflow.org/api_docs/python/nn.html#convolution) rate: 1-D of size 2. The dilation rate in which we sample input values across the `height` and `width` dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1. name: A name for this operation (optional). Returns: A 4-D `Tensor` of shape `[batch, out_height, out_width, out_channels]`. Raises: ValueError: If channel_multiplier * in_channels > out_channels, which means that the separable convolution is overparameterized. """ with ops.name_scope(name, "separable_conv2d", [input, depthwise_filter, pointwise_filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") depthwise_filter = ops.convert_to_tensor( depthwise_filter, name="depthwise_filter") pointwise_filter = ops.convert_to_tensor( pointwise_filter, name="pointwise_filter") pointwise_filter_shape = pointwise_filter.get_shape().with_rank(4) pointwise_filter_shape[0].assert_is_compatible_with(1) pointwise_filter_shape[1].assert_is_compatible_with(1) channel_multiplier = depthwise_filter.get_shape().with_rank(4)[3] in_channels = input.get_shape().with_rank(4)[3] out_channels = pointwise_filter_shape[3] if rate is None: rate = [1, 1] # If any of channel numbers is unknown, then the comparison below returns # None. See TensorShape.__gt__(). if channel_multiplier * in_channels > out_channels: raise ValueError("Refusing to perform an overparameterized separable " "convolution: channel_multiplier * in_channels = " "%d * %d = %d > %d = out_channels" % (channel_multiplier, in_channels, channel_multiplier * in_channels, out_channels)) # The layout of the ops in the graph are expected to be as follows: # depthwise_conv2d // Conv2D op corresponding to native deptwise conv. # separable_conv2d // Conv2D op corresponding to the pointwise conv. def op(input_converted, _, padding): return nn_ops.depthwise_conv2d_native( input=input_converted, filter=depthwise_filter, strides=strides, padding=padding, name="depthwise") depthwise = nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(depthwise_filter), dilation_rate=rate, padding=padding, op=op) return nn_ops.conv2d( depthwise, pointwise_filter, [1, 1, 1, 1], padding="VALID", name=name)
def depthwise_conv2d(input, filter, strides, padding, rate=None, name=None): """Depthwise 2-D convolution. Given an input tensor of shape `[batch, in_height, in_width, in_channels]` and a filter tensor of shape `[filter_height, filter_width, in_channels, channel_multiplier]` containing `in_channels` convolutional filters of depth 1, `depthwise_conv2d` applies a different filter to each input channel (expanding from 1 channel to `channel_multiplier` channels for each), then concatenates the results together. The output has `in_channels * channel_multiplier` channels. In detail, output[b, i, j, k * channel_multiplier + q] = sum_{di, dj} filter[di, dj, k, q] * input[b, strides[1] * i + rate[0] * di, strides[2] * j + rate[1] * dj, k] Must have `strides[0] = strides[3] = 1`. For the most common case of the same horizontal and vertical strides, `strides = [1, stride, stride, 1]`. If any value in `rate` is greater than 1, we perform atrous depthwise convolution, in which case all values in the `strides` tensor must be equal to 1. Args: input: 4-D with shape `[batch, in_height, in_width, in_channels]`. filter: 4-D with shape `[filter_height, filter_width, in_channels, channel_multiplier]`. strides: 1-D of size 4. The stride of the sliding window for each dimension of `input`. padding: A string, either `'VALID'` or `'SAME'`. The padding algorithm. See the [comment here](https://www.tensorflow.org/api_docs/python/nn.html#convolution) rate: 1-D of size 2. The dilation rate in which we sample input values across the `height` and `width` dimensions in atrous convolution. If it is greater than 1, then all values of strides must be 1. name: A name for this operation (optional). Returns: A 4-D `Tensor` of shape `[batch, out_height, out_width, in_channels * channel_multiplier].` """ with ops.name_scope(name, "depthwise", [input, filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") filter = ops.convert_to_tensor(filter, name="filter_in") if rate is None: rate = [1, 1] def op(input_converted, _, padding): return nn_ops.depthwise_conv2d_native( input=input_converted, filter=filter, strides=strides, padding=padding, name=name) return nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(filter), dilation_rate=rate, padding=padding, op=op)
def mpusim_separable_conv2d_impl(input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None, data_format=None, activations_datatype_size_byte=1, weights_datatype_size_byte=1, results_datatype_size_byte=4, systolic_array_height=256, systolic_array_width=256, activation_fifo_depth=8, accumulator_array_height=4096, log_file_output_dir='.', model_name='unnamed'): with ops.name_scope(name, "mpusim_separable_conv2d_impl", [input, depthwise_filter, pointwise_filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") depthwise_filter = ops.convert_to_tensor(depthwise_filter, name="depthwise_filter") pointwise_filter = ops.convert_to_tensor(pointwise_filter, name="pointwise_filter") depthwise_filter_shape = depthwise_filter.get_shape().with_rank(4) channels = depthwise_filter_shape.dims[3] pointwise_filter_shape = pointwise_filter.get_shape().with_rank(4) pointwise_filter_shape.dims[0].assert_is_compatible_with(1) pointwise_filter_shape.dims[1].assert_is_compatible_with(1) if rate is None: rate = [1, 1] # The layout of the ops in the graph are expected to be as follows: # depthwise_conv2d // Conv2D op corresponding to deptwise convolution # separable_conv2d // Conv2D op corresponding to the pointwise convolution def op(input_converted, _, padding): inputs = tf.split(input_converted, channels, 3) kernels = tf.split(depthwise_filter, channels, 3) outputs = [ mpu_sim_conv2d_lib.mpu_sim_conv2d( input_block, kernel_block, activations_datatype_size_byte, weights_datatype_size_byte, results_datatype_size_byte, systolic_array_height, systolic_array_width, activation_fifo_depth, accumulator_array_height, log_file_output_dir, model_name, strides=strides, padding=padding) for input_block, kernel_block in zip(inputs, kernels) ] print('Executed depthwise convolution') return tf.concat(outputs, 3) depthwise = nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(depthwise_filter), dilation_rate=rate, padding=padding, data_format=data_format, op=op) return mpu_sim_conv2d_lib.mpu_sim_conv2d( depthwise, pointwise_filter, activations_datatype_size_byte, weights_datatype_size_byte, results_datatype_size_byte, systolic_array_height, systolic_array_width, activation_fifo_depth, accumulator_array_height, log_file_output_dir, model_name, strides=[1, 1, 1, 1], padding="VALID", data_format=data_format, name=name)
def mpusim_depthwise_conv2d(input, filter, strides, padding, rate=None, name=None, data_format=None, dilations=None, activations_datatype_size_byte=1, weights_datatype_size_byte=1, results_datatype_size_byte=4, systolic_array_height=256, systolic_array_width=256, activation_fifo_depth=8, accumulator_array_height=4096, log_file_output_dir='.', model_name='unnamed'): rate = deprecated_argument_lookup("dilations", dilations, "rate", rate) with ops.name_scope("mpusim_depthwise_conv2d", [input, filter]) as name: input = ops.convert_to_tensor(input, name="tensor_in") filter = ops.convert_to_tensor(filter, name="filter_in") if rate is None: rate = [1, 1] channels = input.get_shape().with_rank(4).dims[3] #print('Depthwise convolution shape: {}'.format(filter.get_shape())) def op(input_converted, _, padding): inputs = tf.split(input_converted, channels, 3) kernels = tf.split(filter, channels, 2) outputs = [] convolution_count = 0 for input_block, kernel_block in zip(inputs, kernels): with ops.name_scope("_{}".format(convolution_count)) as name: channel_output = mpu_sim_conv2d_lib.mpu_sim_conv2d(input_block, kernel_block, activations_datatype_size_byte, weights_datatype_size_byte, results_datatype_size_byte, systolic_array_height, systolic_array_width, activation_fifo_depth, accumulator_array_height, log_file_output_dir, model_name, strides=strides, padding=padding) outputs.append(channel_output) convolution_count += 1 return tf.concat(outputs, 3) return nn_ops.with_space_to_batch(input=input, filter_shape=array_ops.shape(filter), dilation_rate=rate, padding=padding, data_format=data_format, op=op)
def quantizable_separable_convolution2d( inputs, num_outputs, kernel_size, depth_multiplier, stride=1, padding='SAME', data_format=DATA_FORMAT_NHWC, rate=1, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer(), biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None): """Adds a depth-separable 2D convolution with optional batch_norm layer. This op first performs a depthwise convolution that acts separately on channels, creating a variable called `depthwise_weights`. If `num_outputs` is not None, it adds a pointwise convolution that mixes channels, creating a variable called `pointwise_weights`. Then, if `normalizer_fn` is None, it adds bias to the result, creating a variable called 'biases', otherwise, the `normalizer_fn` is applied. It finally applies an activation function to produce the end result. Args: inputs: A tensor of size [batch_size, height, width, channels]. num_outputs: The number of pointwise convolution output filters. If is None, then we skip the pointwise convolution stage. kernel_size: A list of length 2: [kernel_height, kernel_width] of of the filters. Can be an int if both values are the same. depth_multiplier: The number of depthwise convolution output channels for each input channel. The total number of depthwise convolution output channels will be equal to `num_filters_in * depth_multiplier`. stride: A list of length 2: [stride_height, stride_width], specifying the depthwise convolution stride. Can be an int if both strides are the same. padding: One of 'VALID' or 'SAME'. data_format: A string. `NHWC` (default) and `NCHW` are supported. rate: A list of length 2: [rate_height, rate_width], specifying the dilation rates for atrous convolution. Can be an int if both rates are the same. If any value is larger than one, then both stride values need to be one. activation_fn: Activation function. The default value is a ReLU function. Explicitly set it to None to skip it and maintain a linear activation. normalizer_fn: Normalization function to use instead of `biases`. If `normalizer_fn` is provided then `biases_initializer` and `biases_regularizer` are ignored and `biases` are not created nor added. default set to None for no normalizer function normalizer_params: Normalization function parameters. weights_initializer: An initializer for the weights. weights_regularizer: Optional regularizer for the weights. biases_initializer: An initializer for the biases. If None skip biases. biases_regularizer: Optional regularizer for the biases. reuse: Whether or not the layer and its variables should be reused. To be able to reuse the layer scope must be given. variables_collections: Optional list of collections for all the variables or a dictionary containing a different list of collection per variable. outputs_collections: Collection to add the outputs. trainable: Whether or not the variables should be trainable or not. scope: Optional scope for variable_scope. Returns: A `Tensor` representing the output of the operation. Raises: ValueError: If `data_format` is invalid. """ if data_format not in (DATA_FORMAT_NCHW, DATA_FORMAT_NHWC): raise ValueError('data_format has to be either NCHW or NHWC.') layer_variable_getter = _build_variable_getter({ 'bias': 'biases', 'depthwise_kernel': 'depthwise_weights', 'pointwise_kernel': 'pointwise_weights' }) with variable_scope.variable_scope( scope, 'SeparableConv2d', [inputs], reuse=reuse, custom_getter=layer_variable_getter) as sc: inputs = ops.convert_to_tensor(inputs) df = ('channels_first' if data_format and data_format.startswith('NC') else 'channels_last') if num_outputs is not None: # Apply separable conv using the SeparableConvolution2D layer. layer = convolutional_layers.SeparableConvolution2D( filters=num_outputs, kernel_size=kernel_size, strides=stride, padding=padding, data_format=df, dilation_rate=utils.two_element_tuple(rate), activation=None, depth_multiplier=depth_multiplier, use_bias=not normalizer_fn and biases_initializer, depthwise_initializer=weights_initializer, pointwise_initializer=weights_initializer, bias_initializer=biases_initializer, depthwise_regularizer=weights_regularizer, pointwise_regularizer=weights_regularizer, bias_regularizer=biases_regularizer, activity_regularizer=None, trainable=trainable, name=sc.name, dtype=inputs.dtype.base_dtype, _scope=sc, _reuse=reuse) outputs = layer.apply(inputs) # Add variables to collections. _add_variable_to_collections(layer.depthwise_kernel, variables_collections, 'weights') _add_variable_to_collections(layer.pointwise_kernel, variables_collections, 'weights') if layer.bias is not None: _add_variable_to_collections(layer.bias, variables_collections, 'biases') if normalizer_fn is not None: normalizer_params = normalizer_params or {} outputs = normalizer_fn(outputs, **normalizer_params) else: # Actually apply depthwise conv instead of separable conv. dtype = inputs.dtype.base_dtype kernel_h, kernel_w = utils.two_element_tuple(kernel_size) stride_h, stride_w = utils.two_element_tuple(stride) num_filters_in = utils.channel_dimension(inputs.get_shape(), df, min_rank=4) weights_collections = utils.get_variable_collections( variables_collections, 'weights') depthwise_shape = [ kernel_h, kernel_w, num_filters_in, depth_multiplier ] depthwise_weights = variables.model_variable( 'depthwise_weights', shape=depthwise_shape, dtype=dtype, initializer=weights_initializer, regularizer=weights_regularizer, trainable=trainable, collections=weights_collections) strides = [ 1, 1, stride_h, stride_w ] if data_format.startswith('NC') else [1, stride_h, stride_w, 1] # DIFFERING PART START input = ops.convert_to_tensor(inputs, name="tensor_in") filter = ops.convert_to_tensor(depthwise_weights, name="filter_in") if rate is None: rate = [1, 1] def op(input_converted, _, padding): outputs = nn_ops.depthwise_conv2d_native( input=input_converted, filter=filter, strides=strides, padding=padding, data_format=data_format) num_outputs = depth_multiplier * num_filters_in if normalizer_fn is not None: normalizer_params_ = normalizer_params or {} outputs = normalizer_fn(outputs, **normalizer_params_) else: if biases_initializer is not None: biases_collections = utils.get_variable_collections( variables_collections, 'biases') biases = variables.model_variable( 'biases', shape=[ num_outputs, ], dtype=dtype, initializer=biases_initializer, regularizer=biases_regularizer, trainable=trainable, collections=biases_collections) outputs = nn.bias_add(outputs, biases, data_format=data_format) if activation_fn is not None: outputs = activation_fn(outputs) return outputs outputs = nn_ops.with_space_to_batch( input=input, filter_shape=array_ops.shape(filter), dilation_rate=utils.two_element_tuple(rate), padding=padding, data_format=data_format, op=op) return utils.collect_named_outputs(outputs_collections, sc.name, outputs)