论文地址:https://arxiv.org/abs/1807.06521
PyTorch代码:https://github.com/luuuyi/CBAM.PyTorch
CBAM提取特征通道注意力的方式基本和SEnet类似,如下ChannelAttention中的代码所示,在SENet的基础上增加了max_pool的特征提取方式,最终的输出结果是将平均池化的结果与最大池化的结果相加输出。将通道注意力提取厚的特征作为空间注意力模块的输入。
class ChannelAttention(nn.Module):
def __init__(self, in_planes, ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc1 = nn.Conv2d(in_planes, in_planes / 16, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes / 16, in_planes, 1, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
out = avg_out + max_out
return self.sigmoid(out)
CBAM提取特征空间注意力的方式:经过通道注意力后,最终将经过通道重要性选择后的特征图送入特征空间注意力模块,和通道注意力模块类似,空间注意力是以通道为单位进行最大和平均迟化,并将两者的结果进行concat,之后再一个卷积降成 1 ∗ w ∗ h 1*w*h 1∗w∗h的特征图空间权重,再将该权重和输入特征进行点积,从而实现空间注意力机制。
CBAM的实现代码如下:
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
整体框架代码如下:
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.ca = ChannelAttention(planes)
self.sa = SpatialAttention()
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.ca(out) * out # 广播机制
out = self.sa(out) * out # 广播机制
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
论文比较了3种不同的渠道关注:平均池化,最大池化,以及两个池化的联合使用。注意,带有平均池的通道注意模块与SENet模块相同。此外,在使用这两个池时,使用一个共享的MLP进行注意推断来保存参数,因为这两个聚合的通道特征都位于同一语义嵌入空间。在本实验中,我们只使用信道注意模块,并将衰减比定为16,没有进行修改。各种池化方法的实验结果如表1所示。我们观察到最大汇集的特征和平均汇集的特征一样有意义,比较了从基线提高的准确性。共享网络的输出通过按元素进行求和进行合并。我们的经验表明,我们的通道注意方法是一个有效的方法,以推动性能进一步远离SE[28]没有额外的可学习参数。
论文设计探索了一种计算空间注意的有效方法。设计理念与渠道注意分支对称。为了生成一个二维空间注意力地图。
对通道数进行降维存在两种方法:
降维以后,将两个通道变为一个通道时,论文采用了不用的卷积核对特征进行卷积,最终发现使用卷积核大小为7的效果优于卷积核大小为3的效果。因为卷积核大小为7时,卷积核会具有更大的感受野,更加便于观察空间中需要注意的部分。
文中比较了三种不同的通道和空间注意子模块的排列方式:通道注意力-空间注意力、空间注意力-通道注意力,以及两种注意力子模块的平行使用。由于每个模块的功能不同,其顺序可能会影响整体性能。例如,从空间的角度来看,通道注意力是全局应用的,而空间注意力是局部工作的。此外,可以结合两种注意力输出来构建一个3D注意力特征图。在这种情况下,两个注意模块可以并行应用,然后将两个注意模块的输出相加,用Sigmoid函数进行归一化。
表3总结了不同注意安排方法的实验结果。从结果中,我们可以发现,按顺序生成一个注意力图比并行生成一个更好的注意力图。此外,通道一阶的性能略优于空间一阶。请注意,所有的安排方法都优于单独使用通道注意力,这表明利用两个注意力是至关重要的,而最佳的安排策略进一步推动性能。
文中设计了通道注意模块,空间注意模块,以及两个模块的排列。最后的模块如图1和图2所示:通道注意模块和空间注意模块都选择了平均和最大pooling;在空间注意模块中,我们采用核大小为7的卷积;我们按顺序排列通道和空间子模块。(也就是我们最后的模块。ResNet50 + CBAM)的top-1 error为22.66%,远低于SE[28](即如表4所示。