原文:https://zhuanlan.zhihu.com/p/65459972
一、前言
在深度学习领域,CNN分类网络的发展对其它计算机视觉任务如目标检测和语义分割都起到至关重要的作用,因为检测和分割模型通常是构建在CNN分类网络(称为backbone)之上。提到CNN分类网络,我们所熟知的是VGG,ResNet,Inception,DenseNet等模型,它们的效果已经被充分验证,而且被广泛应用在各类计算机视觉任务上。这里我们介绍一篇CVPR2017的文章SENet,它赢得了最后一届ImageNet 2017竞赛分类任务的冠军。重要的一点是SENet思路很简单,很容易扩展在已有网络结构中。
二、Squeeze-and-Excitation (SE) 模块解读
SENet的全称是Squeeze-and-Excitation Networks,中文可以翻译为压缩和激励网络。主要由两部分组成:
1. Squeeze部分。即为压缩部分,原始feature map的维度为H*W*C,其中H是高度(Height),W是宽度(width),C是通道数(channel)。Squeeze做的事情是把H*W*C压缩为1*1*C,相当于把H*W压缩成一维了,实际中一般是用global average pooling实现的。H*W压缩成一维后,相当于这一维参数获得了之前H*W全局的视野,感受区域更广。
2. Excitation部分。得到Squeeze的1*1*C的表示后,加入一个FC全连接层(Fully Connected),对每个通道的重要性进行预测,得到不同channel的重要性大小后再作用(激励)到之前的feature map的对应channel上,再进行后续操作。
三、SE模块的应用
SE模块的灵活性在于它可以直接应用现有的网络结构中。这里以Inception和ResNet为例。对于Inception网络,没有残差结构,这里对整个Inception模块应用SE模块。对于ResNet,SE模块嵌入到残差结构中的残差学习分支中。
具体如下图所示:
同样地,SE模块也可以应用在其它网络结构,如ResNetXt,Inception-ResNet,MobileNet和ShuffleNet中。
这里给出SE-ResNet-50和SE-ResNetXt-50的具体结构,如下表所示:
模型效果
SE模块很容易嵌入到其它网络中,作者为了验证SE模块的作用,在其它流行网络如ResNet和VGG中引入SE模块,测试其在ImageNet上的效果,如下表所示:
四、SE模块的实现
SE模块是非常简单的,实现起来也比较容易,这里给出PyTorch版本的实现(参考senet.pytorch):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class SELayer(nn.Module): def __init__(self, channel, reduction=16): super(SELayer, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(channel, channel // reduction, bias=False), nn.ReLU(inplace=True), nn.Linear(channel // reduction, channel, bias=False), nn.Sigmoid() ) def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x).view(b, c) y = self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x) |
对于SE-ResNet模型,只需要将SE模块加入到残差单元(应用在残差学习那一部分)就可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class SEBottleneck(nn.Module): expansion = 4 def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16): super(SEBottleneck, self).__init__() self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, 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 = SELayer(planes * 4, reduction) self.downsample = downsample self.stride = stride 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) out = self.se(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out |
五、总结
SE模块主要为了提升模型对channel特征的敏感性,这个模块是轻量级的,而且可以应用在现有的网络结构中,只需要增加较少的计算量就可以带来性能的提升。