1. GhostNet网络

一张图片经过神经网络进行特征提取后,能够得到很多特征图。

在特征图中会有一些相似性很高,这就是神经网络中存在的特征图冗杂的情况(如图中扳手相连的两幅特征图)。

作者认为可以对其中的一个特征图进行(Cheap Operations)简单的线性运算从而生成更多相似特征图,从而可以使用更少的参数生成更多的特征图,将相似的特征图认为是彼此的Ghost。

2. Ghost Module


作者用Ghost Module代替传统卷积,首先采用普通的1x1卷积对输入图片进行通道数的压缩,然后再进行深度可分离卷积(逐层卷积)得到更多的特征图,然后将不同的特征图concat到一起,组合成新的output。

Ghost Module构建代码

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,保证输出特征层的通道数等于expself.oup = oupinit_channels = math.ceil(oup / ratio)new_channels = init_channels*(ratio-1)#利用1x1卷积对输入进来的特征图进行通道的浓缩,获得特征通缩#跨通道的特征提取self.primary_conv = nn.Sequential(nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),  #1x1卷积的输入通道数为GhostModule的输出通道数oup/2nn.BatchNorm2d(init_channels),                       #1x1卷积后进行标准化nn.ReLU(inplace=True) if relu else nn.Sequential(),  #ReLU激活函数)#在获得特征浓缩后,使用逐层卷积,获得额外的特征图#跨特征点的特征提取    一般会设定大于1的卷积核大小self.cheap_operation = nn.Sequential(nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),  #groups参数的功能就是将普通卷积转换成逐层卷据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卷积后的结果和逐层卷积后的结果进行堆叠out = torch.cat([x1,x2], dim=1)return out[:,:self.oup,:,:]

1x1卷积的输入通道数为GhostModule的输出通道数oup/2,因为最终输出特征层由1x1卷积后的结果和逐层卷积后的结果进行堆叠组成的,进行逐层卷积时特征层的通道数不变,如果要保证最终输出特征层的通道数是一个固定值,那么就要使得1x1卷积后的结果的通道数为最终输出特征层的1/2。

3. Ghost BottleNeck原理

Ghost BottleNeck是由Ghost Module组成的瓶颈结构

Ghost BottleNeck整体架构和Residual Block非常相似,也可以直接认为是将Residual Block中的普通卷积操作替换成Ghost Module得到。

左图中主干部分用用两个Ghost Module(GM)串联组成,其中第一个GM扩大通道数,第二个GM将通道数降低到与输入通道数一致;残差边部分与ResNet一样。由于S=1,因此不会对输入特征层的高和宽进行压缩,其功能为加深网络的深度。

右图中主干部分的两个GM之间加入了一个stride=2的Deepwise卷积,可以将特征图高和宽进行压缩,使其大小降为输入的1/2;在残差边部分,也会添加一个步长为2x2的深度可分离卷积和1x1的普通卷积,以保证Add操作可以对齐。由于S=2,因此会对输入特征层的高和宽进行压缩,其功能为改变输入特征层的形状。

实际应用中,为了进一步提高效率,GhostModule中的所有常规卷积都用pointwise卷积代替。

Ghost BottleNeck构建代码

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#首先利用一个ghost模块进行特征提取#此时指定的通道数会比较大,可以看作是逆残差结构,进行通道数上升self.ghost1 = GhostModule(in_chs, mid_chs, relu=True)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)if has_se:    #判断是否使用注意力机制模块self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)else:self.se = None#再次利用一个ghost模块进行特征提取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卷积调整通道数,保证主干部分和残差边部分能够进行相加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 x

4. GhostNet的构建


整个Ghostnet都是由Ghost Bottlenecks进行组成的。

当一张图片输入到Ghostnet当中时,首先进行一个16通道的普通1x1卷积块(卷积+标准化+激活函数)

之后就开始Ghost Bottlenecks的堆叠了,利用Ghost Bottlenecks,最终获得了一个7x7x160的特征层(当输入是224x224x3的时候)

然后利用一个1x1的卷积块进行通道数的调整,此时可以获得一个7x7x960的特征层

之后进行一次全局平均池化,然后再利用一个1x1的卷积块进行通道数的调整,获得一个1x1x1280的特征层

最后平铺后进行全连接就可以进行分类了。

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   对输入图片进行第一个卷积标准化+激活函数output_channel = _make_divisible(16 * width, 4)#以应用到yolov4为例   416,416,3 ->  208,208,16   压缩高和宽,通道数扩张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   构建瓶颈结构stages = []block = GhostBottleneckfor cfg in self.cfgs:    #对配置列表进行循环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)#根据cfg里面的内容构建瓶颈结构layers.append(block(input_channel, hidden_channel, output_channel, k, s,se_ratio=se_ratio))#计算下一个瓶颈结构的输入input_channel = output_channelstages.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#根据构建好的block序列模型self.blocks = nn.Sequential(*stages)        #构建分类才能够# 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):#第一个卷积标注化+激活函数x = self.conv_stem(x)x = self.bn1(x)x = self.act1(x)#瓶颈结构特征提取x = self.blocks(x)#构建分类岑那个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代表卷积核大小,表示跨特征点的特征提取能力# t代表第一个ghost模块的通道数大小,它的值一般比较大一点# c代表瓶颈结构最终的输出通道数# SE代表是否使用注意力机制,如果不为0就是用注意力机制# s代表步长,如果s=2就会对输入特征层进行高和宽的压缩# 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应用到Yolov4上

需要利用主干特征提取网络获得的三个有效特征进行加强特征金字塔的构建

取出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:]

参考文献:
原作者解读
Ghostnet网络介绍与构建

GhostNet网络详解相关推荐

  1. ResNet网络详解与keras实现

    ResNet网络详解与keras实现 ResNet网络详解与keras实现 Resnet网络的概览 Pascal_VOC数据集 第一层目录 第二层目录 第三层目录 梯度退化 Residual Lear ...

  2. GoogleNet网络详解与keras实现

    GoogleNet网络详解与keras实现 GoogleNet网络详解与keras实现 GoogleNet系列网络的概览 Pascal_VOC数据集 第一层目录 第二层目录 第三层目录 Incepti ...

  3. Linux系统下ifconfig和route配置网络详解

    Linux系统下ifconfig和route配置网络详解 ifconfig和route合用于配置网络(ip命令综合二者功能,此处不讲),通常在前者设置好ip地址等信息后,采用route命令配置路由.( ...

  4. EfficientNetV2网络详解

    原论文名称:EfficientNetV2: Smaller Models and Faster Training 论文下载地址:https://arxiv.org/abs/2104.00298 原论文 ...

  5. 深度学习之图像分类(二十五)-- S2MLPv2 网络详解

    深度学习之图像分类(二十五)S2MLPv2 网络详解 目录 深度学习之图像分类(二十五)S2MLPv2 网络详解 1. 前言 2. S2MLPv2 2.1 S2MLPv2 Block 2.2 Spat ...

  6. ResNet、ResNeXt网络详解及复现

    网络详解: ResNet网络详解 ResNeXt网络详解 torch复现: import torch.nn as nn import torch''' 对应着18层和34层的残差结构 既要拥有实现部分 ...

  7. MGN网络详解以及代码分析

    MGN网络详解以及代码分析 最近阅读了云从科技最新的关于REID的论文以及相关的博客和代码,算法是基于MGN,关于网络的部分,这里记录一些自己的学习笔记. 以下是我参考的博客和代码的网址 博客: ht ...

  8. YOLOv4网络详解

    0前言 在YOLOv4论文中,作者其实就是把当年所有的常用技术罗列了一遍,然后做了一堆消融实验. 1.YOLOV4的网络改进部分 1.主干特征提取网络:DarkNet53 => CSPDarkN ...

  9. HighwayNet网络详解及复现

    HighwayNet网络详解及复现: https://mp.weixin.qq.com/s?__biz=Mzk0MzIzODM5MA==&mid=2247485190&idx=1&am ...

最新文章

  1. Gartner发布2021年重要战略科技趋势!
  2. python【数据结构与算法】关于树和二叉树的探索与计算
  3. B2C Opinions
  4. mqtt 域名连接_中国移动OneNet物联网平台,如何使用MQTT协议,进行连接
  5. VirtualBox 虚拟机复制
  6. 软件加入使用时间_Mac实用菜单栏管理小工具 Bartender 3 | Mac软件天堂
  7. linux下配置socks 5代理
  8. STM32命名规则 STM32选型手册
  9. Java -- Ajax异步访问数据库内容
  10. 69 MyBatis和Spring整合
  11. IPv4头部结构具体解释
  12. JVM规范(四)Frames
  13. vue使用JavaScript的Number方法或正则表达式进行表单验证,判断值是否为数字(包括整数和小数),验证值只能为小数点后一位
  14. 倍福---CTU和TON的使用
  15. 《WEB安全渗透测试》(23):记一次利用SSRF漏洞到提权
  16. uniform对象及其使用
  17. 硬件工程师成长之路(5)——板子调试
  18. 5.1Kettle数据的清洗与检验——不完全去重
  19. 接口技术实验:七段码显示
  20. 八年级计算机知识点总结,人教版|八年级上册各单元必考知识点汇总,收藏!...

热门文章

  1. Java开发之路—Java反射机制
  2. oracle ocx加载错误,怎么对.ocx格式的文件进行注册加载?出现0x8002801c错误怎么办?...
  3. 类似于萝卜书摘的书摘app推荐
  4. Oracle简介与安装
  5. 利用opencv从USB摄像头获取图片 获得摄像头编号
  6. android获取apk版本号,android 获取apk的版本信息
  7. Linux ssh 端口修改
  8. 微信支付证书如何部署在linux,微信支付平台证书更新指引
  9. 【Unity2D】关卡编辑好帮手——TileMap
  10. Android 开发之上传图片