Beispiel #1
0
    def identity_block(input_tensor: TensorType, kernel_size, filters, stage, block) -> TensorType:
        """The identity block is the block that has no conv layer at shortcut.

        Args:
            input_tensor: input tensor.
            kernel_size: default 3, the kernel size of middle conv layer at main path
            filters: list of integers, the filters of 3 conv layer at main path
            stage: integer, current stage label, used for generating layer names
            block: 'a','b'..., current block label, used for generating layer names

        Returns:
            output tensor for the block.
        """

        name_base = f"conv{stage}_block{block}_"
        x = layers.conv(input_tensor, filters_out=filters[0], kernel_size=1,
                        add_bias=False, name=name_base + '1_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '1_bn')
        x = layers.relu(x, name=name_base + '1_relu')

        x = layers.conv(x, filters_out=filters[1], kernel_size=kernel_size, add_bias=False,
                        name=name_base + '2_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '2_bn')
        x = layers.relu(x, name=name_base + '2_relu')

        x = layers.conv(x, filters_out=filters[2], kernel_size=1, add_bias=False,
                        name=name_base + '3_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '3_bn')

        x = x + input_tensor
        x = layers.relu(x, name=name_base + '3_relu')

        return x
Beispiel #2
0
    def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id):
        in_channels = inputs.get_shape()[-1]
        pointwise_conv_filters = int(filters * alpha)
        pointwise_filters = _make_divisible(pointwise_conv_filters, 8)
        x = inputs
        prefix = 'block_{}_'.format(block_id)

        if block_id:
            # Expand
            x = layers.conv(x, filters_out=expansion * in_channels,
                            kernel_size=1,
                            padding='same',
                            add_bias=False,
                            name=prefix + 'expand')
            x = layers.norm(x, epsilon=1e-3,
                            momentum=0.999,
                            name=prefix + 'expand_BN')
            x = layers.relu(x, max_value=tf.constant(6, tf.float16), name=prefix + 'expand_relu')
        else:
            prefix = 'expanded_conv_'

        # Depthwise
        if stride == 2:
            x = layers.zero_padding(x, padding=((0, 1), (0, 1)),
                                    name=prefix + 'pad')
        x = layers.depthwise_conv(x, kernel_size=3,
                                  stride=stride,
                                  add_bias=False,
                                  padding='same' if stride == 1 else 'valid',
                                  name=prefix + 'depthwise')
        x = layers.norm(x, epsilon=1e-3,
                        momentum=0.999,
                        name=prefix + 'depthwise_BN')

        x = layers.relu(x, max_value=tf.constant(6, tf.float16), name=prefix + 'depthwise_relu')

        # Project
        x = layers.conv(x, filters_out=pointwise_filters,
                        kernel_size=1,
                        padding='same',
                        add_bias=False,
                        name=prefix + 'project')
        x = layers.norm(x,
                        epsilon=1e-3, momentum=0.999, name=prefix + 'project_BN')

        if in_channels == pointwise_filters and stride == 1:
            return x + inputs
        return x
Beispiel #3
0
    def conv_block(input_tensor: Union[tf.Tensor, np.ndarray], kernel_size, filters,
                   stage,
                   block,
                   strides=2) -> TensorType:
        """Building block for a dense block.

        Args:
            input_tensor: Input tensor of type tf.Tensor if using tf backend, np.ndarray if using popart builder.
            kernel_size: default 3, the kernel size of
                middle conv layer at main path
            filters: list of integers, the filters of 3 conv layer at main path
            stage: integer, current stage label, used for generating layer names
            block: 'a','b'..., current block label, used for generating layer names
            strides: Strides for the first conv layer in the block.

        Return:
            Output tensor for the block.
        """

        name_base = f"conv{stage}_block{block}_"

        shortcut = layers.conv(input_tensor, filters_out=filters[2], kernel_size=1, add_bias=False, stride=strides,
                               name=name_base + '0_conv')
        shortcut = layers.norm(shortcut, axis=-1, epsilon=1.001e-5, name=name_base + '0_bn')

        x = layers.conv(input_tensor, filters_out=filters[0], kernel_size=1, stride=strides,
                        add_bias=False, name=name_base + '1_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '1_bn')
        x = layers.relu(x, name=name_base + '1_relu')

        x = layers.conv(x, filters_out=filters[1], kernel_size=kernel_size, add_bias=False,
                        name=name_base + '2_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '2_bn')
        x = layers.relu(x, name=name_base + '2_relu')

        x = layers.conv(x, filters_out=filters[2], kernel_size=1, add_bias=False,
                        name=name_base + '3_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name=name_base + '3_bn')

        x = x + shortcut
        x = layers.relu(x, name=name_base + '3_relu')

        return x
    def _depthwise_conv_block(x: Union[tf.Tensor, np.ndarray],
                              pointwise_conv_filters,
                              alpha,
                              depth_multiplier=1,
                              strides=1,
                              block_id=1) -> TensorType:
        """A block of depthwise convolutions

        Args:
            x: input tensor.
            pointwise_conv_filters: number of pointwise filters modified by the size param Alpha
            alpha: determines the network size (modifies the number of conv filters)
            depth_multiplier: Changes the number of depthwise filters
            strides: changes stride
            block_id: Used to identify blocks

        Returns:
            output tensor for the block.
        """

        pointwise_conv_filters = (pointwise_conv_filters * alpha)

        if strides == 1:
            _x = x
        else:
            _x = layers.zero_padding(x,
                                     padding=((0, 1), (0, 1)),
                                     name='conv_pad_%d' % block_id)

        _x = layers.depthwise_conv(_x,
                                   kernel_size=3,
                                   padding='same' if strides == 1 else 'valid',
                                   filters_out=depth_multiplier,
                                   stride=strides,
                                   add_bias=False,
                                   name='conv_dw_%d' % block_id)
        _x = layers.norm(_x, axis=-1, name='conv_dw_%d_bn' % block_id)

        _x = layers.relu(_x, name='conv_dw_%d' % block_id)

        _x = layers.conv(_x,
                         filters_out=pointwise_conv_filters,
                         kernel_size=1,
                         padding='same',
                         add_bias=False,
                         stride=1,
                         name='conv_pw_%d' % block_id)
        _x = layers.norm(_x, axis=-1, name='conv_pw_%d_bn' % block_id)
        return layers.relu(_x, name='conv_pw_%d_relu' % block_id)
Beispiel #5
0
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

        Args:
            img_input: 4D Image input tensor of shape (batch, height, width, channels)

        Returns:
            `Tensor` holding output probabilities per class, shape (batch, num_classes)
        """

        x = layers.conv(img_input,
                        filters_out=64,
                        kernel_size=7,
                        stride=2,
                        add_bias=False,
                        name='conv1_conv')
        x = layers.norm(x, axis=-1, epsilon=1.001e-5, name='conv1_bn')
        x = layers.relu(x, name='conv1_relu')
        x = layers.zero_padding(x, padding=((1, 1), (1, 1)), name='pool1_pad')
        x = layers.max_pool(x, kernel_size=3, name='pool1')

        x = self.conv_block(x, 3, [64, 64, 256], stage=2, block='1', strides=1)
        x = self.identity_block(x, 3, [64, 64, 256], stage=2, block='2')
        x = self.identity_block(x, 3, [64, 64, 256], stage=2, block='3')

        x = self.conv_block(x, 3, [128, 128, 512], stage=3, block='1')
        x = self.identity_block(x, 3, [128, 128, 512], stage=3, block='2')
        x = self.identity_block(x, 3, [128, 128, 512], stage=3, block='3')
        x = self.identity_block(x, 3, [128, 128, 512], stage=3, block='4')

        x = self.conv_block(x, 3, [256, 256, 1024], stage=4, block='1')
        x = self.identity_block(x, 3, [256, 256, 1024], stage=4, block='2')
        x = self.identity_block(x, 3, [256, 256, 1024], stage=4, block='3')
        x = self.identity_block(x, 3, [256, 256, 1024], stage=4, block='4')
        x = self.identity_block(x, 3, [256, 256, 1024], stage=4, block='5')
        x = self.identity_block(x, 3, [256, 256, 1024], stage=4, block='6')

        x = self.conv_block(x, 3, [512, 512, 2048], stage=5, block='1')
        x = self.identity_block(x, 3, [512, 512, 2048], stage=5, block='2')
        x = self.identity_block(x, 3, [512, 512, 2048], stage=5, block='3')

        x = layers.avg_pool(x, kernel_size=7, strides=1, name='avg_pool')
        x = layers.squeeze(x, axis=[1, 2], name='squeeze')
        x = layers.fully_connected(x, self.num_classes, name='probs')
        x = layers.softmax(x, name='output-prob')
        return x
Beispiel #6
0
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

        Args:
            img_input: 4D Image input tensor of shape (batch, height, width,
            channels)

        Returns:
            `Tensor` holding output probabilities per class, shape (batch,
            num_classes)
        """
        channel_axis = -1

        x = conv_norm_relu(img_input,
                           64,
                           7,
                           strides=2,
                           padding='SAME',
                           name='InceptionV1/Conv2d_1a_7x7',
                           norm_suffix="/BatchNorm",
                           weight_suffix="weights",
                           conv_suffix="")

        x = max_pool(x, 3, strides=2, padding='same', name='MaxPool_2a_3x3')
        x = conv_norm_relu(x,
                           64,
                           1,
                           padding='same',
                           name='InceptionV1/Conv2d_2b_1x1',
                           weight_suffix="weights",
                           conv_suffix="",
                           norm_suffix="/BatchNorm")
        x = conv_norm_relu(x,
                           192,
                           3,
                           padding='same',
                           name='InceptionV1/Conv2d_2c_3x3',
                           weight_suffix="weights",
                           conv_suffix="",
                           norm_suffix="/BatchNorm")
        x = max_pool(x, 3, strides=2, padding='same', name='MaxPool_3a_3x3')

        # Now the '3' level inception units
        x = self.inception_block(x, ((64, ), (96, 128), (16, 32), (32, )),
                                 channel_axis, 'InceptionV1/Mixed_3b')
        x = self.inception_block(x, ((128, ), (128, 192), (32, 96), (64, )),
                                 channel_axis, 'InceptionV1/Mixed_3c')

        x = max_pool(x, 3, strides=2, padding='same', name='MaxPool_4a_3x3')

        # Now the '4' level inception units
        x = self.inception_block(x, ((192, ), (96, 208), (16, 48), (64, )),
                                 channel_axis, 'InceptionV1/Mixed_4b')
        x = self.inception_block(x, ((160, ), (112, 224), (24, 64), (64, )),
                                 channel_axis, 'InceptionV1/Mixed_4c')
        x = self.inception_block(x, ((128, ), (128, 256), (24, 64), (64, )),
                                 channel_axis, 'InceptionV1/Mixed_4d')
        x = self.inception_block(x, ((112, ), (144, 288), (32, 64), (64, )),
                                 channel_axis, 'InceptionV1/Mixed_4e')
        x = self.inception_block(x, ((256, ), (160, 320), (32, 128), (128, )),
                                 channel_axis, 'InceptionV1/Mixed_4f')

        x = max_pool(x, 2, strides=2, padding='same', name='MaxPool_5a_2x2')

        # Now the '5' level inception units
        x = self.inception_block(x, ((256, ), (160, 320), (32, 128), (128, )),
                                 channel_axis, 'InceptionV1/Mixed_5b')
        x = self.inception_block(x, ((384, ), (192, 384), (48, 128), (128, )),
                                 channel_axis, 'InceptionV1/Mixed_5c')

        # Classification block
        x = avg_pool(x,
                     kernel_size=7,
                     strides=1,
                     name='avg_pool',
                     padding='valid')
        x = conv(x,
                 filters_out=self.num_classes + 1,
                 kernel_size=1,
                 padding='valid',
                 add_bias=True,
                 name='InceptionV1/Logits/Conv2d_0c_1x1',
                 weight_suffix="weights",
                 bias_suffix="biases")
        x = squeeze(x, axis=[1, 2], name='squeeze')
        x = softmax(x, name='output-prob')

        return x
Beispiel #7
0
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

                Args:
                    img_input: 4D Image input tensor of shape (batch, height, width, channels)

                Returns:
                    `Tensor` holding output probabilities per class, shape (batch, num_classes)
        """
        filters = _make_divisible(32 * self.alpha, 8)

        # Conv 1 block
        x = layers.zero_padding(img_input,
                                padding=((0, 1), (0, 1)),
                                name='Conv1_pad')
        x = layers.conv(x,
                        filters_out=filters,
                        kernel_size=3,
                        padding='valid',
                        add_bias=False,
                        stride=2,
                        name='Conv1')
        x = layers.norm(x,
                        axis=-1,
                        epsilon=1e-3,
                        momentum=0.999,
                        name='bn_Conv1')
        x = layers.relu(x,
                        name='Conv1_relu',
                        max_value=tf.constant(6, tf.float16))

        # Depthwise separable convolutions
        x = self._inverted_res_block(x,
                                     filters=16,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=1,
                                     block_id=0)

        x = self._inverted_res_block(x,
                                     filters=24,
                                     alpha=self.alpha,
                                     stride=2,
                                     expansion=6,
                                     block_id=1)
        x = self._inverted_res_block(x,
                                     filters=24,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=2)

        x = self._inverted_res_block(x,
                                     filters=32,
                                     alpha=self.alpha,
                                     stride=2,
                                     expansion=6,
                                     block_id=3)
        x = self._inverted_res_block(x,
                                     filters=32,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=4)
        x = self._inverted_res_block(x,
                                     filters=32,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=5)

        x = self._inverted_res_block(x,
                                     filters=64,
                                     alpha=self.alpha,
                                     stride=2,
                                     expansion=6,
                                     block_id=6)
        x = self._inverted_res_block(x,
                                     filters=64,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=7)
        x = self._inverted_res_block(x,
                                     filters=64,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=8)
        x = self._inverted_res_block(x,
                                     filters=64,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=9)

        x = self._inverted_res_block(x,
                                     filters=96,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=10)
        x = self._inverted_res_block(x,
                                     filters=96,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=11)
        x = self._inverted_res_block(x,
                                     filters=96,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=12)

        x = self._inverted_res_block(x,
                                     filters=160,
                                     alpha=self.alpha,
                                     stride=2,
                                     expansion=6,
                                     block_id=13)
        x = self._inverted_res_block(x,
                                     filters=160,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=14)
        x = self._inverted_res_block(x,
                                     filters=160,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=15)

        x = self._inverted_res_block(x,
                                     filters=320,
                                     alpha=self.alpha,
                                     stride=1,
                                     expansion=6,
                                     block_id=16)

        # no alpha applied to last conv as stated in the paper:
        # if the width multiplier is greater than 1 we
        # increase the number of output channels
        if self.alpha > 1.0:
            last_block_filters = _make_divisible(1280 * self.alpha, 8)
        else:
            last_block_filters = 1280

        x = layers.conv(x,
                        filters_out=last_block_filters,
                        kernel_size=1,
                        add_bias=False,
                        name='Conv_1')
        x = layers.norm(x, epsilon=1e-3, momentum=0.999, name='Conv_1_bn')
        x = layers.relu(x,
                        max_value=tf.constant(6, tf.float16),
                        name='out_relu')

        # Include top
        x = layers.global_avg_pool(x)
        x = layers.fully_connected(x, self.num_classes, name='Logits')
        x = layers.softmax(x, name='act_softmax')
        return x
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

                Args:
                    img_input: 4D Image input tensor of shape (batch, height, width, channels)

                Returns:
                    `Tensor` holding output probabilities per class, shape (batch, num_classes)
        """
        filters = int(32 * self.alpha)
        shape = (-1, 1, 1, int(1024 * self.alpha))

        # Conv 1 block
        x = layers.zero_padding(img_input,
                                padding=((0, 1), (0, 1)),
                                name='conv1_pad')
        x = layers.conv(x,
                        filters_out=filters,
                        kernel_size=3,
                        padding='valid',
                        add_bias=False,
                        stride=2,
                        name='conv1')
        x = layers.norm(x, axis=-1, name='conv1_bn')
        x = layers.relu(x, name='conv1_relu')

        # Depthwise convolutions
        x = self._depthwise_conv_block(x,
                                       64,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=1)
        x = self._depthwise_conv_block(x,
                                       128,
                                       self.alpha,
                                       depth_multiplier=1,
                                       strides=2,
                                       block_id=2)
        x = self._depthwise_conv_block(x,
                                       128,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=3)
        x = self._depthwise_conv_block(x,
                                       256,
                                       self.alpha,
                                       depth_multiplier=1,
                                       strides=2,
                                       block_id=4)
        x = self._depthwise_conv_block(x,
                                       256,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=5)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       strides=2,
                                       block_id=6)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=7)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=8)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=9)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=10)
        x = self._depthwise_conv_block(x,
                                       512,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=11)
        x = self._depthwise_conv_block(x,
                                       1024,
                                       self.alpha,
                                       depth_multiplier=1,
                                       strides=2,
                                       block_id=12)
        x = self._depthwise_conv_block(x,
                                       1024,
                                       self.alpha,
                                       depth_multiplier=1,
                                       block_id=13)

        # Include top
        x = layers.global_avg_pool(x)
        x = layers.reshape(x, shape=shape, name='reshape_1')
        x = layers.conv(x,
                        filters_out=self.num_classes,
                        kernel_size=1,
                        padding='same',
                        name='conv_preds',
                        add_bias=False)
        x = layers.reshape(x, shape=(-1, self.num_classes), name='reshape_2')
        x = layers.softmax(x, name='act_softmax')
        return x
Beispiel #9
0
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

        Args:
            img_input: 4D Image input tensor of shape (batch, height, width, channels)

        Returns:
            `Tensor` holding output probabilities per class, shape (batch, num_classes)
        """

        x = layers.conv(img_input, filters_out=32, kernel_size=3, stride=2, add_bias=False, name='block1_conv1')
        x = layers.norm(x, name='block1_conv1_bn')
        x = layers.relu(x, name='block1_conv1_act')
        x = layers.conv(x, filters_out=64, kernel_size=3, add_bias=False, name='block1_conv2')
        x = layers.norm(x, name='block1_conv2_bn')
        x = layers.relu(x, name='block1_conv2_act')

        residual = layers.conv(x, filters_out=128, kernel_size=1, stride=2, padding='same', add_bias=False)
        residual = layers.norm(residual, name="batch_normalization")

        x = layers.separable_conv(x, filters_out=128, kernel_size=3, padding='same', add_bias=False,
                                  name='block2_sepconv1')
        x = layers.norm(x, name='block2_sepconv1_bn')
        x = layers.relu(x, name='block2_sepconv2_act')
        x = layers.separable_conv(x, filters_out=128, kernel_size=3, padding='same', add_bias=False,
                                  name='block2_sepconv2')
        x = layers.norm(x, name='block2_sepconv2_bn')

        x = layers.max_pool(x, 3, strides=2, padding='same', name='block2_pool')
        x += residual

        residual = layers.conv(x, filters_out=256, kernel_size=1, stride=2, padding='same', add_bias=False)
        residual = layers.norm(residual, name="batch_normalization")

        x = layers.relu(x, name='block3_sepconv1_act')
        x = layers.separable_conv(x, filters_out=256, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block3_sepconv1')
        x = layers.norm(x, name='block3_sepconv1_bn')
        x = layers.relu(x, name='block3_sepconv2_act')
        x = layers.separable_conv(x, filters_out=256, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block3_sepconv2')
        x = layers.norm(x, name='block3_sepconv2_bn')

        x = layers.max_pool(x, 3, strides=2,
                            padding='same',
                            name='block3_pool')
        x += residual

        residual = layers.conv(x, filters_out=728, kernel_size=1,
                               stride=2,
                               padding='same',
                               add_bias=False)
        residual = layers.norm(residual, name="batch_normalization")

        x = layers.relu(x, name='block4_sepconv1_act')
        x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block4_sepconv1')
        x = layers.norm(x, name='block4_sepconv1_bn')
        x = layers.relu(x, name='block4_sepconv2_act')
        x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block4_sepconv2')
        x = layers.norm(x, name='block4_sepconv2_bn')

        x = layers.max_pool(x, 3, strides=2,
                            padding='same',
                            name='block4_pool')
        x += residual

        for i in range(8):
            residual = x
            prefix = 'block' + str(i + 5)

            x = layers.relu(x, name=prefix + '_sepconv1_act')
            x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                      padding='same',
                                      add_bias=False,
                                      name=prefix + '_sepconv1')
            x = layers.norm(x, name=prefix + '_sepconv1_bn')
            x = layers.relu(x, name=prefix + '_sepconv2_act')
            x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                      padding='same',
                                      add_bias=False,
                                      name=prefix + '_sepconv2')
            x = layers.norm(x, name=prefix + '_sepconv2_bn')
            x = layers.relu(x, name=prefix + '_sepconv3_act')
            x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                      padding='same',
                                      add_bias=False,
                                      name=prefix + '_sepconv3')
            x = layers.norm(x, name=prefix + '_sepconv3_bn')

            x += residual

        residual = layers.conv(x, filters_out=1024, kernel_size=1, stride=2,
                               padding='same', add_bias=False)
        residual = layers.norm(residual, name="batch_normalization")

        x = layers.relu(x, name='block13_sepconv1_act')
        x = layers.separable_conv(x, filters_out=728, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block13_sepconv1')
        x = layers.norm(x, name='block13_sepconv1_bn')
        x = layers.relu(x, name='block13_sepconv2_act')
        x = layers.separable_conv(x, filters_out=1024, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block13_sepconv2')
        x = layers.norm(x, name='block13_sepconv2_bn')

        x = layers.max_pool(x, 3,
                            strides=2,
                            padding='same',
                            name='block13_pool')
        x += residual

        x = layers.separable_conv(x, filters_out=1536, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block14_sepconv1')
        x = layers.norm(x, name='block14_sepconv1_bn')
        x = layers.relu(x, name='block14_sepconv1_act')

        x = layers.separable_conv(x, filters_out=2048, kernel_size=3,
                                  padding='same',
                                  add_bias=False,
                                  name='block14_sepconv2')
        x = layers.norm(x, name='block14_sepconv2_bn')
        x = layers.relu(x, name='block14_sepconv2_act')

        # Classification block
        x = layers.avg_pool(x, kernel_size=10, strides=1, name='avg_pool')
        x = layers.squeeze(x, axis=[1, 2], name='squeeze')
        x = layers.fully_connected(x, self.num_classes, name='predictions')
        x = layers.softmax(x, name='output-prob')

        return x
    def build_model(self, img_input: TensorType) -> TensorType:
        """Build graph using img_input as input.

        Args:
            img_input: 4D Image input tensor of shape (batch, height, width, channels)

        Returns:
            `Tensor` holding output probabilities per class, shape (batch, num_classes)
        """

        filters = self.penultimate_filters // 24

        x = layers.conv(img_input,
                        filters_out=self.stem_block_filters,
                        kernel_size=3,
                        stride=2,
                        padding='valid',
                        add_bias=False,
                        name='stem_conv1')

        x = layers.norm(x,
                        axis=-1,
                        momentum=0.9997,
                        epsilon=1e-3,
                        name='stem_bn1')

        p = None
        x, p = self.reduction_a_cell(x,
                                     p,
                                     filters // (self.filter_multiplier**2),
                                     block_id='stem_1')
        x, p = self.reduction_a_cell(x,
                                     p,
                                     filters // self.filter_multiplier,
                                     block_id='stem_2')

        for i in range(self.num_blocks):
            x, p = self.normal_a_cell(x, p, filters, block_id='%d' % i)

        x, p0 = self.reduction_a_cell(x,
                                      p,
                                      filters * self.filter_multiplier,
                                      block_id='reduce_%d' % self.num_blocks)

        p = p0 if not self.skip_reduction else p

        for i in range(self.num_blocks):
            x, p = self.normal_a_cell(x,
                                      p,
                                      filters * self.filter_multiplier,
                                      block_id='%d' %
                                      (self.num_blocks + i + 1))

        x, p0 = self.reduction_a_cell(x,
                                      p,
                                      filters * self.filter_multiplier**2,
                                      block_id='reduce_%d' %
                                      (2 * self.num_blocks))

        p = p0 if not self.skip_reduction else p

        for i in range(self.num_blocks):
            x, p = self.normal_a_cell(x,
                                      p,
                                      filters * self.filter_multiplier**2,
                                      block_id='%d' %
                                      (2 * self.num_blocks + i + 1))

        x = layers.relu(x, 'relu')

        # Classification block
        x = layers.avg_pool(x, kernel_size=7, strides=1, name='avg_pool')
        x = layers.squeeze(x, axis=[1, 2], name='squeeze')
        x = layers.fully_connected(x, self.num_classes, name='predictions')
        x = layers.softmax(x, name='output-prob')

        return x
    def reduction_a_cell(ip, p, filters, block_id=None):
        """Adds a Reduction cell for NASNet-A (Fig. 4 in the paper).

         Args:
             ip: Input tensor `x`
             p: Input tensor `p`
             filters: Number of output filters
             block_id: String block_id

         Returns:
             A tf tensor
         """
        channel_dim = -1

        with tf.name_scope('reduction_A_block_%s' % block_id):
            p = NASNetMobile.adjust_block(p, ip, filters, block_id)

            h = layers.relu(ip)
            h = layers.conv(h,
                            filters_out=filters,
                            kernel_size=(1, 1),
                            stride=1,
                            padding='same',
                            name='reduction_conv_1_%s' % block_id,
                            add_bias=False)
            h = layers.norm(h,
                            axis=channel_dim,
                            momentum=0.9997,
                            epsilon=1e-3,
                            name='reduction_bn_1_%s' % block_id)

            h3 = layers.zero_padding(h,
                                     padding=NASNetMobile.correct_pad(
                                         h, (3, 3)),
                                     name='reduction_pad_1_%s' % block_id)

            with tf.name_scope('block_1'):
                x1_1 = NASNetMobile.separable_conv_block(
                    h,
                    filters=filters,
                    kernel_size=5,
                    strides=2,
                    block_id='reduction_left1_%s' % block_id)
                x1_2 = NASNetMobile.separable_conv_block(
                    p,
                    filters=filters,
                    kernel_size=7,
                    strides=2,
                    block_id='reduction_right1_%s' % block_id)
                x1 = x1_1 + x1_2

            with tf.name_scope('block_2'):
                x2_1 = layers.max_pool(h3,
                                       3,
                                       strides=2,
                                       padding='valid',
                                       name='reduction_left2_%s' % block_id)
                x2_2 = NASNetMobile.separable_conv_block(
                    p,
                    filters=filters,
                    kernel_size=7,
                    strides=2,
                    block_id='reduction_right2_%s' % block_id)
                x2 = x2_1 + x2_2

            with tf.name_scope('block_3'):
                x3_1 = layers.avg_pool(h3,
                                       3,
                                       strides=2,
                                       padding='valid',
                                       name='reduction_left3_%s' % block_id)
                x3_2 = NASNetMobile.separable_conv_block(
                    p,
                    filters,
                    5,
                    strides=2,
                    block_id='reduction_right3_%s' % block_id)
                x3 = x3_1 + x3_2

            with tf.name_scope('block_4'):
                x4 = layers.avg_pool(x1,
                                     3,
                                     strides=1,
                                     padding='same',
                                     name='reduction_left4_%s' % block_id)
                x4 += x2

            with tf.name_scope('block_5'):
                x5_1 = NASNetMobile.separable_conv_block(
                    x1, filters, 3, block_id='reduction_left4_%s' % block_id)
                x5_2 = layers.max_pool(h3,
                                       3,
                                       strides=2,
                                       padding='valid',
                                       name='reduction_right5_%s' % block_id)
                x5 = x5_1 + x5_2

            x = layers.concat([x2, x3, x4, x5],
                              axis=channel_dim,
                              name='reduction_concat_%s' % block_id)
            return x, ip
    def normal_a_cell(ip, p, filters, block_id=None):
        """Adds a Normal cell for NASNet-A (Fig. 4 in the paper).

        Args:
            ip: Input tensor `x`
            p: Input tensor `p`
            filters: Number of output filters
            block_id: String block_id

        Returns:
            A tensorflow tensor
        """
        channel_dim = -1

        with tf.name_scope('normal_A_block_%s' % block_id):
            p = NASNetMobile.adjust_block(p, ip, filters, block_id)

            h = layers.relu(ip)
            h = layers.conv(h,
                            filters_out=filters,
                            kernel_size=(1, 1),
                            stride=1,
                            padding='same',
                            name='normal_conv_1_%s' % block_id,
                            add_bias=False)
            h = layers.norm(h,
                            axis=channel_dim,
                            momentum=0.9997,
                            epsilon=1e-3,
                            name='normal_bn_1_%s' % block_id)

            with tf.name_scope('block_1'):
                x1_1 = NASNetMobile.separable_conv_block(
                    h,
                    filters,
                    kernel_size=5,
                    block_id='normal_left1_%s' % block_id)

                x1_2 = NASNetMobile.separable_conv_block(
                    p, filters, block_id='normal_right1_%s' % block_id)
                x1 = x1_1 + x1_2

            with tf.name_scope('block_2'):
                x2_1 = NASNetMobile.separable_conv_block(
                    p, filters, 5, block_id='normal_left2_%s' % block_id)
                x2_2 = NASNetMobile.separable_conv_block(
                    p, filters, 3, block_id='normal_right2_%s' % block_id)
                x2 = x2_1 + x2_2

            with tf.name_scope('block_3'):
                x3 = layers.avg_pool(h,
                                     3,
                                     strides=1,
                                     padding='same',
                                     name='normal_left3_%s' % block_id)
                x3 = x3 + p

            with tf.name_scope('block_4'):
                x4_1 = layers.avg_pool(p,
                                       3,
                                       strides=1,
                                       padding='same',
                                       name='normal_left4_%s' % block_id)
                x4_2 = layers.avg_pool(p,
                                       3,
                                       strides=1,
                                       padding='same',
                                       name='normal_right4_%s' % block_id)
                x4 = x4_1 + x4_2

            with tf.name_scope('block_5'):
                x5 = NASNetMobile.separable_conv_block(
                    h, filters, block_id='normal_left5_%s' % block_id)
                x5 = x5 + h

            x = layers.concat([p, x1, x2, x3, x4, x5],
                              axis=channel_dim,
                              name='normal_concat_%s' % block_id)

        return x, ip
    def adjust_block(p, ip, filters, block_id=None):
        """Adjusts the input `previous path` to match the shape of the `input`.

        Used in situations where the output number of filters needs to be changed.

        Args:
            p: Input tensor which needs to be modified
            ip: Input tensor whose shape needs to be matched
            filters: Number of output filters to be matched
            block_id: String block_id

        Returns:
            Adjusted tf tensor.
        """
        channel_dim = -1
        img_dim = -2

        ip_shape = ip.get_shape().as_list()

        if p is not None:
            p_shape = p.get_shape().as_list()
        else:
            p_shape = ip_shape

        with tf.name_scope('adjust_block'):
            if p is None:
                p = ip

            elif p_shape[img_dim] != ip_shape[img_dim]:
                with tf.name_scope('adjust_reduction_block_%s' % block_id):
                    p = layers.relu(p, name='adjust_relu_1_%s' % block_id)
                    p1 = layers.avg_pool(p,
                                         1,
                                         strides=2,
                                         padding='valid',
                                         name='adjust_avg_pool_1_%s' %
                                         block_id)
                    p1 = layers.conv(p1,
                                     filters_out=filters // 2,
                                     kernel_size=(1, 1),
                                     padding='same',
                                     add_bias=False,
                                     name='adjust_conv_1_%s' % block_id)

                    p2 = layers.zero_padding(p, padding=((0, 1), (0, 1)))
                    p2 = layers.crop(p2, cropping=((1, 0), (1, 0)))
                    p2 = layers.avg_pool(p2,
                                         1,
                                         strides=2,
                                         padding='valid',
                                         name='adjust_avg_pool_2_%s' %
                                         block_id)
                    p2 = layers.conv(p2,
                                     filters_out=filters // 2,
                                     kernel_size=(1, 1),
                                     padding='same',
                                     add_bias=False,
                                     name='adjust_conv_2_%s' % block_id)

                    p = layers.concat([p1, p2], axis=channel_dim)
                    p = layers.norm(p,
                                    axis=channel_dim,
                                    momentum=0.9997,
                                    epsilon=1e-3,
                                    name='adjust_bn_%s' % block_id)

            elif p_shape[channel_dim] != filters:
                with tf.name_scope('adjust_projection_block_%s' % block_id):
                    p = layers.relu(p)
                    p = layers.conv(p,
                                    filters_out=filters,
                                    kernel_size=(1, 1),
                                    stride=1,
                                    padding='same',
                                    name='adjust_conv_projection_%s' %
                                    block_id,
                                    add_bias=False)
                    p = layers.norm(p,
                                    axis=channel_dim,
                                    momentum=0.9997,
                                    epsilon=1e-3,
                                    name='adjust_bn_%s' % block_id)
        return p