示例#1
0
    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)
示例#2
0
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)
示例#4
0
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)
示例#6
0
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)
示例#7
0
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)
示例#8
0
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)
示例#9
0
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)
示例#10
0
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)