# Source code for torchreid.models.squeezenet

    from __future__ import absolute_import
    from __future__ import division

    __all__ = [
        'squeezenet1_0',
        'squeezenet1_1',
        'squeezenet1_0_fc512'
    ]

    from collections import OrderedDict
    import math

    import torch
    import torch.nn as nn
    from torch.utils import model_zoo
    from torch.nn import functional as F
    import torch.nn.init as init
    import torchvision
    import torch.utils.model_zoo as model_zoo

    model_urls = {
        'squeezenet1_0': 'https://download.pytorch.org/models/squeezenet1_0-a815701f.pth',
        'squeezenet1_1': 'https://download.pytorch.org/models/squeezenet1_1-f364aa15.pth',
    }

    class Fire(nn.Module):

        def __init__(self, inplanes, squeeze_planes,
                     expand1x1_planes, expand3x3_planes):
            super(Fire, self).__init__()
            self.inplanes = inplanes
            self.squeeze = nn.Conv2d(inplanes, squeeze_planes, kernel_size=1)
            self.squeeze_activation = nn.ReLU(inplace=True)
            self.expand1x1 = nn.Conv2d(squeeze_planes, expand1x1_planes,
                                       kernel_size=1)
            self.expand1x1_activation = nn.ReLU(inplace=True)
            self.expand3x3 = nn.Conv2d(squeeze_planes, expand3x3_planes,
                                       kernel_size=3, padding=1)
            self.expand3x3_activation = nn.ReLU(inplace=True)

        def forward(self, x):
            x = self.squeeze_activation(self.squeeze(x))
            return torch.cat([
                self.expand1x1_activation(self.expand1x1(x)),
                self.expand3x3_activation(self.expand3x3(x))
            ], 1)

    class SqueezeNet(nn.Module):
        """SqueezeNet.

        Reference:
            Iandola et al. SqueezeNet: AlexNet-level accuracy with 50x fewer parameters
            and< 0.5 MB model size. arXiv:1602.07360.

        Public keys:
            - ``squeezenet1_0``: SqueezeNet (version=1.0).
            - ``squeezenet1_1``: SqueezeNet (version=1.1).
            - ``squeezenet1_0_fc512``: SqueezeNet (version=1.0) + FC.
        """

        def __init__(self, num_classes, loss, version=1.0, fc_dims=None, dropout_p=None, **kwargs):
            super(SqueezeNet, self).__init__()
            self.loss = loss
            self.feature_dim = 512

            if version not in [1.0, 1.1]:
                raise ValueError('Unsupported SqueezeNet version {version}:'
                                 '1.0 or 1.1 expected'.format(version=version))

            if version == 1.0:
                self.features = nn.Sequential(
                    nn.Conv2d(3, 96, kernel_size=7, stride=2),
                    nn.ReLU(inplace=True),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(96, 16, 64, 64),
                    Fire(128, 16, 64, 64),
                    Fire(128, 32, 128, 128),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(256, 32, 128, 128),
                    Fire(256, 48, 192, 192),
                    Fire(384, 48, 192, 192),
                    Fire(384, 64, 256, 256),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(512, 64, 256, 256),
                )
            else:
                self.features = nn.Sequential(
                    nn.Conv2d(3, 64, kernel_size=3, stride=2),
                    nn.ReLU(inplace=True),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(64, 16, 64, 64),
                    Fire(128, 16, 64, 64),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(128, 32, 128, 128),
                    Fire(256, 32, 128, 128),
                    nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True),
                    Fire(256, 48, 192, 192),
                    Fire(384, 48, 192, 192),
                    Fire(384, 64, 256, 256),
                    Fire(512, 64, 256, 256),
                )

            self.global_avgpool = nn.AdaptiveAvgPool2d(1)
            self.fc = self._construct_fc_layer(fc_dims, 512, dropout_p)
            self.classifier = nn.Linear(self.feature_dim, num_classes)

            self._init_params()

        def _construct_fc_layer(self, fc_dims, input_dim, dropout_p=None):
            """Constructs fully connected layer

            Args:
                fc_dims (list or tuple): dimensions of fc layers, if None, no fc layers are constructed
                input_dim (int): input dimension
                dropout_p (float): dropout probability, if None, dropout is unused
            """
            if fc_dims is None:
                self.feature_dim = input_dim
                return None

            assert isinstance(fc_dims, (list, tuple)), 'fc_dims must be either list or tuple, but got {}'.format(type(fc_dims))

            layers = []
            for dim in fc_dims:
                layers.append(nn.Linear(input_dim, dim))
                layers.append(nn.BatchNorm1d(dim))
                layers.append(nn.ReLU(inplace=True))
                if dropout_p is not None:
                    layers.append(nn.Dropout(p=dropout_p))
                input_dim = dim

            self.feature_dim = fc_dims[-1]

            return nn.Sequential(*layers)

        def _init_params(self):
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.BatchNorm2d):
                    nn.init.constant_(m.weight, 1)
                    nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.BatchNorm1d):
                    nn.init.constant_(m.weight, 1)
                    nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.Linear):
                    nn.init.normal_(m.weight, 0, 0.01)
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)

        def forward(self, x):
            f = self.features(x)
            v = self.global_avgpool(f)
            v = v.view(v.size(0), -1)

            if self.fc is not None:
                v = self.fc(v)

            if not self.training:
                return v

            y = self.classifier(v)

            if self.loss == 'softmax':
                return y
            elif self.loss == 'triplet':
                return y, v
            else:
                raise KeyError('Unsupported loss: {}'.format(self.loss))

    def init_pretrained_weights(model, model_url):
        """Initializes model with pretrained weights.

        Layers that don't match with pretrained layers in name or size are kept unchanged.
        """
        pretrain_dict = model_zoo.load_url(model_url, map_location=None)
        model_dict = model.state_dict()
        pretrain_dict = {k: v for k, v in pretrain_dict.items() if k in model_dict and model_dict[k].size() == v.size()}
        model_dict.update(pretrain_dict)
        model.load_state_dict(model_dict)
        print('Initialized model with pretrained weights from {}'.format(model_url))

    def squeezenet1_0(num_classes, loss='softmax', pretrained=True, **kwargs):
        model = SqueezeNet(
            num_classes,
            loss,
            version=1.0,
            fc_dims=None,
            dropout_p=None,
            **kwargs
        )
        if pretrained:
            init_pretrained_weights(model, model_urls['squeezenet1_0'])
        return model

    def squeezenet1_0_fc512(num_classes, loss='softmax', pretrained=True, **kwargs):
        model = SqueezeNet(
            num_classes,
            loss,
            version=1.0,
            fc_dims=[512],
            dropout_p=None,
            **kwargs
        )
        if pretrained:
            init_pretrained_weights(model, model_urls['squeezenet1_0'])
        return model

    def squeezenet1_1(num_classes, loss='softmax', pretrained=True, **kwargs):
        model = SqueezeNet(
            num_classes,
            loss,
            version=1.1,
            fc_dims=None,
            dropout_p=None,
            **kwargs
        )
        if pretrained:
            init_pretrained_weights(model, model_urls['squeezenet1_1'])
        return model