123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- '''
- File name: senet.py
- Squeeze-and-Excitation Networks (SeNet) implementation for fast.ai/pytorch with pretrained model
- Credit https://github.com/hujie-frank/SENet
- SENet is the winner of ImageNet-2017 (https://arxiv.org/pdf/1709.01507.pdf).
- '''
- from collections import OrderedDict
- import math
- import torch.utils.model_zoo as model_zoo
- from ..layers import *
- import torch.nn as nn
- from torch.utils import model_zoo
- __all__ = ['SENet', 'senet154', 'se_resnet50', 'se_resnet101', 'se_resnet152',
- 'se_resnext50_32x4d', 'se_resnext101_32x4d']
- pretrained_settings = {
- 'senet154': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/senet154-c7b49a05.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- 'se_resnet50': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/se_resnet50-ce0d4300.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- 'se_resnet101': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/se_resnet101-7e38fcc6.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- 'se_resnet152': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/se_resnet152-d17c99b7.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- 'se_resnext50_32x4d': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/se_resnext50_32x4d-a260b3a4.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- 'se_resnext101_32x4d': {
- 'imagenet': {
- 'url': 'http://data.lip6.fr/cadene/pretrainedmodels/se_resnext101_32x4d-3b2fe3d8.pth',
- 'input_space': 'RGB',
- 'input_size': [3, 224, 224],
- 'input_range': [0, 1],
- 'mean': [0.485, 0.456, 0.406],
- 'std': [0.229, 0.224, 0.225],
- 'num_classes': 1000
- }
- },
- }
- class SEModule(nn.Module):
- def __init__(self, channels, reduction):
- super(SEModule, self).__init__()
- self.avg_pool = nn.AdaptiveAvgPool2d(1)
- self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1,
- padding=0)
- self.relu = nn.ReLU(inplace=True)
- self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1,
- padding=0)
- self.sigmoid = nn.Sigmoid()
- def forward(self, x):
- module_input = x
- x = self.avg_pool(x)
- x = self.fc1(x)
- x = self.relu(x)
- x = self.fc2(x)
- x = self.sigmoid(x)
- return module_input * x
- class Bottleneck(nn.Module):
- """
- Base class for bottlenecks that implements `forward()` method.
- """
- def forward(self, x):
- residual = x
- out = self.conv1(x)
- out = self.bn1(out)
- out = self.relu(out)
- out = self.conv2(out)
- out = self.bn2(out)
- out = self.relu(out)
- out = self.conv3(out)
- out = self.bn3(out)
- if self.downsample is not None:
- residual = self.downsample(x)
- out = self.se_module(out) + residual
- out = self.relu(out)
- return out
- class SEBottleneck(Bottleneck):
- """
- Bottleneck for SENet154.
- """
- expansion = 4
- def __init__(self, inplanes, planes, groups, reduction, stride=1,
- downsample=None):
- super(SEBottleneck, self).__init__()
- self.conv1 = nn.Conv2d(inplanes, planes * 2, kernel_size=1, bias=False)
- self.bn1 = nn.BatchNorm2d(planes * 2)
- self.conv2 = nn.Conv2d(planes * 2, planes * 4, kernel_size=3,
- stride=stride, padding=1, groups=groups,
- bias=False)
- self.bn2 = nn.BatchNorm2d(planes * 4)
- self.conv3 = nn.Conv2d(planes * 4, planes * 4, kernel_size=1,
- bias=False)
- self.bn3 = nn.BatchNorm2d(planes * 4)
- self.relu = nn.ReLU(inplace=True)
- self.se_module = SEModule(planes * 4, reduction=reduction)
- self.downsample = downsample
- self.stride = stride
- class SEResNetBottleneck(Bottleneck):
- """
- ResNet bottleneck with a Squeeze-and-Excitation module. It follows Caffe
- implementation and uses `stride=stride` in `conv1` and not in `conv2`
- (the latter is used in the torchvision implementation of ResNet).
- """
- expansion = 4
- def __init__(self, inplanes, planes, groups, reduction, stride=1,
- downsample=None):
- super(SEResNetBottleneck, self).__init__()
- self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False,
- stride=stride)
- self.bn1 = nn.BatchNorm2d(planes)
- self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1,
- groups=groups, bias=False)
- self.bn2 = nn.BatchNorm2d(planes)
- self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
- self.bn3 = nn.BatchNorm2d(planes * 4)
- self.relu = nn.ReLU(inplace=True)
- self.se_module = SEModule(planes * 4, reduction=reduction)
- self.downsample = downsample
- self.stride = stride
- class SEResNeXtBottleneck(Bottleneck):
- """
- ResNeXt bottleneck type C with a Squeeze-and-Excitation module.
- """
- expansion = 4
- def __init__(self, inplanes, planes, groups, reduction, stride=1,
- downsample=None, base_width=4):
- super(SEResNeXtBottleneck, self).__init__()
- width = math.floor(planes * (base_width / 64)) * groups
- self.conv1 = nn.Conv2d(inplanes, width, kernel_size=1, bias=False,
- stride=1)
- self.bn1 = nn.BatchNorm2d(width)
- self.conv2 = nn.Conv2d(width, width, kernel_size=3, stride=stride,
- padding=1, groups=groups, bias=False)
- self.bn2 = nn.BatchNorm2d(width)
- self.conv3 = nn.Conv2d(width, planes * 4, kernel_size=1, bias=False)
- self.bn3 = nn.BatchNorm2d(planes * 4)
- self.relu = nn.ReLU(inplace=True)
- self.se_module = SEModule(planes * 4, reduction=reduction)
- self.downsample = downsample
- self.stride = stride
- class SENet(nn.Module):
- def __init__(self, block, layers, groups, reduction, dropout_p=0.2,
- inplanes=128, input_3x3=True, downsample_kernel_size=3,
- downsample_padding=1, num_classes=1000):
- """
- Parameters
- ----------
- block (nn.Module): Bottleneck class.
- - For SENet154: SEBottleneck
- - For SE-ResNet models: SEResNetBottleneck
- - For SE-ResNeXt models: SEResNeXtBottleneck
- layers (list of ints): Number of residual blocks for 4 layers of the
- network (layer1...layer4).
- groups (int): Number of groups for the 3x3 convolution in each
- bottleneck block.
- - For SENet154: 64
- - For SE-ResNet models: 1
- - For SE-ResNeXt models: 32
- reduction (int): Reduction ratio for Squeeze-and-Excitation modules.
- - For all models: 16
- dropout_p (float or None): Drop probability for the Dropout layer.
- If `None` the Dropout layer is not used.
- - For SENet154: 0.2
- - For SE-ResNet models: None
- - For SE-ResNeXt models: None
- inplanes (int): Number of input channels for layer1.
- - For SENet154: 128
- - For SE-ResNet models: 64
- - For SE-ResNeXt models: 64
- input_3x3 (bool): If `True`, use three 3x3 convolutions instead of
- a single 7x7 convolution in layer0.
- - For SENet154: True
- - For SE-ResNet models: False
- - For SE-ResNeXt models: False
- downsample_kernel_size (int): Kernel size for downsampling convolutions
- in layer2, layer3 and layer4.
- - For SENet154: 3
- - For SE-ResNet models: 1
- - For SE-ResNeXt models: 1
- downsample_padding (int): Padding for downsampling convolutions in
- layer2, layer3 and layer4.
- - For SENet154: 1
- - For SE-ResNet models: 0
- - For SE-ResNeXt models: 0
- num_classes (int): Number of outputs in `last_linear` layer.
- - For all models: 1000
- """
- super(SENet, self).__init__()
- self.inplanes = inplanes
- if input_3x3:
- layer0_modules = [
- ('conv1', nn.Conv2d(3, 64, 3, stride=2, padding=1,
- bias=False)),
- ('bn1', nn.BatchNorm2d(64)),
- ('relu1', nn.ReLU(inplace=True)),
- ('conv2', nn.Conv2d(64, 64, 3, stride=1, padding=1,
- bias=False)),
- ('bn2', nn.BatchNorm2d(64)),
- ('relu2', nn.ReLU(inplace=True)),
- ('conv3', nn.Conv2d(64, inplanes, 3, stride=1, padding=1,
- bias=False)),
- ('bn3', nn.BatchNorm2d(inplanes)),
- ('relu3', nn.ReLU(inplace=True)),
- ]
- else:
- layer0_modules = [
- ('conv1', nn.Conv2d(3, inplanes, kernel_size=7, stride=2,
- padding=3, bias=False)),
- ('bn1', nn.BatchNorm2d(inplanes)),
- ('relu1', nn.ReLU(inplace=True)),
- ]
- # To preserve compatibility with Caffe weights `ceil_mode=True`
- # is used instead of `padding=1`.
- layer0_modules.append(('pool', nn.MaxPool2d(3, stride=2,
- ceil_mode=True)))
- self.layer0 = nn.Sequential(OrderedDict(layer0_modules))
- self.layer1 = self._make_layer(
- block,
- planes=64,
- blocks=layers[0],
- groups=groups,
- reduction=reduction,
- downsample_kernel_size=1,
- downsample_padding=0
- )
- self.layer2 = self._make_layer(
- block,
- planes=128,
- blocks=layers[1],
- stride=2,
- groups=groups,
- reduction=reduction,
- downsample_kernel_size=downsample_kernel_size,
- downsample_padding=downsample_padding
- )
- self.layer3 = self._make_layer(
- block,
- planes=256,
- blocks=layers[2],
- stride=2,
- groups=groups,
- reduction=reduction,
- downsample_kernel_size=downsample_kernel_size,
- downsample_padding=downsample_padding
- )
- self.layer4 = self._make_layer(
- block,
- planes=512,
- blocks=layers[3],
- stride=2,
- groups=groups,
- reduction=reduction,
- downsample_kernel_size=downsample_kernel_size,
- downsample_padding=downsample_padding
- )
- self.avg_pool = nn.AvgPool2d(7, stride=1)
- self.dropout = nn.Dropout(dropout_p) if dropout_p is not None else None
- self.last_linear = nn.Linear(512 * block.expansion, num_classes)
- def _make_layer(self, block, planes, blocks, groups, reduction, stride=1,
- downsample_kernel_size=1, downsample_padding=0):
- downsample = None
- if stride != 1 or self.inplanes != planes * block.expansion:
- downsample = nn.Sequential(
- nn.Conv2d(self.inplanes, planes * block.expansion,
- kernel_size=downsample_kernel_size, stride=stride,
- padding=downsample_padding, bias=False),
- nn.BatchNorm2d(planes * block.expansion),
- )
- layers = []
- layers.append(block(self.inplanes, planes, groups, reduction, stride,
- downsample))
- self.inplanes = planes * block.expansion
- for i in range(1, blocks):
- layers.append(block(self.inplanes, planes, groups, reduction))
- return nn.Sequential(*layers)
- def features(self, x):
- x = self.layer0(x)
- x = self.layer1(x)
- x = self.layer2(x)
- x = self.layer3(x)
- x = self.layer4(x)
- return x
- def logits(self, x):
- x = self.avg_pool(x)
- if self.dropout is not None:
- x = self.dropout(x)
- x = x.view(x.size(0), -1)
- x = self.last_linear(x)
- return x
- def forward(self, x):
- x = self.features(x)
- x = self.logits(x)
- return x
- def initialize_pretrained_model(model, num_classes, settings):
- assert num_classes == settings['num_classes'], \
- 'num_classes should be {}, but is {}'.format(
- settings['num_classes'], num_classes)
- model.load_state_dict(model_zoo.load_url(settings['url']))
- model.input_space = settings['input_space']
- model.input_size = settings['input_size']
- model.input_range = settings['input_range']
- model.mean = settings['mean']
- model.std = settings['std']
- def senet154(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEBottleneck, [3, 8, 36, 3], groups=64, reduction=16,
- dropout_p=0.2, num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['senet154'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
- def se_resnet50(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEResNetBottleneck, [3, 4, 6, 3], groups=1, reduction=16,
- dropout_p=None, inplanes=64, input_3x3=False,
- downsample_kernel_size=1, downsample_padding=0,
- num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['se_resnet50'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
- def se_resnet101(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEResNetBottleneck, [3, 4, 23, 3], groups=1, reduction=16,
- dropout_p=None, inplanes=64, input_3x3=False,
- downsample_kernel_size=1, downsample_padding=0,
- num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['se_resnet101'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
- def se_resnet152(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEResNetBottleneck, [3, 8, 36, 3], groups=1, reduction=16,
- dropout_p=None, inplanes=64, input_3x3=False,
- downsample_kernel_size=1, downsample_padding=0,
- num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['se_resnet152'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
- def se_resnext50_32x4d(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEResNeXtBottleneck, [3, 4, 6, 3], groups=32, reduction=16,
- dropout_p=None, inplanes=64, input_3x3=False,
- downsample_kernel_size=1, downsample_padding=0,
- num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['se_resnext50_32x4d'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
- def se_resnext101_32x4d(num_classes=1000, pretrained='imagenet'):
- model = SENet(SEResNeXtBottleneck, [3, 4, 23, 3], groups=32, reduction=16,
- dropout_p=None, inplanes=64, input_3x3=False,
- downsample_kernel_size=1, downsample_padding=0,
- num_classes=num_classes)
- if pretrained is not None:
- settings = pretrained_settings['se_resnext101_32x4d'][pretrained]
- initialize_pretrained_model(model, num_classes, settings)
- return model
|