参考:卷积中的attention map理解及可视化(文章中对于卷积得到特征图的过程进行了图解,很容易理解);
CV中的Attention机制(介绍了CBAM)
计算机视觉中的Attention机制详解(介绍了SENet)
attention in cnn
说得很透彻CV中的注意力机制
近几年来,深度学习与视觉注意力机制结合的研究工作,大多数是集中于使用掩码(mask)来形成注意力机制。掩码的原理在于通过另一层新的权重,将图片数据中关键的特征标识出来,通过学习训练,让深度神经网络学到每一张新图片中需要关注的区域,也就形成了注意力。
计算机视觉中的注意力机制的基本思想是让模型学会专注,把注意力集中在重要的信息上而忽视不重要的信息。
attention机制的本质就是利用相关特征图学习权重分布,再用学出来的权重施加在原特征图之上最后进行加权求和。不过施加权重的方式略有差别,大致总结为如下四点:
- 这个加权可以是保留所有分量均做加权(即soft attention);也可以是在分布中以某种采样策略选取部分分量(即hard attention),此时常用RL来做。
- 加权可以作用在空间尺度上,给不同空间区域加权;
- 加权可以作用在Channel尺度上,给不同通道特征加权;
- 加权可以作用在不同时刻历史特征上,结合循环结构添加权重,例如机器翻译,或者视频相关的工作。
为了更清楚地介绍计算机视觉中的注意力机制,通常将注意力机制中的模型结构分为三大注意力域来分析。主要是:空间域(spatial domain),通道域(channel domain),混合域(mixed domain)。
- 空间域——将图片中的的空间域信息做对应的空间变换,从而能将关键的信息提取出来。对空间进行掩码的生成,进行打分,代表是Spatial Attention Module。
- 通道域——类似于给每个通道上的信号都增加一个权重,来代表该通道与关键信息的相关度的话,这个权重越大,则表示相关度越高。对通道生成掩码mask,进行打分,代表是senet, Channel Attention Module。
- 混合域——空间域的注意力是忽略了通道域中的信息,将每个通道中的图片特征同等处理,这种做法会将空间域变换方法局限在原始图片特征提取阶段,应用在神经网络层其他层的可解释性不强。
卷积神经网络中常用的Attention
在卷积神经网络中常用到的主要有两种:一种是spatial attention, 另外一种是channel attention。当然有时也有使用空间与通道混合的注意力,其中混合注意力的代表主要是BAM, CBAM。
Spatial Attention:
对于卷积神经网络,CNN每一层都会输出一个C x H x W的特征图,C就是通道,同时也代表卷积核的数量,亦为特征的数量,H 和W就是原始图片经过压缩后的图的高度和宽度,
spatial attention就是对于所有的通道,在二维平面上,对H x W尺寸的特征图学习到一个权重,对每个像素都会学习到一个权重。你可以想象成一个像素是C维的一个向量,深度是C,在C个维度上,权重都是一样的,但是在平面上,权重不一样。
Channel Attention: 就是对每个C(通道),在channel维度上,学习到不同的权重,平面维度上权重相同。所以基于通道域的注意力通常是对一个通道内的信息直接全局平均池化,而忽略每一个通道内的局部信息。
spatial 和 channel attention可以理解为关注图片的不同区域和关注图片的不同特征。channel attention的全面介绍可以参考论文:SCA-CNN,通道注意力在图像分类中的网络结构方面,典型的就是SENet。视觉注意力机制在分类网络中的应用-SENet
Squeeze-and-Excitation Networks(SENet)
论文地址:https://arxiv.org/abs/1709.01507
官方代码地址:https://github.com/hujie-frank/SENet
Pytorch实现代码:https://github.com/moskomule/senet.pytorch
SENet是Squeeze-and-Excitation Networks的简称,由Momenta公司所作并发于2017CVPR,论文中的SENet赢得了ImageNet最后一届(ImageNet 2017)的图像识别冠军,SENet主要是学习了channel之间的相关性,筛选出了针对通道的注意力,稍微增加了一点计算量,但是效果比较好。
论文中的motivation: 希望显式地建模特征通道之间的相互依赖关系,通过采用了一种全新的“特征重标定”策略—自适应地重新校准通道的特征响应 。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征。 该文提出的SE模块思想简单,易于实现,并且很容易可以加载到现有的网络模型框架中。
SENet 通俗的说就是:通过对卷积之后得到的feature map进行处理,得到一个和通道数一样的一维向量作为每个通道的评价分数,然后将改动之后的分数通过乘法逐通道加权到原来对应的通道上,最后得到输出结果,就相当于在原有的基础上只添加了一个模块而已。
SENet可以作为一个子模块加载到分类网络结构中去。
上左图是将SE模块嵌入到Inception结构的一个示例。方框旁边的维度信息代表该层的输出。这里我们使用global average pooling作为Squeeze操作。紧接着两个Fully Connected 层组成一个Bottleneck结构去建模通道间的相关性,并输出和输入特征同样数目的权重。我们首先将特征维度降低到输入的1/16,然后经过ReLu激活后再通过一个Fully Connected 层升回到原来的维度。bottleneck层是否可理解为一个两头大中间小(即先降低维度,之后再还原回去。以具有更多非线性和减少参数量。
这样做比直接用一个Fully Connected层的好处在于:
- 具有更多的非线性,可以更好地拟合通道间复杂的相关性;
- 极大地减少了参数量和计算量。然后通过一个Sigmoid的门获得0-1 之间归一化的权重,最后通过一个Scale的操作来将归一化后的权重加权到每个通道的特征上。
除此之外,SE模块还可以嵌入到含有skip-connections的模块中。
具体介绍见参考文章。
容易理解pytorch代码实现
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x)
什么是注意力机制?
注意力机制(Attention Mechanism)是机器学习中的一种数据处理方法,广泛应用在自然语言处理、图像识别及语音识别等各种不同类型的机器学习任务中。
通俗来讲:注意力机制就是希望网络能够自动学出来图片或者文字序列中的需要注意的地方。比如人眼在看一幅画的时候,不会将注意力平等地分配给画中的所有像素,而是将更多注意力分配给人们关注的地方。
从实现的角度来讲:注意力机制通过神经网络的操作生成一个掩码mask, mask上的值一个打分,评价当前需要关注的点的评分。
这个mask是否可理解为权重?
注意力机制可以分为:
- 通道注意力机制:对通道生成掩码mask,进行打分,代表是senet, Channel Attention Module
- 空间注意力机制:对空间进行掩码的生成,进行打分,代表是Spatial Attention Module
混合域注意力机制:同时对通道注意力和空间注意力进行评价打分,代表的有BAM, CBAM
CBAM模块的实现
参考:【CV中的Attention机制】ECCV 2018 Convolutional Block Attention Module
CBAM全称是Convolutional Block Attention Module, 是在ECCV2018上发表的注意力机制代表作之一。本人在打比赛的时候遇见过有人使用过该模块取得了第一名的好成绩,证明了其有效性。
在该论文中,作者研究了网络架构中的注意力,注意力不仅要告诉我们重点关注哪里,还要提高关注点的表示。 目标是通过使用注意机制来增加表现力,关注重要特征并抑制不必要的特征。为了强调空间和通道这两个维度上的有意义特征,作者依次应用通道和空间注意模块,来分别在通道和空间维度上学习关注什么、在哪里关注。此外,通过了解要强调或抑制的信息也有助于网络内的信息流动。
主要网络架构也很简单,一个是通道注意力模块,另一个是空间注意力模块,CBAM就是先后集成了通道注意力模块和空间注意力模块。
对于一个中间层的feature map:F \in\mathbb R^{CHW},CBAM将会顺序推理出1维的channel attention map M_c \in\mathbb R^{C11}以及2维的spatial attention map M_s \in\mathbb R^{1HW}
2.1 通道注意力机制
# planes是feature map的通道个数。
class ChannelAttention(nn.Module):
def __init__(self, in_planes, rotio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.sharedMLP = nn.Sequential(
nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False), nn.ReLU(),
nn.Conv2d(in_planes // rotio, in_planes, 1, bias=False))
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avgout = self.sharedMLP(self.avg_pool(x))
maxout = self.sharedMLP(self.max_pool(x))
return self.sigmoid(avgout + maxout)
上面这个类得到的是一个attention map,也可以说是mask,后面要乘到原来的feature map上面来得到新的feature map.
2.2 空间注意力机制
这个部分实现也很简单,分别从通道维度进行求平均和求最大,合并得到一个通道数为2的卷积层,然后通过一个卷积,得到了一个通道数为1的spatial attention。
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.conv = nn.Conv2d(2,1,kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avgout = torch.mean(x, dim=1, keepdim=True)
maxout, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avgout, maxout], dim=1)
x = self.conv(x)
return self.sigmoid(x)
2.3 Convolutional bottleneck attention module
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 isnotNone:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
为何要先使用通道注意力机制再使用空间注意力机制?
为何要先使用通道注意力机制然后再使用空间注意力机制?使用顺序使用这两个模块还是并行的使用两个模块?其实是作者已经做过了相关实验,并且证明了先试用通道然后再使用空间注意力机制这样的组合效果比较好,这也是CBAM的通用组合模式。
什么情况下可以使用?
以ResNet为例,论文中提供了改造的示意图,如下图所示:
- 也就是在ResNet中的每个block之间添加了CBAM模块
如何更有效的计算channel attention?
可以看出来,使用avgpool和maxpool可以更好的降低错误率,大概有1-2%的提升,这个组合就是dual pooling,能提供更加精细的信息,有利于提升模型的表现。
如何更有效的计算spatial attention?
这里的空间注意力机制参数也是有avg, max组成,另外还有一个卷积的参数kernel_size(k), 通过以上实验,可以看出,当前使用通道的平均和通道的最大化,并且设置kernel size=7是最好的。
代码实现中的一些方法
MaxPool2d()
class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
参数:
- kernel_size(int or tuple) - max pooling的窗口大小,
- stride(int or tuple, optional) - max pooling的窗口移动的步长。默认值是kernel_size
- padding(int or tuple, optional) - 输入的每一条边补充0的层数
- dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数
- kernel_size(int or tuple) - max pooling的窗口大小,
nn.AdaptiveAvgPool2d()与nn.AvgPool2d()模块的区别
- nn.AvgPool2d()
- 一般我们使用它的时候,只需要关注 kernel_size 、stride 与 padding 三个参数就行了,最后输出的尺寸为:
- nn.AdaptiveAvgPool2d()模块
- 相比 nn.AvgPool2d() 多了个自适应,自适应就代表了使用更简单方便。
- 也就是说,我们只需要关注输出维度的大小 output_size ,具体的实现过程和参数选择自动帮你确定了。