123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- # 2020.06.09-Changed for building GhostNet
- # Huawei Technologies Co., Ltd. <foss@huawei.com>
- """
- Creates a GhostNet Model as defined in:
- GhostNet: More Features from Cheap Operations By Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu.
- https://arxiv.org/abs/1911.11907
- Modified from https://github.com/d-li14/mobilenetv3.pytorch and https://github.com/rwightman/pytorch-image-models
- """
- import os
- import torch
- import torch.nn as nn
- import torch.nn.functional as F
- import math
- import logging
- from collections import OrderedDict
- from torchocr.networks.CommonModules import CBAM
- def _make_divisible(v, divisor, min_value=None):
- """
- This function is taken from the original tf repo.
- It ensures that all layers have a channel number that is divisible by 8
- It can be seen here:
- https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
- """
- if min_value is None:
- min_value = divisor
- new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
- # Make sure that round down does not go down by more than 10%.
- if new_v < 0.9 * v:
- new_v += divisor
- return new_v
- def hard_sigmoid(x, inplace: bool = False):
- if inplace:
- return x.add_(3.).clamp_(0., 6.).div_(6.)
- else:
- return F.relu6(x + 3.) / 6.
- class SqueezeExcite(nn.Module):
- def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None,
- act_layer=nn.ReLU, gate_fn=hard_sigmoid, divisor=4, **_):
- super(SqueezeExcite, self).__init__()
- self.gate_fn = gate_fn
- reduced_chs = _make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)
- self.avg_pool = nn.AdaptiveAvgPool2d(1)
- self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)
- self.act1 = act_layer(inplace=True)
- self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)
- def forward(self, x):
- x_se = self.avg_pool(x)
- x_se = self.conv_reduce(x_se)
- x_se = self.act1(x_se)
- x_se = self.conv_expand(x_se)
- x = x * self.gate_fn(x_se)
- return x
- class ConvBnAct(nn.Module):
- def __init__(self, in_chs, out_chs, kernel_size,
- stride=1, act_layer=nn.ReLU):
- super(ConvBnAct, self).__init__()
- self.conv = nn.Conv2d(in_chs, out_chs, kernel_size, stride, kernel_size // 2, bias=False)
- self.bn1 = nn.BatchNorm2d(out_chs)
- self.act1 = act_layer(inplace=True)
- def forward(self, x):
- x = self.conv(x)
- x = self.bn1(x)
- x = self.act1(x)
- return x
- class GhostModule(nn.Module):
- def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):
- super(GhostModule, self).__init__()
- self.oup = oup
- init_channels = math.ceil(oup / ratio)
- new_channels = init_channels * (ratio - 1)
- self.primary_conv = nn.Sequential(
- nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size // 2, bias=False),
- nn.BatchNorm2d(init_channels),
- nn.ReLU(inplace=True) if relu else nn.Sequential(),
- )
- self.cheap_operation = nn.Sequential(
- nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size // 2, groups=init_channels, bias=False),
- nn.BatchNorm2d(new_channels),
- nn.ReLU(inplace=True) if relu else nn.Sequential(),
- )
- def forward(self, x):
- x1 = self.primary_conv(x)
- x2 = self.cheap_operation(x1)
- out = torch.cat([x1, x2], dim=1)
- return out[:, :self.oup, :, :]
- class GhostBottleneck(nn.Module):
- """ Ghost bottleneck w/ optional SE"""
- def __init__(self, in_chs, mid_chs, out_chs, dw_kernel_size=3,
- stride=1, act_layer=nn.ReLU, se_ratio=0.):
- super(GhostBottleneck, self).__init__()
- has_se = se_ratio is not None and se_ratio > 0.
- self.stride = stride
- # Point-wise expansion
- self.ghost1 = GhostModule(in_chs, mid_chs, relu=True)
- # Depth-wise convolution
- if self.stride > 1:
- self.conv_dw = nn.Conv2d(mid_chs, mid_chs, dw_kernel_size, stride=stride,
- padding=(dw_kernel_size - 1) // 2,
- groups=mid_chs, bias=False)
- self.bn_dw = nn.BatchNorm2d(mid_chs)
- # Squeeze-and-excitation
- if has_se:
- self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)
- # self.se = CBAM(mid_chs,mid_chs)
- else:
- self.se = None
- # Point-wise linear projection
- self.ghost2 = GhostModule(mid_chs, out_chs, relu=False)
- # shortcut
- if (in_chs == out_chs and self.stride == 1):
- self.shortcut = nn.Sequential()
- else:
- self.shortcut = nn.Sequential(
- nn.Conv2d(in_chs, in_chs, dw_kernel_size, stride=stride,
- padding=(dw_kernel_size - 1) // 2, groups=in_chs, bias=False),
- nn.BatchNorm2d(in_chs),
- nn.Conv2d(in_chs, out_chs, 1, stride=1, padding=0, bias=False),
- nn.BatchNorm2d(out_chs),
- )
- def forward(self, x):
- residual = x
- # 1st ghost bottleneck
- x = self.ghost1(x)
- # Depth-wise convolution
- if self.stride > 1:
- x = self.conv_dw(x)
- x = self.bn_dw(x)
- # Squeeze-and-excitation
- if self.se is not None:
- x = self.se(x)
- # 2nd ghost bottleneck
- x = self.ghost2(x)
- x += self.shortcut(residual)
- return x
- class GhostNet(nn.Module):
- def __init__(self, cfgs, num_classes=1000, width=1.0, dropout=0.2, pretrained=True,**kwargs):
- super(GhostNet, self).__init__()
- # setting of inverted residual blocks
- model_name = kwargs.get('model_name', 'default')
- self.disable_se = kwargs.get('disable_se', False)
- if model_name=='default':
- self.cfgs= [
- # k, t, c, SE, s
- # stage1
- [[3, 16, 16, 0, 1]],
- # stage2
- [[3, 48, 24, 0, 2]],
- [[3, 72, 24, 0, 1]],
- # stage3
- [[5, 72, 40, 0.25, 2]],
- [[5, 120, 40, 0.25, 1]],
- # stage4
- [[3, 240, 80, 0, 2]],
- [[3, 200, 80, 0, 1],
- [3, 184, 80, 0, 1],
- [3, 184, 80, 0, 1],
- [3, 480, 112, 0.25, 1],
- [3, 672, 112, 0.25, 1]
- ],
- # stage5
- [[5, 672, 160, 0.25, 2]],
- [[5, 960, 160, 0, 1],
- [5, 960, 160, 0.25, 1],
- [5, 960, 160, 0, 1],
- [5, 960, 160, 0.25, 1]
- ]
- ]
- # self.cfgs = cfgs
- # self.dropout = dropout
- # building first layer
- output_channel = _make_divisible(16 * width, 4) # 16
- self.conv_stem = nn.Conv2d(3, output_channel, 3, 2, 1, bias=False)
- self.bn1 = nn.BatchNorm2d(output_channel)
- self.act1 = nn.ReLU(inplace=True)
- input_channel = output_channel
- # building inverted residual blocks
- stages = []
- block = GhostBottleneck
- self.keep_stages = []
- self.out_channels = []
- i = 0
- for cfg in self.cfgs:
- layers = []
- for k, exp_size, c, se_ratio, s in cfg:
- if s == 2 and i > 2:
- self.out_channels.append(input_channel)
- output_channel = _make_divisible(c * width, 4)
- hidden_channel = _make_divisible(exp_size * width, 4)
- layers.append(block(input_channel, hidden_channel, output_channel, k, s,
- se_ratio=se_ratio))
- input_channel = output_channel
- i += 1
- stages.append(nn.Sequential(*layers))
- output_channel = _make_divisible(exp_size * width, 4)
- stages.append(nn.Sequential(ConvBnAct(input_channel, output_channel, 1)))
- input_channel = output_channel
- self.out_channels.append(input_channel)
- self.blocks = nn.Sequential(*stages)
- # building last several layers
- # output_channel = 1280
- # self.global_pool = nn.AdaptiveAvgPool2d((1, 1))
- # self.conv_head = nn.Conv2d(input_channel, output_channel, 1, 1, 0, bias=True)
- # self.act2 = nn.ReLU(inplace=True)
- # self.classifier = nn.Linear(output_channel, num_classes)
- if pretrained:
- ckpt_path = f'./weights/state_dict_73.98.pth'
- logger = logging.getLogger('torchocr')
- if os.path.exists(ckpt_path):
- logger.info('load imagenet weights')
- dic_ckpt = torch.load(ckpt_path)
- filtered_dict = OrderedDict()
- for key in dic_ckpt.keys():
- flag = key.find('se') != -1
- if self.disable_se and flag:
- continue
- filtered_dict[key] = dic_ckpt[key]
- self.load_state_dict(filtered_dict)
- else:
- logger.info(f'{ckpt_path} not exists')
- def forward(self, x):
- x = self.conv_stem(x)
- x = self.bn1(x)
- x = self.act1(x)
- out = []
- for stage in self.blocks:
- x = stage(x)
- out.append(x)
- return [out[2], out[4], out[6], out[9]]
- def ghostnet(**kwargs):
- """
- Constructs a GhostNet model
- """
- cfgs = [
- # k, t, c, SE, s
- # stage1
- [[3, 16, 16, 0, 1]],
- # stage2
- [[3, 48, 24, 0, 2]],
- [[3, 72, 24, 0, 1]],
- # stage3
- [[5, 72, 40, 0.25, 2]],
- [[5, 120, 40, 0.25, 1]],
- # stage4
- [[3, 240, 80, 0, 2]],
- [[3, 200, 80, 0, 1],
- [3, 184, 80, 0, 1],
- [3, 184, 80, 0, 1],
- [3, 480, 112, 0.25, 1],
- [3, 672, 112, 0.25, 1]
- ],
- # stage5
- [[5, 672, 160, 0.25, 2]],
- [[5, 960, 160, 0, 1],
- [5, 960, 160, 0.25, 1],
- [5, 960, 160, 0, 1],
- [5, 960, 160, 0.25, 1]
- ]
- ]
- return GhostNet(cfgs, **kwargs)
- if __name__ == '__main__':
- model = ghostnet()
- model.eval()
- # print(model)
- input = torch.randn(32, 3, 320, 256)
- y = model(input)
- print(y.size())
|