text: # Licensed to the Apache Software Foundation (ASF) under one
  # or more contributor license agreements.  See the NOTICE file
  # distributed with this work for additional information
  # regarding copyright ownership.  The ASF licenses this file
  # to you under the Apache License, Version 2.0 (the
  # "License"); you may not use this file except in compliance
  # with the License.  You may obtain a copy of the License at
  #
  #   http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing,
  # software distributed under the License is distributed on an
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  # KIND, either express or implied.  See the License for the
  # specific language governing permissions and limitations
  # under the License.
  
  # coding: utf-8
  # pylint: disable= arguments-differ,missing-docstring
  """ResNext, implemented in Gluon."""
  from __future__ import division
  
  __all__ = ['ResNext', 'Block', 'get_resnext',
             'resnext50_32x4d', 'resnext101_32x4d', 'resnext101_64x4d',
             'se_resnext50_32x4d', 'se_resnext101_32x4d', 'se_resnext101_64x4d',
             'resnext101e_64x4d', 'se_resnext101e_64x4d']
  
  import os
  import math
  from mxnet import cpu
  from mxnet.gluon import nn
  from mxnet.gluon.nn import BatchNorm
  from mxnet.gluon.block import HybridBlock
  
  
  class Block(HybridBlock):
      r"""Bottleneck Block from `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      stride : int
          Stride size.
      downsample : bool, default False
          Whether to downsample the input.
      last_gamma : bool, default False
          Whether to initialize the gamma of the last BatchNorm layer in each bottleneck to zero.
      use_se : bool, default False
          Whether to use Squeeze-and-Excitation module
      avg_down : bool, default False
          Whether to use average pooling for projection skip connection between stages/downsample.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
  
      def __init__(self, channels, cardinality, bottleneck_width, stride,
                   downsample=False, last_gamma=False, use_se=False, avg_down=False,
                   norm_layer=BatchNorm, norm_kwargs=None, **kwargs):
          super(Block, self).__init__(**kwargs)
          D = int(math.floor(channels * (bottleneck_width / 64)))
          group_width = cardinality * D
  
          self.body = nn.HybridSequential(prefix='')
          self.body.add(nn.Conv2D(group_width, kernel_size=1, use_bias=False))
          self.body.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
          self.body.add(nn.Activation('relu'))
          self.body.add(nn.Conv2D(group_width, kernel_size=3, strides=stride, padding=1,
                                  groups=cardinality, use_bias=False))
          self.body.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
          self.body.add(nn.Activation('relu'))
          self.body.add(nn.Conv2D(channels * 4, kernel_size=1, use_bias=False))
          if last_gamma:
              self.body.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
          else:
              self.body.add(norm_layer(gamma_initializer='zeros',
                                       **({} if norm_kwargs is None else norm_kwargs)))
  
          if use_se:
              self.se = nn.HybridSequential(prefix='')
              self.se.add(nn.Conv2D(channels // 4, kernel_size=1, padding=0))
              self.se.add(nn.Activation('relu'))
              self.se.add(nn.Conv2D(channels * 4, kernel_size=1, padding=0))
              self.se.add(nn.Activation('sigmoid'))
          else:
              self.se = None
  
          if downsample:
              self.downsample = nn.HybridSequential(prefix='')
              if avg_down:
                  self.downsample.add(nn.AvgPool2D(pool_size=stride, strides=stride,
                                                   ceil_mode=True, count_include_pad=False))
                  self.downsample.add(nn.Conv2D(channels=channels * 4, kernel_size=1,
                                                strides=1, use_bias=False))
              else:
                  self.downsample.add(nn.Conv2D(channels * 4, kernel_size=1, strides=stride,
                                                use_bias=False))
              self.downsample.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
          else:
              self.downsample = None
  
      def hybrid_forward(self, F, x):
          residual = x
  
          x = self.body(x)
  
          if self.se:
              w = F.contrib.AdaptiveAvgPooling2D(x, output_size=1)
              w = self.se(w)
              x = F.broadcast_mul(x, w)
  
          if self.downsample:
              residual = self.downsample(residual)
  
          x = F.Activation(x + residual, act_type='relu')
          return x
  
  
  # Nets
  class ResNext(HybridBlock):
      r"""ResNext model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      layers : list of int
          Numbers of layers in each block
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      classes : int, default 1000
          Number of classification classes.
      last_gamma : bool, default False
          Whether to initialize the gamma of the last BatchNorm layer in each bottleneck to zero.
      use_se : bool, default False
          Whether to use Squeeze-and-Excitation module
      deep_stem : bool, default False
          Whether to replace the 7x7 conv1 with 3 3x3 convolution layers.
      stem_width : int, default 64
          Width of the stem intermediate layer.
      avg_down : bool, default False
          Whether to use average pooling for projection skip connection between stages/downsample.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
  
      def __init__(self, layers, cardinality, bottleneck_width,
                   classes=1000, last_gamma=False, use_se=False, deep_stem=False, avg_down=False,
                   stem_width=64, norm_layer=BatchNorm, norm_kwargs=None, **kwargs):
          super(ResNext, self).__init__(**kwargs)
          self.cardinality = cardinality
          self.bottleneck_width = bottleneck_width
          channels = 64
  
          with self.name_scope():
              self.features = nn.HybridSequential(prefix='')
              if not deep_stem:
                  self.features.add(nn.Conv2D(channels=64, kernel_size=7, strides=2,
                                              padding=3, use_bias=False))
              else:
                  self.features.add(nn.Conv2D(channels=stem_width, kernel_size=3, strides=2,
                                              padding=1, use_bias=False))
                  self.features.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
                  self.features.add(nn.Activation('relu'))
                  self.features.add(nn.Conv2D(channels=stem_width, kernel_size=3, strides=1,
                                              padding=1, use_bias=False))
                  self.features.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
                  self.features.add(nn.Activation('relu'))
                  self.features.add(nn.Conv2D(channels=stem_width * 2, kernel_size=3, strides=1,
                                              padding=1, use_bias=False))
  
              self.features.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
              self.features.add(nn.Activation('relu'))
              self.features.add(nn.MaxPool2D(3, 2, 1))
              for i, num_layer in enumerate(layers):
                  stride = 1 if i == 0 else 2
                  self.features.add(self._make_layer(channels, num_layer, stride, last_gamma, use_se,
                                                     False if i == 0 else avg_down, i + 1,
                                                     norm_layer=norm_layer, norm_kwargs=norm_kwargs))
                  channels *= 2
              self.features.add(nn.GlobalAvgPool2D())
  
              self.output = nn.Dense(classes)
  
      def _make_layer(self, channels, num_layers, stride, last_gamma, use_se, avg_down, stage_index,
                      norm_layer=BatchNorm, norm_kwargs=None):
          layer = nn.HybridSequential(prefix='stage%d_' % stage_index)
          with layer.name_scope():
              layer.add(Block(channels, self.cardinality, self.bottleneck_width,
                              stride, True, last_gamma=last_gamma, use_se=use_se, avg_down=avg_down,
                              prefix='', norm_layer=norm_layer, norm_kwargs=norm_kwargs))
              for _ in range(num_layers - 1):
                  layer.add(Block(channels, self.cardinality, self.bottleneck_width,
                                  1, False, last_gamma=last_gamma, use_se=use_se, prefix='',
                                  norm_layer=norm_layer, norm_kwargs=norm_kwargs))
          return layer
  
      # pylint: disable=unused-argument
      def hybrid_forward(self, F, x):
          x = self.features(x)
          x = self.output(x)
  
          return x
  
  
  # Specification
  resnext_spec = {50: [3, 4, 6, 3],
                  101: [3, 4, 23, 3]}
  
  
  # Constructor
  def get_resnext(num_layers, cardinality=32, bottleneck_width=4, use_se=False, deep_stem=False,
                  avg_down=False, pretrained=False, ctx=cpu(),
                  root=os.path.join('~', '.mxnet', 'models'), **kwargs):
      r"""ResNext model from `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      num_layers : int
          Numbers of layers. Options are 50, 101.
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      assert num_layers in resnext_spec, \
          "Invalid number of layers: %d. Options are %s" % (
              num_layers, str(resnext_spec.keys()))
      layers = resnext_spec[num_layers]
      net = ResNext(layers, cardinality, bottleneck_width, use_se=use_se, deep_stem=deep_stem,
                    avg_down=avg_down, **kwargs)
      if pretrained:
          from .model_store import get_model_file
          if not use_se:
              net.load_parameters(get_model_file('resnext%d_%dx%dd' % (num_layers, cardinality,
                                                                       bottleneck_width),
                                                 tag=pretrained, root=root), ctx=ctx)
          else:
              net.load_parameters(get_model_file('se_resnext%d_%dx%dd' % (num_layers, cardinality,
                                                                          bottleneck_width),
                                                 tag=pretrained, root=root), ctx=ctx)
          from ..data import ImageNet1kAttr
          attrib = ImageNet1kAttr()
          net.synset = attrib.synset
          net.classes = attrib.classes
          net.classes_long = attrib.classes_long
  
      return net
  
  
  def resnext50_32x4d(**kwargs):
      r"""ResNext50 32x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = False
      return get_resnext(50, 32, 4, **kwargs)
  
  
  def resnext101_32x4d(**kwargs):
      r"""ResNext101 32x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = False
      return get_resnext(101, 32, 4, **kwargs)
  
  
  def resnext101_64x4d(**kwargs):
      r"""ResNext101 64x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = False
      return get_resnext(101, 64, 4, **kwargs)
  
  
  def resnext101e_64x4d(**kwargs):
      # pylint: disable=line-too-long
      r"""ResNext101e 64x4d model modified from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = False
      if kwargs['pretrained']:
          msg = 'GluonCV does not have pretrained weights for resnext101e_64x4d at this moment. Please set pretrained=False.'
          raise RuntimeError(msg)
      return get_resnext(101, 64, 4, deep_stem=True, avg_down=True, **kwargs)
  
  
  def se_resnext50_32x4d(**kwargs):
      r"""SE-ResNext50 32x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = True
      return get_resnext(50, 32, 4, **kwargs)
  
  
  def se_resnext101_32x4d(**kwargs):
      r"""SE-ResNext101 32x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = True
      return get_resnext(101, 32, 4, **kwargs)
  
  
  def se_resnext101_64x4d(**kwargs):
      r"""SE-ResNext101 64x4d model from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = True
      return get_resnext(101, 64, 4, **kwargs)
  
  
  def se_resnext101e_64x4d(**kwargs):
      # pylint: disable=line-too-long
      r"""SE-ResNext101e 64x4d model modified from
      `"Aggregated Residual Transformations for Deep Neural Network"
      <http://arxiv.org/abs/1611.05431>`_ paper.
  
      Parameters
      ----------
      cardinality: int
          Number of groups
      bottleneck_width: int
          Width of bottleneck block
      pretrained : bool or str
          Boolean value controls whether to load the default pretrained weights for model.
          String value represents the hashtag for a certain version of pretrained weights.
      ctx : Context, default CPU
          The context in which to load the pretrained weights.
      root : str, default '~/.mxnet/models'
          Location for keeping the model parameters.
      norm_layer : object
          Normalization layer used (default: :class:`mxnet.gluon.nn.BatchNorm`)
          Can be :class:`mxnet.gluon.nn.BatchNorm` or :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      norm_kwargs : dict
          Additional `norm_layer` arguments, for example `num_devices=4`
          for :class:`mxnet.gluon.contrib.nn.SyncBatchNorm`.
      """
      kwargs['use_se'] = True
      if kwargs['pretrained']:
          msg = 'GluonCV does not have pretrained weights for resnext101e_64x4d at this moment. Please set pretrained=False.'
          raise RuntimeError(msg)
      return get_resnext(101, 64, 4, deep_stem=True, avg_down=True, **kwargs)
  