结合源码分析Squeeze-and-Excitation Networks

论文地址:https://arxiv.org/abs/1709.01507
pytorch实现:https://github.com/miraclewkf/SENet-PyTorch

主题思想

论文中心的思想是下列该图,其主要目的是有选择地注重信息有用的特征通道并抑制无用的特征通道,方法是给下图U中C个HxW的特征通道分配不同的权重,当然权重也是通过网络学习获得。这个思想有点类似生活中我们投票,你地位高那么你1票可以抵上普通人的5票,如果你地位过低只能算0.5票。论文中也提到这有关于注意力和门机制,由于本人不熟悉该NLP方面的知识,大家有兴趣可以参考:https://www.jianshu.com/p/e14c6a722381

SE block

代码实现

论文作者表示可以灵活地将SE block整合到现有的网络模型。以ResNet-18作为基础网络,在ResNet的BasicBlock基础上添加Squeeze和Excitation操作得到的上图SE block。其中squeeze操作采用平均池化,对应上图的Fsq方法,excitation操作包括两个全连接层和激活函数,对应上图Fex。上图中得到1x1xC的tensor不同颜色表示C个不同的权重,表示特征选择后的每个特征通道的重要性,然后与先前特征U上的逐个通道相乘,完成在通道维度上的对原始特征的重标定。


squeeze_and_excitation公式
Basic block和SE block

代码实现为

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

        if planes == 64:
            self.globalAvgPool = nn.AvgPool2d(56, stride=1)
        elif planes == 128:
            self.globalAvgPool = nn.AvgPool2d(28, stride=1)
        elif planes == 256:
            self.globalAvgPool = nn.AvgPool2d(14, stride=1)
        elif planes == 512:
            self.globalAvgPool = nn.AvgPool2d(7, stride=1)

        self.fc1 = nn.Linear(in_features=planes, out_features=round(planes / 16))
        self.fc2 = nn.Linear(in_features=round(planes / 16), out_features=planes)
        self.sigmoid = nn.Sigmoid()

    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)

        if self.downsample is not None:
            residual = self.downsample(x)

        original_out = out
        out = self.globalAvgPool(out)
        out = out.view(out.size(0), -1)
        out = self.fc1(out)
        out = self.relu(out)
        out = self.fc2(out)
        out = self.sigmoid(out)
        out = out.view(out.size(0), out.size(1), 1, 1)
        out = out * original_out

        out += residual
        out = self.relu(out)

        return out

以一张224x224的彩色图片为例,其一个SE block维度变化的流程图为下图红框区域。


SE block流程图

额外的参数量

相对于原来的模型,现在网络中新增的参数量主要是两个全连接层。第一个FC首先将特征维度降低到输入的 1/16,然后经过 ReLu 激活后再通过第二个FC层升回到原来的维度。这样做比直接用一个全连接层的好处在于:1)具有更多的非线性,可以更好地拟合通道间复杂的相关性;2)极大地减少了参数量和计算量。其中1/16也是一个超参数r,下图为不同r下的错误率和参数量。


不同r对应的错误率和参数量

全连接层其参数量为输入通道数乘以输出通道数,上述SE block有两个全连接层,第一个降维的FC维度为Cx(C/r),第二个升维的FC维度为(C/r)xC,故一个SE block新增的参数量为2xCx(C/r)。若网络中有S个stage,每个stage有Ns个SE block,那么总共新增的参数量为下图公式:


新增的参数量

从公式5可以看出,增加的参数量和通道C关系很大,而网络越到高层,其feature map的通道数越多,也就是C越大,因此大部分增加的参数都是在高层。同时作者通过实验发现即便去掉最后一个stage的SE block,对模型的影响也非常小(<0.1% top-1 error),因此如果你对参数量的限制要求很高,倒是可以这么做,毕竟具体在哪些stage,哪些block中添加SE block都是自由定义的。
下图两张图可以看出使用SE block在计算成本上的增加可以忽略不计,但是错误率却有显著的下降,尤其是ResNet-101在添加SE block后比原先更深的ResNet-152错误还要低,添加SE block显然是一个明智的选择。
增加的运算时间成本

不同模型添加SE block后的错误率和运算成本

参考博客

https://www.cnblogs.com/bonelee/p/9030092.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容