论文地址:https://arxiv.org/abs/1807.06521
一.简介
我们提出了Convolutional Block Attention Module(CBAM), 一个简单而有效的注意模块的前馈卷积神经网络。给出了一个中间特征映射, 我们的模块按照两个独立的维度、通道和空间顺序推断出注意力映射, 然后将注意力映射相乘为自适应特征细化的输入特征映射。因为 CBAM 是一个轻量级和通用的模块, 它可以无缝地集成到任何 CNN 架构只增加微不足道的间接开销, 可以集成到端到端的CNN里面去。通过对 ImageNet-1K、COCO、MS 检测和 VOC 2007 检测数据集的广泛实验, 我们验证了我们的 CBAM。我们的实验表明, 各种模型的分类和检测性能都有了一致的改进, 证明了 CBAM 的广泛适用性。这些代码和模型将公开提供。
二.Convolutional Block Attention Module
给定一个中间特征映射F∈RC xHxW作为输入, CBAM的1维通道注意图Mc ∈RC ×1×1 和2D 空间注意图Ms ∈R1×HxW 如图1所示。总的注意过程可以概括为:
表示逐元素相乘。在相乘过程中,注意值被广播。相应地,通道注意值被沿着空间维度广播,反之亦然。F’’是最终输出。
2.1 Channel attention module
我们利用特征的通道间关系, 生成了通道注意图。当一个特征图的每个通道被考虑作为特征探测器, 通道注意聚焦于 ’ what ’ 是有意义的输入图像。为了有效地计算通道的注意力, 我们压缩了输入特征图的空间维数。为了聚焦空间信息,我们同时使用平均池化和最大池化。我们的实验证实, 同时使用这两种功能大大提高了网络的表示能力。下面将描述详细操作。
我们首先使用平均池化和最大池化操作来聚合特征映射的空间信息, 生成两个不同的空间上下文描述符:Fcavg 和Fcmax , 分别表示平均池化和最大池化。两个描述符然后送到一个共享网络, 以产生我们的通道注意力图 Mc ∈ Rc×1×1。共享网络由多层感知机(MLP) 和一个隐藏层组成。为了减少参数开销, 隐藏的激活大小被设置为 rc/c++×1×1, 其中 r 是压缩率。在将共享网络应用于每个描述符之后, 我们使用逐元素求和合并输出特征向量。简而言之, 频道的注意力被计算为:
Mc(f) = σ(MLP(AvgPool(f)) + MLP(MaxPool(f)))
= σ(W1(W0(Fcavg)) + W1(W0(Fcmax)))
σ是sigmoid function,W0 ∈ RC/r × C, W1 ∈ RC × C/r
W0 ,W1 是多层感知机的权重,共享输入和W0 的RELU激活函数。
2.2 Spatial attention module
我们利用特征间的空间关系, 生成空间注意图。与通道注意力不同的是, 空间注意力集中在 “where” 是一个信息的部分, 这是对通道注意力的补充。为了计算空间注意力, 我们首先在通道轴上应用平均池和最大池运算, 并将它们连接起来以生成一个有效的特征描述符。在串联特征描述符上, 我们应用7×7的卷积生成空间注意图的层Ms (F) ∈RH×W 。我们描述下面的详细操作.
我们使用两个池化操作来聚合功能映射的通道信息, 生成两个2维映射:Fsavg∈R1×HxW 和Fsmax∈R1×HxW 每个通道都表示平均池化和最大池化。然后通过一个标准的卷积层连接和卷积混合, 产生我们的2D 空间注意图。简而言之, 空间注意力被计算为:
Ms (f) = σ( f7×7( AvgPool(f) ; MaxPool(F)] )))
= σ(f7×7 (Fsavg; Fsmax])),
σ是sigmoid function, f7×7 是7×7的卷积。
通过实验发现 串联两个注意力模块的效果要优于并联 。通道注意力放在前面要优于空间注意力模块放在前面。
三.使用Grad-CAM进行网络可视化
四.总结
作者提出了一种提高 CNN 网络表示的新方法–卷积瓶颈注意模块 (CBAM)。作者将基于注意力的特征细化成两个不同的模块、通道和空间结合起来, 实现了显著的性能改进, 同时保持了小的开销。对于通道的关注,使用最大池化和平均池化,最终模块 (CBAM) 学习了如何有效地强调或压缩提取中间特征。为了验证它的有效性, 我们进行了广泛的实验与并证实, CBAM 优于所有基线上的三不同的基准数据集: ImageNet-1K, COCO, 和 VOC 2007。此外, 我们可视化模块如何准确推断给定的输入图像。CBAM 或许会成为各种网络体系结构的重要组成部分。
附CBAM代码
from collections import OrderedDict
import math
import torch
import torch.nn as nn
# import torchvision.models.resnet
class CBAM_Module(nn.Module):
def __init__(self, channels, reduction):
super(CBAM_Module, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1,
padding=0)
self.relu = nn.ReLU(inplace=True)
self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1,
padding=0)
self.sigmoid_channel = nn.Sigmoid()
self.conv_after_concat = nn.Conv2d(2, 1, kernel_size = 3, stride=1, padding = 1)
self.sigmoid_spatial = nn.Sigmoid()
def forward(self, x):
# Channel attention module:(Mc(f) = σ(MLP(AvgPool(f)) + MLP(MaxPool(f))))
module_input = x
avg = self.avg_pool(x)
mx = self.max_pool(x)
avg = self.fc1(avg)
mx = self.fc1(mx)
avg = self.relu(avg)
mx = self.relu(mx)
avg = self.fc2(avg)
mx = self.fc2(mx)
x = avg + mx
x = self.sigmoid_channel(x)
# Spatial attention module:Ms (f) = σ( f7×7( AvgPool(f) ; MaxPool(F)] )))
x = module_input * x
module_input = x
avg = torch.mean(x, 1, keepdim=True)
mx, _ = torch.max(x, 1, keepdim=True)
x = torch.cat((avg, mx), 1)
x = self.conv_after_concat(x)
x = self.sigmoid_spatial(x)
x = module_input * x
return x
class Bottleneck(nn.Module):
"""
Base class for bottlenecks that implements `forward()` method.
"""
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.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(x)
out = self.se_module(out) + residual
out = self.relu(out)
return out
class CBAMResNetBottleneck(Bottleneck):
"""
ResNet bottleneck with a CBAM_Module. It follows Caffe
implementation and uses `stride=stride` in `conv1` and not in `conv2`
(the latter is used in the torchvision implementation of ResNet).
"""
expansion = 4
def __init__(self, inplanes, planes, groups, reduction, stride=1,
downsample=None):
super(CBAMResNetBottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False,
stride=stride)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1,
groups=groups, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.se_module = CBAM_Module(planes * 4, reduction=reduction)
self.downsample = downsample
self.stride = stride
class CABMNet(nn.Module):
def __init__(self, block, layers, groups, reduction, dropout_p=0.2,
inplanes=128, input_3x3=True, downsample_kernel_size=3,
downsample_padding=1, num_classes=1000):
super(CABMNet, self).__init__()
self.inplanes = inplanes
if input_3x3:
layer0_modules = [
('conv1', nn.Conv2d(3, 64, 3, stride=2, padding=1,
bias=False)),
('bn1', nn.BatchNorm2d(64)),
('relu1', nn.ReLU(inplace=True)),
('conv2', nn.Conv2d(64, 64, 3, stride=1, padding=1,
bias=False)),
('bn2', nn.BatchNorm2d(64)),
('relu2', nn.ReLU(inplace=True)),
('conv3', nn.Conv2d(64, inplanes, 3, stride=1, padding=1,
bias=False)),
('bn3', nn.BatchNorm2d(inplanes)),
('relu3', nn.ReLU(inplace=True)),
]
else:
layer0_modules = [
('conv1', nn.Conv2d(3, inplanes, kernel_size=7, stride=2,
padding=3, bias=False)),
('bn1', nn.BatchNorm2d(inplanes)),
('relu1', nn.ReLU(inplace=True)),
]
# To preserve compatibility with Caffe weights `ceil_mode=True`
# is used instead of `padding=1`.
layer0_modules.append(('pool', nn.MaxPool2d(3, stride=2,
ceil_mode=True)))
self.layer0 = nn.Sequential(OrderedDict(layer0_modules))
self.layer1 = self._make_layer(
block,
planes=64,
blocks=layers[0],
groups=groups,
reduction=reduction,
downsample_kernel_size=1,
downsample_padding=0
)
self.layer2 = self._make_layer(
block,
planes=128,
blocks=layers[1],
stride=2,
groups=groups,
reduction=reduction,
downsample_kernel_size=downsample_kernel_size,
downsample_padding=downsample_padding
)
self.layer3 = self._make_layer(
block,
planes=256,
blocks=layers[2],
stride=2,
groups=groups,
reduction=reduction,
downsample_kernel_size=downsample_kernel_size,
downsample_padding=downsample_padding
)
self.layer4 = self._make_layer(
block,
planes=512,
blocks=layers[3],
stride=2,
groups=groups,
reduction=reduction,
downsample_kernel_size=downsample_kernel_size,
downsample_padding=downsample_padding
)
self.avg_pool = nn.AvgPool2d(7, stride=1)
self.dropout = nn.Dropout(dropout_p) if dropout_p is not None else None
self.last_linear = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
# for m in self.modules():
# if isinstance(m, nn.Conv2d):
# nn.init.kaiming_normal(m.weight.data)
# elif isinstance(m, nn.BatchNorm2d):
# m.weight.data.fill_(1)
# m.bias.data.zero_()
def _make_layer(self, block, planes, blocks, groups, reduction, stride=1,
downsample_kernel_size=1, downsample_padding=0):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=downsample_kernel_size, stride=stride,
padding=downsample_padding, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, groups, reduction, stride,
downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes, groups, reduction))
return nn.Sequential(*layers)
def features(self, x):
x = self.layer0(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
return x
def logits(self, x):
x = self.avg_pool(x)
if self.dropout is not None:
x = self.dropout(x)
x = x.view(x.size(0), -1)
x = self.last_linear(x)
return x
def forward(self, x):
x = self.features(x)
x = self.logits(x)
return x
def cbam_resnet50(num_classes=1000):
model = CABMNet(CBAMResNetBottleneck, [3, 4, 6, 3], groups=1, reduction=16,
dropout_p=None, inplanes=64, input_3x3=False,
downsample_kernel_size=1, downsample_padding=0,
num_classes=num_classes)
print(model)
return model
cbam_resnet50()