论文阅读笔记(通道注意力)

摘要

本周阅读了 Squeeze-and-Excitation Networks 这篇文献,该文献提出了SENet架构,SENet(Squeeze-and-Excitation Networks)是一种深度学习架构,它通过引入注意力机制来增强卷积神经网络(CNN)的特征学习能力。其核心是SE模块,它通过显式地建模特征通道之间的相互依赖关系,使网络能够自适应地调整每个通道的响应。SE模块通过学习每个特征通道的重要性权重,然后根据这些权重对特征进行重标定,从而提升有用的特征并抑制无关的特征。本文将详细介绍SENet架构。

Abstract

This week read the paper Squeeze-and-Excitation Networks, which proposes the SENet architecture, a deep learning architecture that enhances the feature learning of Convolutional Neural Networks (CNNs) by introducing an attention mechanism to enhance their feature learning capability. At its core is the SE module, which enables the network to adaptively adjust the response of each channel by explicitly modeling the interdependencies between feature channels.The SE module enhances useful features and suppresses irrelevant features by learning the importance weights of each feature channel and then recalibrating the features according to these weights. In this paper, we describe the SENet architecture in detail.

论文出处:Squeeze-and-Excitation Networks

1. SENet

1.1 研究背景

卷积神经网络(CNN)已被证明是处理各种视觉任务的有效模型。 对于每个卷积层,学习一组滤波器来表达沿输入通道的局部空间连接模式。卷积滤波器有望通过在局部感受野内将空间和通道信息融合在一起而成为信息组合。 通过堆叠一系列与非线性和下采样交错的卷积层,CNN 能够捕获具有全局感受野的分层模式作为强大的图像描述。

在本文中,作者通过引入一个新的架构单元(我们将其称为“挤压和激励”(SE)块)来研究架构设计的另一个方面 - 通道关系。作者提出了一种允许网络执行特征重新校准的机制,通过该机制它可以学习使用全局信息来选择性地强调信息丰富的特征并抑制不太有用的特征。通过简单地堆叠一组 SE 构建块就可以生成 SE 网络。 SE 块还可以用作架构中任何深度的原始块的直接替代品。

最后一届ImageNet 2017竞赛 Image Classification任务的冠军.作者采用SENet block和ResNeXt结合在ILSVRC 2017的分类项目中拿到第一,在ImageNet数据集上将top-5 error降低到2.251%,原先的最好成绩是2.991%。

1.2 创新点

SENet的核心思想是通过网络根据损失函数学习特征权重,使得有效的特征图(feature map)权重更大,而对当前任务效果较小或无效的特征图权重减小。以下便是这篇文献的创新点:

  1. SENet引入了SE模块,该模块嵌入到现有的分类网络中,通过显式地建模特征通道之间的相互依赖关系来提升网络性能。
  2. SE模块中的Squeeze操作通过全局平均池化(Global Average Pooling)将每个特征通道压缩成一个实数,这个实数具有全局感受野,使得网络能够捕捉全局上下文信息。
  3. SENet在多个视觉任务和数据集上展示了其有效性,例如在ImageNet和COCO数据集上取得优异的性能,同时在ILSVRC 2017竞赛中获得分类任务第一名。

1.3 SE块的构建过程

在这里插入图片描述
对于任何给定的变换(例如卷积或一组卷积)
在这里插入图片描述
我们可以构造一个相应的SE块来执行特征重新校准(对变换完了的新特征进行重新校准)。如图所示。执行步骤如下:

  1. 特征U首先通过squeeze操作,该操作跨越空间维度W×H聚合特征映射来产生通道描述符(也就是聚合一张Fm)。这个描述符嵌入了通道特征响应的全局分布(对一张Fm的描述),使来自网络全局感受野的信息能够被其较低层利用(指这个模块的较底层部分,就是一提取完就给最近的层用)。
  2. 这之后是一个excitation(激励)操作,其中通过基于通道依赖性的自门机制为每个通道学习特定采样的激活,控制每个通道的激励。
  3. 然后特征映射U被重新加权以生成SE块的输出,然后可以将其直接输入到随后的层中。

SE网络可以通过简单地堆叠SE构建块的集合来生成。SE块也可以用作架构中任意深度的原始块的直接替换。然而,虽然构建块的模板是通用的,正如我们6.3节中展示的那样,但它在不同深度的作用适应于网络的需求。

SE块在网络不同部分的作用有所不同:

  1. 在前面的层中,它学习以类不可知的方式激发信息特征,增强共享的较低层表示的质量。
  2. 在后面的层中,SE块越来越专业化,并以高度类特定的方式响应不同的输入。

因此,SE块进行特征重新校准的好处可以通过整个网络进行累积。

1.3.1 注意力和门机制

从广义上讲,可以将注意力视为一种工具,将可用处理资源的分配偏向于输入信号的信息最丰富的组成部分。这种机制的发展和理解一直是神经科学社区的一个长期研究领域,并且近年来作为一个强大补充,已经引起了深度神经网络的极大兴趣。注意力已经被证明可以改善一系列任务的性能,从图像的定位和理解到基于序列的模型。它通常结合门函数(例如softmax或sigmoid)和序列技术来实现。(Highway Networks)高速网络[36]采用门机制来调节快捷连接,使得可以学习非常深的架构。王等人[42]受到语义分割成功的启发,引入了一个使用沙漏模块[27]的强大的trunk-and-mask注意力机制。这个高容量的单元被插入到中间阶段之间的深度残差网络中。

相比之下,作者提出的SE块是一个轻量级的门机制,专门用于以计算有效的方式对通道关系进行建模,并设计用于增强整个网络中模块的表示能力。

1.3.2 SE块具体运行过程

Squeeze-and-Excitation块是一个计算单元,可以为任何给定的变换构建(就是给某个变换结束了,对它产生的结果进行一个处理)。

为了简化说明,在接下来的表示中,我们将Ftr看作一个标准的卷积算子。V=[v1,v2,…,vC]表示学习到的一组滤波器核,vc指的是第c个滤波器的参数。然后我们可以将Ftr的输出写作U=[u1,u2,…,uC],其中
在这里插入图片描述
这里∗表示卷积,vc=[v1c,v2c,…,vC′c],X=[x1,x2,…,xC′](为了简洁表示,忽略偏置项)。这里vsc是2D空间核,因此表示vc的一个单通道,作用于对应的通道X。Uc表示输出的第C个通道的Fm,Vc代表在输入的第C个通道上使用的卷积核,Vsc代表该卷积核对应输入的某个通道的2D空间核。

从上式不难知道输出的某个Fm是通过所有通道的和来产生的,所以通道依赖性(这里指的通道依赖性指的是上一批通道,也就是输入信号的那批通道,后面我们处理的是后面一批通道)被隐式地嵌入到vc中,而vc是滤波器(卷积核),它的作用是捕获图片像素点的空间相关性,所以我们说通道间的依赖性与滤波器捕获的空间相关性是纠缠在一起的。

1.3.3 通道间依赖关系的提取

输出特征中每个通道的信号:要找通道间信号的关系,先把他们输出。

问题:

每个通道在这里是指刚刚输出的每张Fm,所以,输出每个通道信息就是说输出每张Fm,而Fm上有很多像素点,这些像素点都是由上一步的局部感受野处理得到的,没有哪个像素点可以代表整个通道(Fm):

每个学习到的滤波器都对局部感受野进行操作,因此变换输出U的每个单元(每张Fm)都无法利用该区域之外的上下文信息。在网络较低的层次上其感受野尺寸很小,这个问题变得更严重。

解决方法:

使用全局平均池化将整个通道压缩成一个通道描述符。第c个通道(第c个Fm)表示如下:
在这里插入图片描述
上一步的转换输出U可以被解释为局部描述子的集合,这些描述子的统计信息对于整个图像来说是有表现力的。特征工程工作中普遍使用这些信息。作者选择最简单的全局平均池化,同时也可以采用更复杂的汇聚策略。

1.3.4 自适应重新校正(Excitation)

通过一定方式提取完了通道信息,我们就要开始利用这些通道信息了,利用他们来全面捕获通道依赖性。

能捕获通道间依赖关系的机制必须要满足下面两个条件:

  1. 它必须是灵活的(特别是它必须能够学习通道之间的非线性交互)
  2. 它必须学习一个非互斥的关系(non-mutually-exclusive relationship ),因为允许多个通道被强调, 相对于独热激活。(这里感觉第一条灵活的非线性交互互其实包含了第二条,第二条只是为了强调下非互斥关系)

解决方案:

为了满足这些标准,我们选择采用一个简单的门机制,并使用sigmoid激活(这里得到的标量S就相当于一会要加在输出信息U的某个Fm上的权重了):
在这里插入图片描述
δ是指Relu激活函數,W1和W2是用于降维和升维的两个全连接层的权重,目的是为了(1)限制模型复杂度(2)辅助模型泛化。

通过在非线性周围形成两个全连接(FC)层的瓶颈来参数化门机制,即降维层参数为W1,降维比例为r。一个ReLU,然后是一个参数为W2的升维层。

块的最终输出通过重新调节变换输出U得到:

在这里插入图片描述
其中X˜=[x˜1,x˜2,…,x˜C]和Fscale(uc,sc)指的是特征映射uc∈RW×H和标量sc之间的对应通道乘积。激活作为适应特定输入描述符z的通道权重。在这方面,SE块本质上引入了以输入为条件的动态特性,有助于提高特征辨别力。

1.4 SE结合先进架构的灵活应用

SE块的灵活性意味着它可以直接应用于标准卷积之外的变换。为了说明这一点,我们通过将SE块集成到两个流行的网络架构系列Inception和ResNet中来开发SENets。前面之所以用一个看似加一个无关的Ftr其实是现在用将它们替换为Inception和Residual模块了。

1. SE-Inception模块:

通过对架构中的每个模块进行更改,我们构建了一个SE-Inception网络。如下图:
在这里插入图片描述
2. SE-ResNet模块:

在这里,SE块变换Ftr被认为是残差模块的非恒等分支。压缩和激励都在恒等分支相加之前起作用。如下图:

在这里插入图片描述

1.5 实验

SENet通过堆叠一组SE块来构建。实际上,它是通过用原始块的SE对应部分(即SE残差块)替换每个原始块(即残差块)而产生的。我们在表1中描述了SE-ResNet-50和SE-ResNeXt-50的架构。fc后面的内括号表示SE模块中两个全连接层的输出维度。

在这里插入图片描述
在实践中提出的SE块是可行的,它必须提供可接受的模型复杂度和计算开销。每个SE块利用压缩阶段的全局平均池化操作和激励阶段中的两个小的全连接层,接下来是几乎算不上计算量的通道缩放操作。

为了说明模块的成本,作为例子我们比较了ResNet-50和SE-ResNet-50,其中SE-ResNet-50的精确度明显优于ResNet-50,接近更深的ResNet-101网络(如表2所示)。对于224×224像素的输入图像,ResNet-50单次前向传播需要∼ 3.86 GFLOP。总的来说,SE-ResNet-50需要∼ 3.87 GFLOP,相对于原始的ResNet-50只相对增加了0.26%。

下图是ImageNet验证集上的单裁剪图像错误率(%)和复杂度比较。original列是指原始论文中报告的结果。为了进行公平比较,我们重新训练了基准模型,并在re-implementation列中报告分数。SENet列是指已添加SE块后对应的架构。括号内的数字表示与重新实现的基准数据相比的性能改善。†表示该模型已经在验证集的非黑名单子集上进行了评估(在[38]中有更详细的讨论),这可能稍微改善结果。在实践中,训练的批数据大小为256张图像,ResNet-50的一次前向传播和反向传播花费190 ms,而SE-ResNet-50则花费209 ms(两个时间都在具有8个NVIDIA Titan X GPU的服务器上执行)。我们认为这是一个合理的开销,因为在现有的GPU库中,全局池化和小型内积操作(最后一步乘权重)的优化程度较低。此外,由于其对嵌入式设备应用的重要性,我们还对每个模型的CPU推断时间进行了基准测试:对于224×224像素的输入图像,ResNet-50花费了164ms,相比之下,SE-ResNet-50花费了167ms。SE块所需的小的额外计算开销对于其对模型性能的贡献来说是合理的。

在这里插入图片描述

对SE引入的附加参数的讨论:

SE的所有附加参数其实都在其中的两个全连接层了,他们构成了网络总容量的一小部分。增加的参数的计算方法如下:
在这里插入图片描述
单个SE块增加的参数为:c*c/r + (c/r)*c=(2c^2)/r . 其中r表示减少比率(我们在所有的实验中将r设置为16),S指的是阶段数量(每个阶段是指在共同的空间维度的特征映射上运行的块的集合),Cs表示阶段s的输出通道的维度,Ns表示重复的块编号。(一个网络S个阶段,每个阶段重复N个SE块)

1.6 模型的实现

训练过程

  1. 使用随机大小裁剪到224×224像素(299×299用于Inception-ResNet-v2[38]和SE-Inception-ResNet-v2)
  2. 随机的水平翻转
  3. 输入图像通过通道减去均值进行归一化
  4. 采用数据均衡策略进行小批量采样,以补偿类别的不均匀分布
  5. 网络在我们的分布式学习系统“ROCS”上进行训练
  6. 使用同步SGD进行优化,动量为0.9,小批量数据的大小为1024(在4个服务器的每个GPU上分成32张图像的子批次,每个服务器包含8个GPU)
  7. 初始学习率设为0.6
  8. 每30个迭代周期减少10倍
  9. 所有模型都从零开始训练100个迭代周期

imagenet测试
1.验证集上的处理过程:

在验证集上使用中心裁剪图像评估来报告top-1和top-5错误率,其中每张图像短边首先归一化为256,然后从每张图像中裁剪出224×224个像素,(对于Inception-ResNet-v2和SE-Inception-ResNet-v2,每幅图像的短边首先归一化到352,然后裁剪出299×299个像素)。

2.SE块对不同深度基础网络的影响(以ResNet为例):

我们首先将SE-ResNet与一系列标准ResNet架构进行比较。每个ResNet及其相应的SE-ResNet都使用相同的优化方案进行训练。

在这里插入图片描述
虽然应该注意SE块本身增加了深度,但是它们的计算效率极高,即使在扩展的基础架构的深度达到收益递减的点上也能产生良好的回报。而且,我们看到通过对各种不同深度的训练,性能改进是一致的,这表明SE块引起的改进可以与增加基础架构更多深度结合使用。

1.7 结论

在本文中,作者提出了 SE 块,这是一种新颖的架构单元,旨在通过使其能够执行动态通道特征重新校准来提高网络的表示能力。 大量的实验证明了 SENet 的有效性,它在多个数据集上实现了最先进的性能。

2. SE-Resnet50 代码实现

import torch.nn as nn
import torch.nn.functional as F
import math
from torchsummary import summary

class SElayer(nn.Module):
    def __init__(self,ratio,input_aisle):
        super(SElayer, self).__init__()
        self.pool=nn.AdaptiveAvgPool2d(1)
        self.fc=nn.Sequential(
            nn.Linear(input_aisle,input_aisle//ratio,bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(input_aisle//ratio,input_aisle,bias=False),
            nn.Sigmoid()
                             )
    def forward(self,x):
        a, b, _, _ = x.size()
        #读取批数据图片数量及通道数
        y = self.pool(x).view(a, b)
        #经池化后输出a*b的矩阵
        y = self.fc(y).view(a, b, 1, 1)
        #经全连接层输出【啊,吧,1,1】矩阵
        return x * y.expand_as(x)
        #输出权重乘以特征图
class SE_block(nn.Module):
    def __init__(self, outchannels, ratio=16, ):
        super(SE_block, self).__init__()
        self.backbone=nn.Sequential(nn.Conv2d(in_channels=outchannels, out_channels=outchannels // 4, kernel_size=1, stride=1),
                                    nn.BatchNorm2d(outchannels // 4),
                                    nn.ReLU(inplace=True),
                                    nn.Conv2d(in_channels=outchannels // 4, out_channels=outchannels // 4, kernel_size=3, stride=1, padding=1),
                                    nn.BatchNorm2d(outchannels // 4),
                                    nn.ReLU(inplace=True),
                                    nn.Conv2d(in_channels=outchannels // 4, out_channels=outchannels, kernel_size=1, stride=1),
                                    nn.BatchNorm2d(outchannels),
                                    nn.ReLU(inplace=True),
                                    )
        self.Selayer=SElayer(ratio, outchannels)
    def forward(self,x):
        residual=x
        x=self.backbone(x)
        x=self.Selayer(x)
        x=x+residual
        return x
class SE_Idenitity(nn.Module):
    def __init__(self, inchannel,outchannels, stride=2,ratio=16,):
        super(SE_Idenitity, self).__init__()
        self.backbone = nn.Sequential(
                                nn.Conv2d(in_channels=inchannel, out_channels=outchannels//4, kernel_size=1, stride=1),
                                nn.BatchNorm2d(outchannels//4),
                                nn.ReLU(inplace=True),
                                nn.Conv2d(in_channels=outchannels//4, out_channels=outchannels//4, kernel_size=3, stride=stride, padding=1),
                                nn.BatchNorm2d(outchannels//4),
                                nn.ReLU(inplace=True),
                                nn.Conv2d(in_channels=outchannels//4, out_channels=outchannels, kernel_size=1, stride=1),
                                nn.BatchNorm2d(outchannels),
                                nn.ReLU(inplace=True),
                                    )
        self.residual=nn.Conv2d(in_channels=inchannel, out_channels=outchannels, kernel_size=1, stride=stride)
        self.selayer = SElayer(ratio, outchannels)
    def forward(self,x):
        residual=self.residual(x)
        x=self.backbone(x)
        x=self.selayer(x)
        x=x+residual
        return x
class SE_Resnet(nn.Module):
    def __init__(self,layer_num,outchannel_num,num_class=10):
        super(SE_Resnet, self).__init__()
        self.initial=nn.Sequential(nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,padding=3,stride=2),
                                   nn.BatchNorm2d(64),
                                   nn.ReLU(inplace=True),
                                   nn.MaxPool2d(kernel_size=3,padding=1,stride=2))
        self.residual=self.make_layer(layer_num,outchannel_num)
        self.pool=nn.AdaptiveAvgPool2d(1)
        self.fc=nn.Linear(2048,num_class)
    def make_layer(self,number_layer,number_outchannel):
        layer_list=[]

        for i in range(len(number_layer)):
            if i==0:
                stride=1
            else:
                stride=2
            layer_list.append(SE_Idenitity(number_outchannel[i],number_outchannel[i+1],stride))
            for j in range(number_layer[i]-1):
                layer_list.append(SE_block(number_outchannel[i+1]))
        return nn.Sequential(*layer_list)
    def forward(self,x):
        x=self.initial(x)
        x=self.residual(x)
        x=self.pool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return F.softmax(x,dim=1)

def SE_Resnet50(num_class):
    model=SE_Resnet([3,4,6,3],[64,256,512,1024,2048],num_class)
    return model
def SE_Resnet101(num_class):
    model=SE_Resnet([3,4,23,3],[64,256,512,1024,2048],num_class)
    return model
def SE_Resnet151(num_class):
    model = SE_Resnet([3, 8, 36, 3], [64,256, 512, 1024, 2048], num_class)
    return model
device=torch.device("cuda:0")
Xception=SE_Resnet50(10).to(device)
summary(Xception,(3,224,224))

总结

本周学习了SENet这篇经典的通道注意力网络架构,同时也对其代码实现有了一定的了解,下周我将学习空间注意力模块。

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@默然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值