这是去年ECCV2018的一篇文章,主要贡献为提出一个新的网络结构。之前有一篇论文提出了SENet,在feature map的通道上进行attention生成,然后与原来的feature map相乘。这篇文章指出,该种attention方法只关注了通道层面上哪些层会具有更强的反馈能力,但是在空间维度上并不能体现出attention的意思。CBAM作为本文的亮点,将attention同时运用在channel和spatial两个维度上,CBAM与SE Module一样,可以嵌入了目前大部分主流网络中,在不显著增加计算量和参数量的前提下能提升网络模型的特征提取能力。 近几年,随着CNN的兴起,很多结构新颖、有效的网络结构被提出,比如ResNet、ResNext等;最近,大部分论文证明了在网络结构中引入attention机制可以提升网络模型的特征表达能力。attention不止能告诉网络模型该注意什么,同时也能增强特定区域的表征。本文的CBAM在channel和spatial两个维度上引入了attention机制。
1.我们提出了一种简单而有效的注意力模块(CBAM),可以广泛应用于提高CNN的表示能力。
2.我们通过广泛的消融研究验证了我们注意力模块的有效性。
3.我们通过插入轻量级模块,验证多个基准(ImageNet-1K,MS COCO和VOC 2007)的各种网络性能得到了极大提升。
根据近年的深度学习的深入研究调查了网络的三个重要因素:深度,宽度和基数。
当然除了这些因素之外,该论文还研究了架构设计的一个不同方面,即注意力。虽然在之前的文献中,已经有很多的相关研究关于注意力的重要性。所谓的注意力不仅要告诉重点在哪里,还要提高兴趣点的代表性。此论文的目标是通过使用注意机制来增加表现力:关注重要特征并抑制不必要的特征。
由于卷积运算通过将多通道和空间信息混合在一起来提取信息特征,而在论文中采用他们的模块来强调沿着这两个主要维度的有意义的特征:通道和空间轴。这样每个分支都可以分别在通道和空间轴上学习到"what"和"where"。
主要的公式:
F是input feature map作为输入,其中⊗表示逐元素乘法
Mc表示在channel维度上做attention提取的操作
Ms表示的是在spatial维度上做attention提取的操作
下图便是具体的处理过程图:
算法公式:
为了有效地计算通道注意力,我们压缩输入特征图谱的空间维度。为了聚集空间信息首先将feature map在spatial维度上进行压缩,得到一个一维矢量以后再进行操作。对输入feature map进行spatial维度压缩时,a使用的是average pooling(平均池化)和max pooling(最大值池化),通过两个pooling函数以后总共可以得到两个一维矢量,这样做的好处是:average pooling有效地学习目标物体,而最大池化收集了关于独特对象特征的另一个重要线索,以推断出通道方面的注意力。此外,我在网上学习时看到过另一种说法:average pooling对feature map上的每一个像素点都有反馈,而 max pooling在进行梯度反向传播计算只有feature map中响应最大的地方有梯度的反馈,能作为GAP的一个补充。
在得到两个一维矢量后,将其放入一个共享网络中,共享网络是由一个隐藏层和多层感知机(MLP)组成。为了减少参数开销,隐藏的激活大小设置为,其中r是压缩率。在将共享网络应用于矢量之后,我们使用逐元素求和来合并输出特征向量。其中σ表示Sigmoid函数,即再经过激活函数后最后得到的结果
值得注意的是,多层感知机模型中W0和W1之间的feature需要使用ReLU作为激活函数去处理。代码如下:
def channel_attention(input_feature, name, ratio=8):
kernel_initializer = tf.contrib.layers.variance_scaling_initializer()
bias_initializer = tf.constant_initializer(value=0.0)
with tf.variable_scope(name):
channel = input_feature.get_shape()[-1]
avg_pool = tf.reduce_mean(input_feature, axis=[1,2], keepdims=True)
assert avg_pool.get_shape()[1:] == (1,1,channel)
avg_pool = tf.layers.dense(inputs=avg_pool,
units=channel//ratio,
activation=tf.nn.relu,
kernel_initializer=kernel_initializer,
bias_initializer=bias_initializer,
name='mlp_0',
reuse=None)
assert avg_pool.get_shape()[1:] == (1,1,channel//ratio)
avg_pool = tf.layers.dense(inputs=avg_pool,
units=channel,
kernel_initializer=kernel_initializer,
bias_initializer=bias_initializer,
name='mlp_1',
reuse=None)
assert avg_pool.get_shape()[1:] == (1,1,channel)
max_pool = tf.reduce_max(input_feature, axis=[1,2], keepdims=True)
assert max_pool.get_shape()[1:] == (1,1,channel)
max_pool = tf.layers.dense(inputs=max_pool,
units=channel//ratio,
activation=tf.nn.relu,
name='mlp_0',
reuse=True)
assert max_pool.get_shape()[1:] == (1,1,channel//ratio)
max_pool = tf.layers.dense(inputs=max_pool,
units=channel,
name='mlp_1',
reuse=True)
assert max_pool.get_shape()[1:] == (1,1,channel)
scale = tf.sigmoid(avg_pool + max_pool, 'sigmoid')
return input_feature * scale
算法公式:
首先沿通道轴应用平均池化和最大池化操作(此处注意点:这里的压缩变成了通道层面上的压缩,不再是最开始的feature map,这里面的F=Mc),接着对输入特征分别在通道维度上做了avg和max操作。最后得到了两个二维的feature,将其按通道维度拼接在一起得到一个通道数为2的feature map,之后使用一个包含单个卷积核的隐藏层对其进行卷积操作,要保证最后得到的feature在spatial维度上与输入的feature map一致。
其中σ表示sigmoid函数,f7×7表示卷积操作,卷积核大小为7×7。代码如下:
def spatial_attention(input_feature, name):
kernel_size = 7
kernel_initializer = tf.contrib.layers.variance_scaling_initializer()
with tf.variable_scope(name):
avg_pool = tf.reduce_mean(input_feature, axis=[3], keepdims=True)
assert avg_pool.get_shape()[-1] == 1
max_pool = tf.reduce_max(input_feature, axis=[3], keepdims=True)
assert max_pool.get_shape()[-1] == 1
concat = tf.concat([avg_pool,max_pool], 3)
assert concat.get_shape()[-1] == 2
concat = tf.layers.conv2d(concat,
filters=1,
kernel_size=[kernel_size,kernel_size],
strides=[1,1],
padding="same",
activation=None,
kernel_initializer=kernel_initializer,
use_bias=False,
name='conv')
assert concat.get_shape()[-1] == 1
concat = tf.sigmoid(concat, 'sigmoid')
return input_feature * concat
当然论文作者也说明了注意力模块的安排。给定输入图像,两个注意力模块,通道和空间,计算互补注意力,分别关注“什么”和“何处”。 考虑到这一点,可以以并行或顺序方式放置两个模块。我们发现顺序排列比并行排列能得到更好的结果。对于顺序过程的安排,我们的实验结果表明,通道的第一顺序略好于空间第一顺序。
def cbam_block(input_feature, name, ratio=8):
"""Contains the implementation of Convolutional Block Attention Module(CBAM) block.
As described in https://arxiv.org/abs/1807.06521.
"""
with tf.variable_scope(name):
attention_feature = channel_attention(input_feature, 'ch_at', ratio)
attention_feature = spatial_attention(attention_feature, 'sp_at')
print ("CBAM Hello")
return attention_feature