GhostNet网络详解
1. GhostNet網(wǎng)絡(luò)
一張圖片經(jīng)過神經(jīng)網(wǎng)絡(luò)進(jìn)行特征提取后,能夠得到很多特征圖。
在特征圖中會有一些相似性很高,這就是神經(jīng)網(wǎng)絡(luò)中存在的特征圖冗雜的情況(如圖中扳手相連的兩幅特征圖)。
作者認(rèn)為可以對其中的一個(gè)特征圖進(jìn)行(Cheap Operations)簡單的線性運(yùn)算從而生成更多相似特征圖,從而可以使用更少的參數(shù)生成更多的特征圖,將相似的特征圖認(rèn)為是彼此的Ghost。
2. Ghost Module
作者用Ghost Module代替?zhèn)鹘y(tǒng)卷積,首先采用普通的1x1卷積對輸入圖片進(jìn)行通道數(shù)的壓縮,然后再進(jìn)行深度可分離卷積(逐層卷積)得到更多的特征圖,然后將不同的特征圖concat到一起,組合成新的output。
Ghost Module構(gòu)建代碼
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__()#ratio一般會指定成2,保證輸出特征層的通道數(shù)等于expself.oup = oupinit_channels = math.ceil(oup / ratio)new_channels = init_channels*(ratio-1)#利用1x1卷積對輸入進(jìn)來的特征圖進(jìn)行通道的濃縮,獲得特征通縮#跨通道的特征提取self.primary_conv = nn.Sequential(nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False), #1x1卷積的輸入通道數(shù)為GhostModule的輸出通道數(shù)oup/2nn.BatchNorm2d(init_channels), #1x1卷積后進(jìn)行標(biāo)準(zhǔn)化nn.ReLU(inplace=True) if relu else nn.Sequential(), #ReLU激活函數(shù))#在獲得特征濃縮后,使用逐層卷積,獲得額外的特征圖#跨特征點(diǎn)的特征提取 一般會設(shè)定大于1的卷積核大小self.cheap_operation = nn.Sequential(nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False), #groups參數(shù)的功能就是將普通卷積轉(zhuǎn)換成逐層卷據(jù)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)#將1x1卷積后的結(jié)果和逐層卷積后的結(jié)果進(jìn)行堆疊out = torch.cat([x1,x2], dim=1)return out[:,:self.oup,:,:]1x1卷積的輸入通道數(shù)為GhostModule的輸出通道數(shù)oup/2,因?yàn)樽罱K輸出特征層由1x1卷積后的結(jié)果和逐層卷積后的結(jié)果進(jìn)行堆疊組成的,進(jìn)行逐層卷積時(shí)特征層的通道數(shù)不變,如果要保證最終輸出特征層的通道數(shù)是一個(gè)固定值,那么就要使得1x1卷積后的結(jié)果的通道數(shù)為最終輸出特征層的1/2。
3. Ghost BottleNeck原理
Ghost BottleNeck是由Ghost Module組成的瓶頸結(jié)構(gòu)
Ghost BottleNeck整體架構(gòu)和Residual Block非常相似,也可以直接認(rèn)為是將Residual Block中的普通卷積操作替換成Ghost Module得到。
左圖中主干部分用用兩個(gè)Ghost Module(GM)串聯(lián)組成,其中第一個(gè)GM擴(kuò)大通道數(shù),第二個(gè)GM將通道數(shù)降低到與輸入通道數(shù)一致;殘差邊部分與ResNet一樣。由于S=1,因此不會對輸入特征層的高和寬進(jìn)行壓縮,其功能為加深網(wǎng)絡(luò)的深度。
右圖中主干部分的兩個(gè)GM之間加入了一個(gè)stride=2的Deepwise卷積,可以將特征圖高和寬進(jìn)行壓縮,使其大小降為輸入的1/2;在殘差邊部分,也會添加一個(gè)步長為2x2的深度可分離卷積和1x1的普通卷積,以保證Add操作可以對齊。由于S=2,因此會對輸入特征層的高和寬進(jìn)行壓縮,其功能為改變輸入特征層的形狀。
實(shí)際應(yīng)用中,為了進(jìn)一步提高效率,GhostModule中的所有常規(guī)卷積都用pointwise卷積代替。
Ghost BottleNeck構(gòu)建代碼:
class GhostBottleneck(nn.Module):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#首先利用一個(gè)ghost模塊進(jìn)行特征提取#此時(shí)指定的通道數(shù)會比較大,可以看作是逆殘差結(jié)構(gòu),進(jìn)行通道數(shù)上升self.ghost1 = GhostModule(in_chs, mid_chs, relu=True)if self.stride > 1: #根據(jù)步長判斷是否使用深度可分離卷積對輸入特征圖進(jìn)行高和寬的壓縮#如果要進(jìn)行特征圖的高寬壓縮,則進(jìn)行逐層卷積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)if has_se: #判斷是否使用注意力機(jī)制模塊self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)else:self.se = None#再次利用一個(gè)ghost模塊進(jìn)行特征提取self.ghost2 = GhostModule(mid_chs, out_chs, relu=False)#判斷步長是否等1、輸入通道和輸出通道是否一樣if (in_chs == out_chs and self.stride == 1):self.shortcut = nn.Sequential()else: #如果不一樣則利用深度可分離卷積和1x1卷積調(diào)整通道數(shù),保證主干部分和殘差邊部分能夠進(jìn)行相加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 = xx = self.ghost1(x)if self.stride > 1:x = self.conv_dw(x)x = self.bn_dw(x)if self.se is not None:x = self.se(x)x = self.ghost2(x)x += self.shortcut(residual)return x4. GhostNet的構(gòu)建
整個(gè)Ghostnet都是由Ghost Bottlenecks進(jìn)行組成的。
當(dāng)一張圖片輸入到Ghostnet當(dāng)中時(shí),首先進(jìn)行一個(gè)16通道的普通1x1卷積塊(卷積+標(biāo)準(zhǔn)化+激活函數(shù))。
之后就開始Ghost Bottlenecks的堆疊了,利用Ghost Bottlenecks,最終獲得了一個(gè)7x7x160的特征層(當(dāng)輸入是224x224x3的時(shí)候)。
然后利用一個(gè)1x1的卷積塊進(jìn)行通道數(shù)的調(diào)整,此時(shí)可以獲得一個(gè)7x7x960的特征層。
之后進(jìn)行一次全局平均池化,然后再利用一個(gè)1x1的卷積塊進(jìn)行通道數(shù)的調(diào)整,獲得一個(gè)1x1x1280的特征層。
最后平鋪后進(jìn)行全連接就可以進(jìn)行分類了。
class GhostNet(nn.Module):def __init__(self, cfgs, num_classes=1000, width=1.0, dropout=0.2):super(GhostNet, self).__init__()# setting of inverted residual blocksself.cfgs = cfgsself.dropout = dropout# building first layer 對輸入圖片進(jìn)行第一個(gè)卷積標(biāo)準(zhǔn)化+激活函數(shù)output_channel = _make_divisible(16 * width, 4)#以應(yīng)用到y(tǒng)olov4為例 416,416,3 -> 208,208,16 壓縮高和寬,通道數(shù)擴(kuò)張self.conv_stem = nn.Conv2d(3, output_channel, 3, 2, 1, bias=False) #s=2,output_channel=16self.bn1 = nn.BatchNorm2d(output_channel)self.act1 = nn.ReLU(inplace=True)input_channel = output_channel# building inverted residual blocks 構(gòu)建瓶頸結(jié)構(gòu)stages = []block = GhostBottleneckfor cfg in self.cfgs: #對配置列表進(jìn)行循環(huán)layers = []for k, exp_size, c, se_ratio, s in cfg:output_channel = _make_divisible(c * width, 4)hidden_channel = _make_divisible(exp_size * width, 4)#根據(jù)cfg里面的內(nèi)容構(gòu)建瓶頸結(jié)構(gòu)layers.append(block(input_channel, hidden_channel, output_channel, k, s,se_ratio=se_ratio))#計(jì)算下一個(gè)瓶頸結(jié)構(gòu)的輸入input_channel = output_channelstages.append(nn.Sequential(*layers))#卷積標(biāo)準(zhǔn)化+激活函數(shù)output_channel = _make_divisible(exp_size * width, 4)stages.append(nn.Sequential(ConvBnAct(input_channel, output_channel, 1)))input_channel = output_channel#根據(jù)構(gòu)建好的block序列模型self.blocks = nn.Sequential(*stages) #構(gòu)建分類才能夠# building last several layersoutput_channel = 1280self.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)def forward(self, x):#第一個(gè)卷積標(biāo)注化+激活函數(shù)x = self.conv_stem(x)x = self.bn1(x)x = self.act1(x)#瓶頸結(jié)構(gòu)特征提取x = self.blocks(x)#構(gòu)建分類岑那個(gè)x = self.global_pool(x)x = self.conv_head(x)x = self.act2(x)x = x.view(x.size(0), -1)if self.dropout > 0.:x = F.dropout(x, p=self.dropout, training=self.training)x = self.classifier(x)return xdef ghostnet(**kwargs):"""Constructs a GhostNet model"""cfgs = [# k, t, c, SE, s# k代表卷積核大小,表示跨特征點(diǎn)的特征提取能力# t代表第一個(gè)ghost模塊的通道數(shù)大小,它的值一般比較大一點(diǎn)# c代表瓶頸結(jié)構(gòu)最終的輸出通道數(shù)# SE代表是否使用注意力機(jī)制,如果不為0就是用注意力機(jī)制# s代表步長,如果s=2就會對輸入特征層進(jìn)行高和寬的壓縮# stage1# 208,208,16 -> 208,208,16[[3, 16, 16, 0, 1]],# stage2# 208,208,16 -> 104,104,24[[3, 48, 24, 0, 2]],[[3, 72, 24, 0, 1]],# stage3# 104,104,24 -> 52,52,40[[5, 72, 40, 0.25, 2]],[[5, 120, 40, 0.25, 1]],# stage4# 52,52,40 -> 26,26,80 -> 26,26,112[[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# 26,26,112 -> 13,13,160[[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)5. 將GhostNet應(yīng)用到Y(jié)olov4上
需要利用主干特征提取網(wǎng)絡(luò)獲得的三個(gè)有效特征進(jìn)行加強(qiáng)特征金字塔的構(gòu)建
取出stage3、stage4、stage5的輸出:
class GhostNet(nn.Module):def __init__(self, pretrained=True):super(GhostNet, self).__init__()model = ghostnet()if pretrained:state_dict = torch.load("model_data/ghostnet_weights.pth")model.load_state_dict(state_dict)del model.global_pooldel model.conv_headdel model.act2del model.classifierdel model.blocks[9]self.model = modeldef forward(self, x):x = self.model.conv_stem(x)x = self.model.bn1(x)x = self.model.act1(x)feature_maps = []for idx, block in enumerate(self.model.blocks):x = block(x)if idx in [2,4,6,8]:feature_maps.append(x)return feature_maps[1:]參考文獻(xiàn):
原作者解讀
Ghostnet網(wǎng)絡(luò)介紹與構(gòu)建
總結(jié)
以上是生活随笔為你收集整理的GhostNet网络详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA图片与字节流的相互转换
- 下一篇: 腾讯云容器团队内部Istio专题分享