SENet详解及Keras复现代码

原作者开源代码:https://arxiv.org/pdf/1709.01507.pdf

代码:https://github.com/hujie-frank/SENet

1、通道间的特征关系

近些年来,卷积神经网络在很多领域上都取得了巨大的突破。而卷积核作为卷积神经网络的核心,通常被看做是在局部感受野上,将空间上(spatial)的信息和特征维度上(channel-wise)的信息进行聚合的信息聚合体。卷积神经网络由一系列卷积层、非线性层和下采样层构成,这样它们能够从全局感受野上去捕获图像的特征来进行图像的描述。

我们可以看到,已经有很多工作在空间维度上来提升网络的性能。那么很自然想到,网络是否可以从其他层面来考虑去提升性能,比如考虑特征通道之间的关系?论文的作者就是基于这一点并且提出了Squeeze-and-Excitation Networks(简称 SENet)。作者并不希望引入一个新的维度来进行特征通道间的融合,而是采用一种全新的特征重标定策略。简单来说,就是通过增加一条分支,自动获取到每个通道的重要程度,然后依照这个重要程度去提升有用的信息,同时抑制对当前任务用处不大的特征。
se block

上图是SE模块的示意图。给定一个输入X,其通道数维C’,经过一系列卷积等变换后得到一个通道数维C的特征。接下来的结构有点类似ResNet,但又与ResNet有很大不同。

首先是Squeeze操作,我们顺着空间维度来进行特征压缩,将每个二维特征通道变换成一个实数,这个实数某种程度上具有全局感受野,并且输出和输入的通道数是一样的。它表征着在特征通道上响应的全局分布,而且使得靠近输入的层也可以获得全局的感受野,这一点在很多任务中都是非常有用的。

其次是Excitation操作,他是类似于RNN中门的机制,通过参数W来为每个特征通道生成权重,其中参数W将被用来控制U中每个通道的重要性。

最后一个是Reweight的操作,我们将Excitation的输出权重看做是特征选择后每个特征通道的重要性,然后通过乘法逐通道加权到原来的特征U上,完成在通道维度上的特征重标定。

2、具体的网络结构

由于SE模块并不像GoogLeNet和ResNet一样,提出了全新的网络结构,所以它是可以很灵活的嵌入到已有主流网络中去。

SE network

上图左边是将SE模块嵌入到Inception结构的一个示例。

这里的Global pooling对应着Squeeze操作,它将输入特征层的维度压缩至1 x 1 x C。紧接着两个全连接层组成一个Bottleneck结构去建模通道间的相关性,并且最终输出的维度信息保持不变,为1 x 1 x C。

我们可以看到第一个全连接层使用了ReLU作为激活函数,第二个层采用了Sigmoid的作为激活函数。而我们知道Sigmoid会变量映射到0,1之间,也就是说有用的特征通过SE模块让他更偏向于1了,同时无用的特征也更接近0了,那么通过最后Scale操作,将输出权重与原始特征的每个通道逐乘也就增益了原始通道有用的特征,抑制了无用特征。

为什么采用两个全连接层而不是一个的原因在于:

  1. 通过ReLU可以获得更多的非线性
  2. 引入r参数可以减少参数量和计算量

除此之外,SE模块也可以嵌入到含有跨层连接的网络中去,上图右边就是将SE嵌入到模块中的一个例子,原理、操作基本和SE-Inception一样,只不过是在最后的Addition前对分支上Residual的特征进行重标定。

目前大多数的主流网络都是基于这两种类似的单元通过 repeat 方式叠加来构造的。由此可见,SE 模块可以嵌入到现在几乎所有的网络结构中。通过在原始网络结构的 building block 单元中嵌入 SE 模块,我们可以获得不同种类的 SENet。如 SE-BN-Inception、SE-ResNet、SE-ReNeXt、SE-Inception-ResNet-v2 等等。

3、实验结果

论文中给出了ResNet、ResNeXt等在当时比较常见的网络对比结果。深度学习经过几年的发展,在数据增强上也有了新的突破,为了公平起见,作者将网络又重新实现了一遍并且采用了同样的数据增强方式。结果如下图所示:

SE result

结合试验结果和上面的介绍来看,我们可以发现SENet的构造非常简单,不需要引入新的函数或层。对比原来的网络,仅仅只需要增加2%-10%的参数,就能将误差降低0.4-1.1左右。

4、更多的尝试

参数r的调节

SE ratio

我们在第一个全连接中引入了参数r,使得第一个全连接层的通道数减少,整体呈现一个瓶颈状的结构。作者的试验结果发现r=8时会有一个比较好的效果。

Pooling的方式

SE pooling
对于空间维度的压缩方式,作者尝试了Global Max Pooling和Global Average Pooling两种方式,无论是top-1还是top-5,结果都表明,AvgPool效果会更好。

激活函数的选择

SE activation
接下来是最后一个全连接层激活函数的选择,用tanh替换sigmoid会略微恶化性能,而使用ReLU会显著恶化,实际上会导致SE-ResNet-50的性能低于ResNet-50基线。这表明,为了使SE块有效,激活函数的选择是很重要的。

SE Block添加的位置

SE position
作者还对SE模块添加的位置做了对比,在top-5上在越靠后的Stage上填加SE模块比在越靠前的Stage上添加效果要好,当然如果在所有Stage都添加效果是最好的。

SE的四种结构

SE structure
最后是对SE模块的结构进行了对比:

​ (a) 普通的残差
​ (b) 标准的SE模块
​ © 先进行SE再进行残差
​ (d) 先完成残差计算再进行SE模块计算
​ (e) 在跨层连接上完成SE模块计算

SE struct res
从结果上看,是SE-PRE的结构略胜一筹,但个人觉得只是在ResNet-50上对比了一下还不具有说服力。但是无论是什么结构都会提高ResNet的准确率,说明SE模块是有起作用的。

5、总结

SENet作为ImageNet竞赛的最后一届图像识别冠军,作者是付出了很多的时间,在网络架构上做了大量的尝试和实验,论文中还有些细节没有在本文中展开解释,读者可以下载论文详细阅读。作者开源的代码是基于caffe的,笔者自己也尝试在keras和tf2上进行复现,比较直观的效果是分类的置信度比原本ResNet要高很多。可能是因为在SE-ResNet中进行了多次sigmoid函数激活。

SENet是一种用于图像分类的深度学习模型,它在卷积神经网络中引入了一种新的机制,即“Squeeze-and-Excitation”(简称SE)模块,可以自适应地调整特征图的通道权重,从而增强模型的表征能力。下面是SENet代码实现(以PyTorch为例): ```python import torch import torch.nn as nn 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, bias=False) self.relu = nn.ReLU(inplace=True) self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x).view(b, c, 1, 1) y = self.fc1(y) y = self.relu(y) y = self.fc2(y) y = self.sigmoid(y) return x * y class SEBasicBlock(nn.Module): def __init__(self, inplanes, planes, reduction=16, stride=1, downsample=None): super(SEBasicBlock, self).__init__() self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.relu = nn.ReLU(inplace=True) self.se_module = SEModule(planes, 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.se_module(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out class SENet(nn.Module): def __init__(self, block, layers, num_classes=1000, reduction=16): super(SENet, self).__init__() self.inplanes = 64 self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, layers[0], reduction) self.layer2 = self._make_layer(block, 128, layers[1], reduction, stride=2) self.layer3 = self._make_layer(block, 256, layers[2], reduction, stride=2) self.layer4 = self._make_layer(block, 512, layers[3], reduction, stride=2) self.avgpool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Linear(512 * block.expansion, num_classes) def _make_layer(self, block, planes, blocks, reduction, stride=1): downsample = None if stride != 1 or self.inplanes != planes * block.expansion: downsample = nn.Sequential( nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(planes * block.expansion), ) layers = [] layers.append(block(self.inplanes, planes, reduction, stride, downsample)) self.inplanes = planes * block.expansion for i in range(1, blocks): layers.append(block(self.inplanes, planes, reduction)) return nn.Sequential(*layers) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x ``` 以上代码实现了SENet的SE模块和SEBasicBlock模块,以及整个SENet模型。其中,SEModule模块是SENet中的核心部分,用于自适应地调整特征图的通道权重;SEBasicBlock模块则是SENet的基本组成单元,由卷积层、BN层、ReLU层、SE模块和残差连接组成;SENet模型则是由多个SEBasicBlock模块组成的深度卷积神经网络
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值