Abstract
1.提出了卷积块注意模块(CBAM),一种前馈卷积神经网络注意模块.
2.给定一个中间特征映射,该模块沿着两个独立的维度顺序推理注意映射(通道和空间),然后将注意映射乘以输入特征映射来进行自适应特征细化。
3.CBAM是一个轻量级的通用模块,可以无缝地集成到任何CNN架构中,并且可以与基础CNN一起进行端到端的训练。
1. Introduction
1.最近研究网络的三个重要因素:深度,宽度和基数。
2.VGGNet:相同形状的堆叠块可以得到公平的结果;ResNet:将残差块的相同拓扑与跳过连接堆叠起来,构建一个极深的体系;GoogLeNet:宽度是提高模型性能的另一个重要因素。增加网络基数:基数不仅节省了参数总数,而且还比深度和宽度具有更强的表示能力。
3.本文研究方向:注意力。注意力不仅告诉我们应该把注意力集中在哪里,他还提高了兴趣的表现。我们的目标是通过注意力机制来提高表征能力,关注重要特征并抑制不重要特征。由于卷积通过混合交叉通道和空间信息来提取信息特征,我们采用CBAM来强调通道和空间这两个维度上的意义特征。依次应用通道和空间注意力模块,每个分支都可以在通道粥和空间轴上学习”什么“和”在哪里“。CBAM通过学习强调或抑制哪些信息,帮助信息在网络中流动。
4.Contribution:
①提出了CBAM模块,可广泛应用于提高CNN的表征能力。
②通过消融研究来验证我们的注意力模块的有效性。
③通过插入我们的轻量级模块,各种网络上的性能得到了极大的提高。
2. Related Work
2.1 Network engineering
大多数最新的网络工程方法主要针对三个因素:深度、宽度和基数,但本文关注的是”注意“。
2.2 Attention mechanism
Wanget提出了一种使用encoder-decoder注意力模块的Residual Attention Network.通过细化特征映射,该网络不仅性能良好,而且对噪声输入具有鲁棒性.Huet引入了一个紧凑的模块来利用通道见的关系,与我们的工作更接近。在他们的SE模块中,使用全局平均池化来计算通道注意力。本文建议使用最大池化。SEnet还忽略了空间注意力,空间注意力在决定”在哪里“集中注意力方面起着重要作用。
在CBAM中,本文基于一个有效的体系结构同时利用了空间和通道注意。
3. Convolutional Block Attention Module
给定一个中间特征映射作为输入,CBAM按顺序推理1D的通道注意映射
和一个2D的空间注意映射
,整体注意力机制可以概括为:
,
其中表示逐元素乘法算子。在乘法过程中,注意值相应地广播(复制):通道注意沿空间维度广播,空间注意沿空间维度广播。
是最终的精炼输出。
3.1 通道注意力
利用特征的通道间关系生成通道注意映射。由于特征映射的每个通道都被视为一个特征检测器,通道注意力集中在给定输入图像的“什么”是有意义的。
为有效计算通道注意力,压缩输入特征图的空间维数(空间信息聚合):
1)平均池化( average-pooling)是目前普遍采用的方法,
2)最大池化(max-pooling)我们认为它收集了关于目标特征另一个重要线索,以推断更精细的通道注意的。
我们同时使用平均池化和最大池化来池化特征。经验证实利用这两个特征比单独使用每一个特征能极大地提高网络的表征能力
操作步骤:
1.使用average-pooling和max-pooling操作聚合特征映射的空间信息,生成两个不同的空间上下文描述符:和
,分别表示average-pooled特征和max-pooled特征。
2.将这两个描述符传入到一个共享网络,以生成通道注意力映射 。共享网络由多层感知器(MLP)和一个隐藏层组成。为了减少参数开销,将隐藏激活大小设置为
3.将共享网络应用于每个描述符后,使用逐元素加法合并输出特征向量。
4:通道注意力计算公式:
其中,表示sigmoid函数,
,
。MLP权重
和
,被两个输入共享,且ReLU激活函数后接
。
3.2 空间注意力
利用特征间的空间关系生成空间注意映射。空间注意力侧重于“何处”是信息部分,与通道注意是互补的。为了计算空间注意:
1.沿着通道轴应用average-pooling和max-pooling,并将它们concat起来以生成有效的特征描述符。沿通道轴应用pooling操作可以有效地突出显示信息区域。
2.在concatenated特征描述符上,应用卷积层来生成空间注意映射,其中他对强调或抑制的位置进行encode。
操作步骤:
1.使用两个pooling操作聚合特征映射的通道信息,生成两个2D映射:和
。分别表示通道中average-pooled特征和max-pooled特征。
2.通过一个标准卷积层将它们concat并卷积,生成2D空间注意力映射。具体计算如下所示:
其中,表示sigmoid函数,
表示卷积核大小。
3.3 注意力模块的排列
给定一幅输入图像,通道和空间两个注意模块计算互补注意,分别关注“什么”和“哪里”。顺序排列比平行排列效果好。通道注意力优先的排列比空间注意力优先的排列略好。
4. Experiments
进行了广泛的消融实验,验证了CBAM的性能优于所有的baseline,证明了CBAM在不同体系结构和不同任务中的普遍适用性。可以在任何CNN架构中无缝集成CBAM。ResNet与ResBlock集成:
4.1 Ablation studies
实验设置
1.使用ImageNet-1K数据集:包括120万张用于训练的图像和50000张用于1000个对象类验证的图像。
2.采用ResNet-50作为基础架构。
3.采用数据增强的方案进行训练,并在测试时应用224224大小的single-crop评估。
4.学习率从0.1开始,每30个epochs下降一次。训练90个epochs。
模块设计过程
1.首先寻找计算通道注意的有效方法;
2.然后寻找计算空间注意的有效方法;
3.考虑如何结合通道和空间注意模块。
4.1.1 Channel Attention计算方法
通过实验验证了同时使用max-pooled和average-pooled这两个功能效果最好。
(1)实验设置
比较了三种不同的通道注意力:
1)平均池化(average pooling):具有average pooling的通道注意力与SE模块相同。
2)最大池化(max pooling);
3)两种池化联合使用:当使用两个池化时,使用共享MLP进行注意推理以保存参数,因为两个聚合通道特征位于相同的语义嵌入空间中。
(2)实验结果 (表1)
1.max-pooled特征与average-pooled特征一样有意义,比baseline提高了准确性,在SE的工作中,只利用average-pooled特征,忽略了max-pooled特征的重要性。
2.本文认为,encode最显著部分程度的max-pooled特征可以补偿encode全局统计信息的average-pooled特征。因此,建议同时使用这两个功能,对这两个功能应用共享网络,共享网络的输出通过元素求和进行合并。
(3)实验表明
通道注意方法是一种有效的方法,可以在不增加额外学习参数的情况下提高SE的性能。
(4)实验结论
本文通道注意力模块中同时使用了平均池化和最大池化。
4.1.2 Spatial Attention计算方法
基于逐通道细化特征,探索了一种计算空间注意力的有效方法。设计理念与通道注意分支对称。
(1)生成2D空间映射:
1)计算2D描述符,该描述符对所有空间位置上每个像素处的通道信息进行encode。
2)将一个卷积层应用于2D描述符,获得原始注意映射。
3)注意力映射由sigmoid函数进行归一化。
(2)两种生成2D描述符的方法:
1)通道池化:沿着通道轴使用平均池化和最大池化;
2)标准的1×1卷积:减少通道维数为1维。
(3)卷积核大小的影响
将卷积核大小分别设置为3和7
(4)实验设置
在实验中,将空间注意模块放在先前设计的通道注意模块之后,因为最终目标是将这两个模块一起使用。
(5)实验结果(表2)
1)通道池化生成2D描述符的方法产生了更好的准确性,这表明明确建模的池化产生了更好的注意力推理,相比于可学习的权重通道池化(既标准1 × 1卷积)。
2)在不同卷积核大小的比较中,采用较大的核可以获得更好的精度。这意味着决定空间上重要的区域需要更广阔的视野(即大的感受野)。
(6)结论
使用通道轴上的average-pooling和max-pooling特征,卷积核大小为7,作为我们的空间注意模块。
4.1.3 结合空间和通道注意模块
(1)有三种不同的安排方法:
1)顺序channel-spatial
2)顺序spatial-channel
3)并行使用两个模块
(2)设计理念
1)由于每个模块具有不同的功能,因此顺序可能会影响整体性能,从空间角度来看,通道注意是全局应用的,而空间注意是局部应用的。
2)我们自然会认为我们可以结合两种注意力输出来构建一个3D注意力映射。在这种情况下,两个注意可以并行应用,然后将两个注意模块的输出相加,并使用sigmoid函数进行归一化。
(3)实验结果(表3)
1)顺序生成的注意映射比并行生成的更精细;
2)通道优先表现比空间优先稍好;
3)所有的安排方法都优于单独使用通道注意,这表明利用这两种注意是至关重要的。
4.1.4 总结
在消融实验中,设计了通道注意模块、空间注意模块以及两个模块的排列方式。
在通道和空间模块中选择使用average-pooling和max-pooling,并且按照通道-空间的顺序排列这两个模块,在空间注意模块中卷积核的大小设置为7。
4.2 在ImageNet-1K上图像分类
在各种网络架构中评估我们的模块(ResNet、WideResNet和ResNext)。
实验结果:
(1)使用CBAM的网络性能显著优于所有baseline,表明CBAM可以在大规模数据集中的各种模型上很好地推广。
(2)提高了SE的精度,现实了最新的pooling方法的有效性,它可以产生更丰富的描述符和空间注意,有效地补充通道注意。
(3)就参数和计算而言,CBAM的总体开销非常小。CBAM在低端设备上应用具有巨大潜力。
4.3 使用Grad-CAM进行网络可视化
Grad-CAM
Grad-CAM是最近提出的一种可视化方法,它使用梯度来计算卷积层中空间位置的重要性。由于梯度是针对于唯一类别计算的,Grad-CAM结果清楚地显示了参与区域,通过观察网络认为对预测类很重要的区域,试图看这个网络是如何很好地利用特征。
可视化比较
比较了CBAM-integrated(ResNet50+CBAM)、baseline(ResNet50)和SE-integrated(ResNet50+SE)可视化结果。CBAM-integrated网络的Grad-CAM masks比其他方法更好地覆盖目标对象区域。也就是说,CBAM-integrated网络能够很好地利用目标区域中的信息并从中聚合特征。注意,target class的分数也相应增加。
4.4 MS COCO目标检测
实验设置
(1)数据集:Microsoft COCO,包括80k训练图像和40k验证图像
(2)评估指标:IOU阈值从0.5到0.95上平均mAp
(3)迭代次数:49000
(4)检测方法:Faster-RCNN
(5)基础网络:ResNet50和ResNet101
通过将CBAM插入baseline网络来提高性能。由于我们在所有模型中使用相同的检测方法,因此增益只能归因于我们的模块CBAM提供的增强表示能力。CBAM在其他识别任务中的泛化性能提高。
4.5 VOC 2007目标检测
本实验将CBAM应用于检测器,而之前的实验CBAM被应用于基础网络。
实验设置:
(1)框架:StairNet,这是基于SSD的最强多尺度方法之一。
(2)在Pytorch平台上复制了SSD和StairNet,分别达到了77.8%和78.9%mAP@.5分别高于原始论文中报告的原始准确性。
(3)将SE和CBAM放在每个分类器之前,在预测之前细化由上采样的全局特征和相应的局部特征组成的最终特征,强制模型仅自适应地选择有意义的特征。
实验结果:
(1)CBAM通过两个backbone网网络提高了所有强baselines的准确性。
(2)CBAM的准确度提高带来的参数开销可以忽略不计,这表明增强不是由于简单的容量增加,而是由于我们有效的特征细化。
(3)使用轻量级backbone网络[35]的结果再次表明,CBAM对于低端设备来说是一种令人感兴趣的方法。
5 Conclusion
(1) 提出卷积块注意模块(CBAM),这是一种提高CNN网络表示能力的新方法。将基于注意力的特征细化应用于两个不同的模块:通道和空间,并在保持较小开销的同时实现了相当大的性能改进。
(2)对于通道注意,我们建议使用max-pooling特征和average-poooling特征,从而产生比SE[28]更精细的注意。
(3)进一步利用空间注意来提高绩效。
(4)在最后一个模块(CBAM)学习强调或抑制的内容和位置,并有效地细化中间特征。
(5)为了验证其有效性,我们对各种最先进的模型进行了广泛的实验,并确认CBAM在三种不同基准数据集(ImageNet1K、MS COCO和VOC 2007)上优于所有baseline。
(6)可视化了模块如何准确地推断给定的输入图像。观察到CBAM模块诱导网络正确地聚焦于目标对象。
6. 总结
1.CBAM block
2.CBAM流程图
3.pytorch源码
这里代码的MLP部分是没有采用conv实现,采用的是FC设计。
import torch
import math
import torch.nn as nn
import torch.nn.functional as F
class BasicConv(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, dilation=1, groups=1, relu=True, bn=True, bias=False):
super(BasicConv, self).__init__()
self.out_channels = out_planes
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
self.bn = nn.BatchNorm2d(out_planes,eps=1e-5, momentum=0.01, affine=True) if bn else None
self.relu = nn.ReLU() if relu else None
def forward(self, x):
x = self.conv(x)
if self.bn is not None:
x = self.bn(x)
if self.relu is not None:
x = self.relu(x)
return x
class Flatten(nn.Module):
def forward(self, x):
return x.view(x.size(0), -1)
class ChannelGate(nn.Module):
def __init__(self, gate_channels, reduction_ratio=16, pool_types=['avg', 'max']):
super(ChannelGate, self).__init__()
self.gate_channels = gate_channels
self.mlp = nn.Sequential(
Flatten(),
nn.Linear(gate_channels, gate_channels // reduction_ratio),
nn.ReLU(),
nn.Linear(gate_channels // reduction_ratio, gate_channels)
)
self.pool_types = pool_types
def forward(self, x):
channel_att_sum = None
for pool_type in self.pool_types:
if pool_type=='avg':
avg_pool = F.avg_pool2d( x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3)))
channel_att_raw = self.mlp( avg_pool )
elif pool_type=='max':
max_pool = F.max_pool2d( x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3)))
channel_att_raw = self.mlp( max_pool )
elif pool_type=='lp':
lp_pool = F.lp_pool2d( x, 2, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3)))
channel_att_raw = self.mlp( lp_pool )
elif pool_type=='lse':
# LSE pool only
lse_pool = logsumexp_2d(x)
channel_att_raw = self.mlp( lse_pool )
if channel_att_sum is None:
channel_att_sum = channel_att_raw
else:
channel_att_sum = channel_att_sum + channel_att_raw
scale = F.sigmoid( channel_att_sum ).unsqueeze(2).unsqueeze(3).expand_as(x)
return x * scale
def logsumexp_2d(tensor):
tensor_flatten = tensor.view(tensor.size(0), tensor.size(1), -1)
s, _ = torch.max(tensor_flatten, dim=2, keepdim=True)
outputs = s + (tensor_flatten - s).exp().sum(dim=2, keepdim=True).log()
return outputs
class ChannelPool(nn.Module):
def forward(self, x):
return torch.cat( (torch.max(x,1)[0].unsqueeze(1), torch.mean(x,1).unsqueeze(1)), dim=1 )
class SpatialGate(nn.Module):
def __init__(self):
super(SpatialGate, self).__init__()
kernel_size = 7
self.compress = ChannelPool()
self.spatial = BasicConv(2, 1, kernel_size, stride=1, padding=(kernel_size-1) // 2, relu=False)
def forward(self, x):
x_compress = self.compress(x)
x_out = self.spatial(x_compress)
scale = F.sigmoid(x_out) # broadcasting
return x * scale
class CBAM(nn.Module):
def __init__(self, gate_channels, reduction_ratio=16, pool_types=['avg', 'max'], no_spatial=False):
super(CBAM, self).__init__()
self.ChannelGate = ChannelGate(gate_channels, reduction_ratio, pool_types)
self.no_spatial=no_spatial
if not no_spatial:
self.SpatialGate = SpatialGate()
def forward(self, x):
x_out = self.ChannelGate(x)
if not self.no_spatial:
x_out = self.SpatialGate(x_out)
return x_out
4.思维导图