text: """
      SE-ResNeXt for ImageNet-1K, implemented in TensorFlow.
      Original paper: 'Squeeze-and-Excitation Networks,' https://arxiv.org/abs/1709.01507.
  """
  
  __all__ = ['SEResNeXt', 'seresnext50_32x4d', 'seresnext101_32x4d', 'seresnext101_64x4d']
  
  import os
  import tensorflow as tf
  from .common import conv1x1_block, se_block, is_channels_first, flatten, keras_layer, clear_keras_layers
  from .resnet import res_init_block
  from .resnext import resnext_bottleneck
  
  
  def seresnext_unit(x,
                     in_channels,
                     out_channels,
                     strides,
                     cardinality,
                     bottleneck_width,
                     training,
                     data_format,
                     name="seresnext_unit"):
      """
      SE-ResNeXt unit.
  
      Parameters:
      ----------
      x : Tensor
          Input tensor.
      in_channels : int
          Number of input channels.
      out_channels : int
          Number of output channels.
      strides : int or tuple/list of 2 int
          Strides of the convolution.
      cardinality: int
          Number of groups.
      bottleneck_width: int
          Width of bottleneck block.
      training : bool, or a TensorFlow boolean scalar tensor
        Whether to return the output in training mode or in inference mode.
      data_format : str
          The ordering of the dimensions in tensors.
      name : str, default 'seresnext_unit'
          Unit name.
  
      Returns
      -------
      Tensor
          Resulted tensor.
      """
      resize_identity = (in_channels != out_channels) or (strides != 1)
      if resize_identity:
          identity = conv1x1_block(
              x=x,
              in_channels=in_channels,
              out_channels=out_channels,
              strides=strides,
              activation=None,
              training=training,
              data_format=data_format,
              name=name + "/identity_conv")
      else:
          identity = x
  
      x = resnext_bottleneck(
          x=x,
          in_channels=in_channels,
          out_channels=out_channels,
          strides=strides,
          cardinality=cardinality,
          bottleneck_width=bottleneck_width,
          training=training,
          data_format=data_format,
          name=name + "/body")
  
      x = se_block(
          x=x,
          channels=out_channels,
          data_format=data_format,
          name=name + "/se")
  
      x = x + identity
  
      x = tf.nn.relu(x, name=name + "/activ")
      return x
  
  
  class SEResNeXt(object):
      """
      SE-ResNeXt model from 'Squeeze-and-Excitation Networks,' https://arxiv.org/abs/1709.01507.
  
      Parameters:
      ----------
      channels : list of list of int
          Number of output channels for each unit.
      init_block_channels : int
          Number of output channels for the initial unit.
      cardinality: int
          Number of groups.
      bottleneck_width: int
          Width of bottleneck block.
      in_channels : int, default 3
          Number of input channels.
      in_size : tuple of two ints, default (224, 224)
          Spatial size of the expected input image.
      classes : int, default 1000
          Number of classification classes.
      data_format : str, default 'channels_last'
          The ordering of the dimensions in tensors.
      """
      def __init__(self,
                   channels,
                   init_block_channels,
                   cardinality,
                   bottleneck_width,
                   in_channels=3,
                   in_size=(224, 224),
                   classes=1000,
                   layer_index=0,
                   data_format="channels_last",
                   **kwargs):
          super(SEResNeXt, self).__init__(**kwargs)
          assert (data_format in ["channels_last", "channels_first"])
          self.channels = channels
          self.init_block_channels = init_block_channels
          self.cardinality = cardinality
          self.bottleneck_width = bottleneck_width
          self.in_channels = in_channels
          self.in_size = in_size
          self.classes = classes
          self.data_format = data_format
          self.layer_index = layer_index
  
      def __call__(self,
                   x,
                   training=False):
          """
          Build a model graph.
  
          Parameters:
          ----------
          x : Tensor
              Input tensor.
          training : bool, or a TensorFlow boolean scalar tensor, default False
            Whether to return the output in training mode or in inference mode.
  
          Returns
          -------
          Tensor
              Resulted tensor.
          """
          in_channels = self.in_channels
          x = res_init_block(
              x=x,
              in_channels=in_channels,
              out_channels=self.init_block_channels,
              training=training,
              data_format=self.data_format,
              name="features/init_block")
          in_channels = self.init_block_channels
          for i, channels_per_stage in enumerate(self.channels):
              for j, out_channels in enumerate(channels_per_stage):
                  strides = 2 if (j == 0) and (i != 0) else 1
                  x = seresnext_unit(
                      x=x,
                      in_channels=in_channels,
                      out_channels=out_channels,
                      strides=strides,
                      cardinality=self.cardinality,
                      bottleneck_width=self.bottleneck_width,
                      training=training,
                      data_format=self.data_format,
                      name="features/stage{}/unit{}".format(i + 1, j + 1))
                  in_channels = out_channels
              if self.layer_index == i + 1:
                  return x
          if self.layer_index == -2:
              return x
          x = tf.keras.layers.AveragePooling2D(
              pool_size=7,
              strides=1,
              data_format=self.data_format,
              name="features/final_pool")(x)
  
          # x = tf.layers.flatten(x)
          x = flatten(
              x=x,
              data_format=self.data_format)
          if self.layer_index == -1:
              return x
          x = keras_layer("output", tf.keras.layers.Dense,
              units=self.classes)(x)
  
          return x
  
  
  def get_seresnext(blocks,
                    cardinality,
                    bottleneck_width,
                    model_name=None,
                    pretrained=False,
                    root=os.path.join("~", ".tensorflow", "models"),
                    **kwargs):
      """
      Create SE-ResNeXt model with specific parameters.
  
      Parameters:
      ----------
      blocks : int
          Number of blocks.
      cardinality: int
          Number of groups.
      bottleneck_width: int
          Width of bottleneck block.
      model_name : str or None, default None
          Model name for loading pretrained model.
      pretrained : bool, default False
          Whether to load the pretrained weights for model.
      root : str, default '~/.tensorflow/models'
          Location for keeping the model parameters.
  
      Returns
      -------
      functor
          Functor for model graph creation with extra fields.
      """
  
      if blocks == 50:
          layers = [3, 4, 6, 3]
      elif blocks == 101:
          layers = [3, 4, 23, 3]
      else:
          raise ValueError("Unsupported SE-ResNeXt with number of blocks: {}".format(blocks))
  
      init_block_channels = 64
      channels_per_layers = [256, 512, 1024, 2048]
  
      channels = [[ci] * li for (ci, li) in zip(channels_per_layers, layers)]
  
      net = SEResNeXt(
          channels=channels,
          init_block_channels=init_block_channels,
          cardinality=cardinality,
          bottleneck_width=bottleneck_width,
          **kwargs)
  
      if pretrained:
          if (model_name is None) or (not model_name):
              raise ValueError("Parameter `model_name` should be properly initialized for loading pretrained model.")
          from .model_store import download_state_dict
          net.state_dict, net.file_path = download_state_dict(
              model_name=model_name,
              local_model_store_dir_path=root)
      else:
          net.state_dict = None
          net.file_path = None
  
      return net
  
  
  def seresnext50_32x4d(**kwargs):
      """
      SE-ResNeXt-50 (32x4d) model from 'Squeeze-and-Excitation Networks,' https://arxiv.org/abs/1709.01507.
  
      Parameters:
      ----------
      pretrained : bool, default False
          Whether to load the pretrained weights for model.
      root : str, default '~/.tensorflow/models'
          Location for keeping the model parameters.
  
      Returns
      -------
      functor
          Functor for model graph creation with extra fields.
      """
      return get_seresnext(blocks=50, cardinality=32, bottleneck_width=4, model_name="seresnext50_32x4d", **kwargs)
  
  
  def seresnext101_32x4d(**kwargs):
      """
      SE-ResNeXt-101 (32x4d) model from 'Squeeze-and-Excitation Networks,' https://arxiv.org/abs/1709.01507.
  
      Parameters:
      ----------
      pretrained : bool, default False
          Whether to load the pretrained weights for model.
      root : str, default '~/.tensorflow/models'
          Location for keeping the model parameters.
  
      Returns
      -------
      functor
          Functor for model graph creation with extra fields.
      """
      return get_seresnext(blocks=101, cardinality=32, bottleneck_width=4, model_name="seresnext101_32x4d", **kwargs)
  
  
  def seresnext101_64x4d(**kwargs):
      """
      SE-ResNeXt-101 (64x4d) model from 'Squeeze-and-Excitation Networks,' https://arxiv.org/abs/1709.01507.
  
      Parameters:
      ----------
      pretrained : bool, default False
          Whether to load the pretrained weights for model.
      root : str, default '~/.tensorflow/models'
          Location for keeping the model parameters.
  
      Returns
      -------
      functor
          Functor for model graph creation with extra fields.
      """
      return get_seresnext(blocks=101, cardinality=64, bottleneck_width=4, model_name="seresnext101_64x4d", **kwargs)
  
  
  def _test():
      import numpy as np
  
      data_format = "channels_last"
      pretrained = False
  
      models = [
          seresnext50_32x4d,
          seresnext101_32x4d,
          seresnext101_64x4d,
      ]
  
      for model in models:
  
          net = model(pretrained=pretrained, data_format=data_format)
          x = tf.placeholder(
              dtype=tf.float32,
              shape=(None, 3, 224, 224) if is_channels_first(data_format) else (None, 224, 224, 3),
              name="xx")
          y_net = net(x)
          y_net2 = net(x) #reuse
  
          weight_count = np.sum([np.prod(v.get_shape().as_list()) for v in tf.trainable_variables()])
          print("m={}, {}".format(model.__name__, weight_count))
          assert (model != seresnext50_32x4d or weight_count == 27559896)
          assert (model != seresnext101_32x4d or weight_count == 48955416)
          assert (model != seresnext101_64x4d or weight_count == 88232984)
  
          with tf.Session() as sess:
              if pretrained:
                  from .model_store import init_variables_from_state_dict
                  init_variables_from_state_dict(sess=sess, state_dict=net.state_dict)
              else:
                  sess.run(tf.global_variables_initializer())
              x_value = np.zeros((1, 3, 224, 224) if is_channels_first(data_format) else (1, 224, 224, 3), np.float32)
              y = sess.run(y_net, feed_dict={x: x_value})
              assert (y.shape == (1, 1000))
          tf.reset_default_graph()
          clear_keras_layers()
  
  
  if __name__ == "__main__":
      _test()
  