人类在处理信息时,天然会过滤掉不太关注的信息,着重于感兴趣信息,于是将这种处理信息的机制称为注意力机制
。
注意力机制分类:软注意力机制(全局注意)、硬注意力机制(局部注意)、和自注意力机制(内注意)。
软注意力机制:根据每个区域被关注程度的高低,用0~1之间的概率值来表示;与硬注意力相比,软注意力是一个可微的过程,可以通过训练过程的前向和后向反馈学习得到;因为对每部分信息都有考虑,所以相对于硬注意机制,计算量比较大。
硬注意力机制:又称强注意力机制,即哪些区域是被关注的,哪些区域是不被关注的,是一个是或不是的问题,会直接舍弃掉一些不相关项,如在图像领域的图像裁剪,裁剪后留下的部分即被关注的区域;优势在于会节省一定的时间和计算成本,但是有可能会丢失一部分信息。值得注意的是,因其是一个不可微的过程,所以在cv领域,一般用在强化学习中;如在视频领域中,因为有时序性关系,每张图片即为某个时间点下的采样,强注意力机制则可以看成是否对该时间点的采样关注,可以通过强化学习来训练。
自注意力机制:自注意力是对每个输入赋予的权重取决于输入数据之间的关系,即通过输入项内部之间的相互博弈决定每个输入项的权重。与前两项机制相比,自注意力在计算时,具有并行计算的优势。
CV领域注意力分类
在CV领域,按照注意力关注的域,可以将其分成:
- 空间域注意力
- 通道域注意力
- 时间域注意力
- 混合域注意力
- 自注意力
- CV领域注意力分类
- 1.通道注意力机制Channel Attention
- SENet
- GSoP-Net
- FcaNet
- ECA-Net
- SRM
- GCT
- 2.空间注意力机制Spatial Attention
- RAM
- DRAW
- STN-Net
- DCN v1/v2
- GENet
- Non-local Neural Networks
- CCNet
- EMANet
- SASA
- SAN
- ViT/DETR
- 3.时间注意力机制Temporal Attention
- TAM
- GLTR
- 4.通道和空间注意力机制
- CBAM
- BAM
- scSE
- Triplet Attention
- RGA
- Residual attention
- SimAM
- SCNet
- 5.时域和空间注意力机制
- RSTAN
- STA
- STGCN
1.通道注意力机制Channel Attention
通道注意力机制在计算机视觉中,更关注特征图中channel之间的关系,而普通的卷积会对通道做通道融合,这个开山鼻祖是SENet,后面有GSoP-Net,FcaNet 对SENet中的squeeze部分改进,EACNet对SENet中的excitation部分改进,SRM,GCT等对SENet中的scale部分改进。
SENet
paper:https://arxiv.org/abs/1709.01507
github: pytorch:https://github.com/moskomule/senet.pytorch
SENet《Squeeze-and-Excitation Networks》是CVPR17年的一篇文章,提出SE module。在卷积神经网络中,卷积操作更多的是关注感受野,在通道上默认为是所有通道的融合,而SENet提出SE模块,将注意力放到通道之间,希望模型可以学习到不同通道之间的权重:
SE模块由三个部分组成:squeeze、excitation和scale。
首先对h* w* c2大小的feature map 做squeeze操作将其变成1* 1* c2大小,这一步原文中使用简单粗暴的GAP(全局平均池化);
第二步excitation操作,对1* 1* c2的特征图经过两次全连接,特征图大小1* 1* c2→1* 1* c3→1* 1* c2,再对1* 1* c2的特征图做sigmoid将值限制到[0,1]范围内;
第三步scale, 将1* 1* c2的特征权重图与输入的h* w* c2的feature map在通道上相乘;完成SE模块的通道注意力机制。
值得注意的是,在squeeze部分,作者使用的是最简单的全局平均池化的方式,主要原因是作者基于通道的整体信息,关注通道之间的相关性,而不是空间分布的相关性,也尽可能的屏蔽掉空间分布信息;
在excitation部分,作者通过两个全连接实现1* 1* c2→1* 1* c3→1* 1* c2,第一个全连接层将c2压缩到c3,用于计算量减少,在原文中作者做实验后发现c3=c2/16时,能实现性能和计算量的平衡。
SE模型可以即插即用
,理论上可以安插在任意一个卷积后面,简单方便,只会增加一点参数量和计算量。
class SEAttention(nn.Module):
def __init__(self, channel=512,reduction=16):
super().__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 init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
b, c, _, _ = x.size() # 取batchsize和channel大小
y = self.avg_pool(x).view(b, c) # squeeze全局平均池化
y = self.fc(y).view(b, c, 1, 1) # excitation全连接层
return x * y.expand_as(x) # scale特征图乘通道注意力
GSoP-Net
paper:https://arxiv.org/abs/1811.12006
github:https://github.com/ZilinGao/Global-Second-order-Pooling-Convolutional-Networks
SE模块中squeeze部分用的是全局平均池化,GSoP-Net的主要创新点是将一阶全局平均池化替换成二阶池化。
主要操作为对输入为h* w* c’的特征图先通过卷积降维到h* w* c,然后通道之间两两计算相关性,得到c* c的协方差矩阵,第i行元素表明第 i个通道和其他通道的统计层面的依赖。由于二次运算涉及到改变数据的顺序,因此对协方差矩阵执行逐行归一化,保留固有的结构信息。然后通过激励模块对协方差特征图做非线性逐行卷积得到1* 1* 4c的特征信息,后面同SENet。相对于SNet,增加了全局的统计建模。
FcaNet
paper:http://arxiv.org/abs/2012.11879
github: https://github.com/cfzd/FcaNet
FCANet主要也是更新SE模块中squeeze部分,作者设计了一种新的高效多谱通道注意力框架。该框架在GAP是DCT的一种特殊形式的基础上,在频域上推广了GAP通道注意力机制,提出使用有限制的多个频率分量代替只有最低频的GAP。通过集成更多频率分量,不同的信息被提取从而形成一个多谱描述。此外,为了更好进行分量选择,作者设计了一种二阶段特征选择准则,在该准则的帮助下,提出的多谱通道注意力框架达到了SOTA效果。
ECA-Net
paper: https://arxiv.org/pdf/1910.03151
github: https://github.com/BangguWu/ECANet
ECANet 改进SENet中的excitation部分,SENet采用的降维操作会对通道注意力的预测产生负面影响,且获取依赖关系效率低且不必要 ;基于此,提出了一种针对CNN的高效通道注意力(ECA)模块,避免了降维,有效地实现了跨通道交互 ;
主要提出了一个用CNN的极轻量化的通道注意力模型,通过大小为 k 的快速一维卷积实现,其中核大小k表示局部跨通道交互的覆盖范围,即有多少领域参与了一个通道的注意预测 ;同时为了避免通过交叉验证手动调整 k,开发了一种自适应方法确定 k,其中跨通道交互的覆盖范围 (即核大小k) 与通道维度成比例 。
class ECAAttention(nn.Module):
def __init__(self, kernel_size=3):
super().__init__()
self.gap=nn.AdaptiveAvgPool2d(1)
self.conv=nn.Conv1d(1,1,kernel_size=kernel_size,padding=(kernel_size-1)//2)
self.sigmoid=nn.Sigmoid()
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
y=self.gap(x) #bs,c,1,1
y=y.squeeze(-1).permute(0,2,1) #bs,1,c
y=self.conv(y) #bs,1,c
y=self.sigmoid(y) #bs,1,c
y=y.permute(0,2,1).unsqueeze(-1) #bs,c,1,1
return x*y.expand_as(x)
SRM
paper: https://arxiv.org/pdf/1903.10829
github:https://github.com/hyunjaelee410/style-based-recalibration-module
SRM受风格迁移的启发,对SENet中的squeeze和excitation部分进行更新:
提出了一种基于style的重新校准模块(SRM),可以通过利用其style自适应地重新校准中间特征图。SRM首先通过样式池从特征图的每个通道中提取样式信息,然后通过与通道无关的style集成来估计每个通道的重新校准权重。
给定一个输入特征特征图大小为C × H × W ,SRM首先通过结合全局平均池和全局标准差池化的风格式池化收集全局信息。然后使用通道全连接层(即每个通道全连接)、批量归一化BN和sigmoid函数σ来提供注意力向量。最后,与SE块一样,输入特征乘以注意力向量。它利用输入特征的均值和标准差来提高捕获全局信息的能力。为了降低计算层(CFC)的完全连接要求,在全连接的地方也采用了CFC。通过将各个style的相对重要性纳入特征图,SRM有效地增强了CNN的表示能力。重点是轻量级,引入的参数非常少,同时效果还优于SENet。
GCT
paper: https://openaccess.thecvf.com/content_CVPR_2020/papers/Yang_Gated_Channel_Transformation_for_Visual_Recognition_CVPR_2020_paper.pdf
github: https://github.com/z-x-yang/GCT
GCT是由百度提出,一种通用且轻量型变化单元,结合了归一化和注意力机制,分析通道之间的相互关系(竞争or协作),对SENet中的squeeze、excitation和scale部分进行更新:在squeeze部分中使用L2 norm进行了global context embeding,同时在excitation中,仍然使用L2 norm 对通道归一化,在scale中通过设置权重γ和偏置β来控制通道特征是否激活。当一个通道的特征权重γc被正激活,GCT将促进这个通道的特征和其它通道的特征“竞争”。当一个通道的特征 γc 被负激活,GCT将促进这个通道的特征和其它通道的特征“合作”。
2.空间注意力机制Spatial Attention
空间注意力机制主要包括RNN为基础的注意力机制、预测相关区域的注意力机制、预测潜在mask的注意力机制以及自注意力机制。
- RAM为用于cv领域,backbone为RNN的注意力机制,DRAW,Glimps Net 是基于此的拓展和延申;
- STN为最早关注相关区域的注意力机制,DCN,DCN V2 也是此后研究关注相关区域的注意力;
- GENet为预测潜在mask的注意力机制;
- 在自注意力机制上,一开始提出是Non-local ,后续有提高效率的自注意力:CCNet,EMANet; 有关注局部的自注意力:SASA,SAN; 从transformer 进入cv后,又有基于transformer改进的自注意力:ViT,DETR等,下面一一介绍。
RAM
paper: https://arxiv.org/abs/1406.6247
github: https://github.com/jlindsey15/RAM
这是谷歌在14年基于RNN提出的注意力机制,用于图像分类,在14年卷积网络刚提出时,受限于硬件资源等因素,对计算量敏感,而当时减少计算量的方法主要有两个:一是减少窗口数量、二是引入注意力。
于是作者将注意力问题看作目标驱动的序列决策问题,使用了一个代理(agent)实现与环境的可视交互。代理每次只会用有限波长宽度的sensor来检测图像中的环境,但不会是全部图像。由于环境也都只是被部分检测到,因此后面还需要agent整合所有信息。在每一步,代理都会得到一个奖励,目的是要最大化代理得到奖励,一种强化学习的机制,因此该模型是典型的硬性注意力模型。
该模型利用RNN结构,并结合LSTM,按照序列决策(sequential decision)的思路,从图像或者视频中以自适应方式不断挑选重要区域并对其进行高分辨率处理,而这些所谓的“重要区域”即为那些能够为视觉任务带来决定性影响的注意力投射区域。
RAM模型对图像的分析过程可以用上图所示“瓢虫看图”的例子做以类比:一只瓢虫趴在一幅图像上看图,它每次只能看到身体下方的一小块图像(即注意力投射区域,如图中白色实线边框所示)。在某一时刻t,瓢虫所在位置为l_t-1(在开始的时候瓢虫趴在一个随机位置l_0),它收集当前所看见图像的信息(即G传感器抽取的类视网膜特征)并将其与位置信息进行综合(即G网络进行的图像-位置特征融合)得到一个信息表示g_t。然后瓢虫将该信息和其脑子中对图像已有的认知信息h_t-1进行再次整合(即RNN单元完成的工作),得到一个对图像新的认知信息h_t。然后,瓢虫按照当前认知信息h_t做出对图像状态的判断a_t,并决定下一个看图位置l_t(如图中黄色实线边框所示,白色虚线边框表示历史看图位置),如果瓢虫判断对了,就给它一个奖励。瓢虫以获得最多奖励为目标在图像上不断前行,在奖励的“诱惑”下,不断在图像上选择重点区域并对图像状态做出更加恰当的判断。
这种网络的优势在于:(1)参数量和计算量都能够通过输入图像的尺寸进行控制(2)RAM能够忽略杂乱图像中的噪声部分(3)能够方便的进行扩展(4)网络对输入图像采样的尺度能够控制,使它可用于不同尺寸的图像。
DRAW
paper:https://arxiv.org/pdf/1502.04623.pdf
github: https://github.com/ericjang/draw
DRAW是15年Google Deepmind 发表在ICML上的一篇文章,用于图像生成领域,该网络用于模仿人眼视觉偏好性的空间注意力机制,基于变分自动编码器(Variational AutoEncoder VAEs),对图像自动生成。
相对于上图左侧一般的变分编码器,右侧会基于前一次的解码器的输出来选取重点区域关注,参与这次的编码过程,解码器的结果也是之前结果的累加。
STN-Net
paper:https://arxiv.org/pdf1506.02025.pdf
github:
tf: https://github.com/kevinzakka/spatial-transformer-network
pytorch:https://github.com/qassemoquab/stnbhwd
STN-Net《Spatial Transformer Networks》是15年NIPS上的文章,提出了空间变换器的概念,STN-Net认为传统的卷积网络如果没有pooling层会要求输入数据空间固定,而pooling的方法过于暴力,可能会导致关键信息无法识别,所以提出了一个空间转换器(Spatial Transformer)的模块,主要作用是找到图片中需要被关注的区域,并对其旋转、缩放,提取出固定大小的区域
。
空间采样器的实现主要分成三个部分:1)局部网络(Localisation Network);2)参数化网格采样( Parameterised Sampling Grid);3)差分图像采样(Differentiable Image Sampling)。
在理解采样器前需要大家对图像的仿射变化、旋转、平移等图像仿射变换有先验知识,会发现对图像做旋转等变换时,实际上即是通过矩阵则可达到:
局部网络是将图像提出特征的feature map作为输入,通过本地网络(卷积,FC等)训练出来的参数θ,在图像类一般是个[2,3]大小的6维变换参数;
参数化网格采样则是将训练的参数将输入的特征图进行变换,这决定了变换前后图像之间的坐标映射关系。
差分图像采样是根据第二步产生的坐标映射关系将输入图像U像素点变换成输出图片V的像素点。这里需要注意的是,提取到输出特征图对应到输入特征的位置可能不一定是整数点,非整数坐标的像素点需要通过双向线性插值提取。
因以上三个网络均为可微的,因此它可以插入到正常的网络中,通过反向传播更新参数,且无需额外的监督信息。
DCN v1/v2
v1: paper:https://arxiv.org/pdf/1703.06211.pdf
github:https://github.com/ msracver/Deformable-ConvNets
v2: paper:https://arxiv.org/pdf/1811.11168.pdf
github:https://github.com/CharlesShang/DCNv2
可变形卷积因为空间域中,会关注感兴趣点的位置,有空间注意力的思想在,具体DCN实现如下:
其核心思想在于,它不认为卷积核应该是规规整整的矩形,其可以是任意形状的,如下图:
GENet
paper:https://arxiv.org/abs/1810.12348
github:https://github.com/hujie-frank/GENet.
受 SENet 启发,Hu 等人设计 GENet 通过在空间域中提供重新校准功能来捕获远程空间上下文信息。GENet 结合了部分收集和激发操作。
第一步,它聚合大邻域的输入特征,并对不同空间位置之间的关系进行建模。
第二步中,它首先使用插值生成与输入特征图大小相同的注意力图。然后通过乘以注意力图中的相应元素来缩放输入特征图中的每个位置。这个过程可以描述为:
在这里,fgather(X)可以采用任何捕捉空间相关性的形式,例如全局平均池化或一系列深度卷积;Interp ( ⋅ ) ,(⋅)表示插值。
Gather-excite 模块是轻量级的,可以像 SE 块一样插入到每个残差单元中。它在抑制噪音的同时强调重要特征。
Non-local Neural Networks
paper:https://arxiv.org/abs/1711.07971
github:https://github.com/facebookresearch/video-nonlocal-net
Non-local Neural networks 是CVPR2018提出的一个自注意力模型,普通的卷积即局部注意力,是33的卷积核,然后在整个图片上移动,non-local是综合一个比较大的搜索范围,并进行加权。作者基于图片滤波中的non-local means 的思想,提出的一个非局部操作算子,非局部操作可以直接计算图片空间中两个位置之间的关系,忽略其空间位置的影响,具体计算公式如下:
其中x为输入信号,在cv中为feature map; i为输出位置,输出位置响应等于所有枚举j对i的响应计算得到;f计算i与j之间的相似度;g计算feature map在j位置上的表示;y通过c(x)进行标准化处理后得到。具体在cv上的计算示例图如下:
文中谈到有多种实现方式,本文中介绍其中一种简单的:
1.首先在输入的特征图进行线性映射(通过11的卷积,压缩通道数),得到θ,φ,g 特征;
2. 通过reshape操作,强行合并上述三个特征除通道外了其他维度,对θ和φ进行举证点乘操作,用于计算特征中的自相关性,得到每个像素对其他像素的关系;
3. 通过对自相关特征进行softmax操作,得到[0,1]的权重,即自注意力系数;
4. 将自注意力系数乘回矩阵g中,并通过1*1卷积,拓展通道数,与输入的特征图相加,得到non-local 模块的输出。
non-local 模块保证了输入和输出尺寸不变,容易嵌入网络架构中,通过直接融合全局信息,计算量和参数量均有一定的增加。non-local 因为时间原因也有一定的局限性:1. 没有考虑到通道注意力;2. 如果特征图很大,则会很耗内存和计算量。
CCNet
paper:https://arxiv.org/pdf/1811.11721.pdf
github:https://github.com/speedinghzl/CCNet
Non-local可以看成是注意力机制下的密集连接的GNN,虽然能够捕获全局的上下文信息,但是其计算复杂度为O(N^2),为了解决复杂度问题,CCNet提出一个更高效的注意力机制——交叉注意力模块,用于语义分割领域,其与Non-local的对比见图1:
如上图可知,与Non-local相比,criss-cross attention module生成attention map只利用了十字交叉路径上的特征,这种操作大大降低了计算复杂度。交叉注意力模块的结构如下图,但是单次的criss-cross attention module只能捕获十字交叉路径上的特征,为了同时用上其他位置特征,作者用了两次criss-cross attention module,第一次相关性计算会将当前像素的信息传递到同行同列的所有位置,第二次计算时之前接受到信息的每个像素又会将再次将信息传递到自己的同行同列。如下二图所示,称为RCCA模块:
这样通过RCCA模块,即可获得全局信息,与Non-local达到相同的目的,但是其复杂度从O(N^2) 变成 O(N^(3/2)).
EMANet
paper:https://arxiv.org/pdf/1907.13426.pdf
github:https://github.com/XiaLiPKU/EMANet
EMANet的提出也是为了解决Non local带来的计算量过于庞大的问题,该论文将注意力机制表述为一种期望最大化方式,并自动估计出一组更紧凑的基,在此基础上计算注意力图。通过对这些基的加权求和,得到的表示是低秩的,并且有效消除来自输入的噪声信息。所提出的期望最大化注意力(EMA)模块对输入方差具有鲁棒性,并且在内存和计算方面也很友好。
SASA
paper:https://arxiv.org/pdf/1906.05909.pdf
github:https://github.com/leaderj1001/Stand-Alone-Self-Attention
主要的贡献是提出使用 “自注意力操作”代替一般的空间卷积操作,以弥补空间卷积无法有效捕捉长距离信息间的关系的不足,同时使用的计算量和参数量更少.目前的self attention平等对待中心像素邻近的其他像素点,没有利用位置信息,因此文中进一步通过用嵌入向量来表示相对位置,把位置信息也添加到了自注意力操作中。
这里作者通过实验也说明了 相对位置信息> 绝对位置信息 > 没有位置信息.
SAN
paper:https://arxiv.org/abs/2004.13621
github:https://github.com/hszhao/SAN
这篇文章发表在CVPR2020。作者提出一种将self-attention机制应用到图像识别领域的方法。作者认为,使用卷积网络进行图像识别任务实际上在实现两个函数:
1.特征聚集(feature aggregation): 即通过卷积核在特征图上进行卷积来融合特征的过程。
2.特征变换(feature transformation): 在卷积完成后进行的一系列线性和非线性变换(比如全连接和激活函数。这一部分通过感知机就能很好地完成。
在以上观点的基础上,作者提出使用self-attention机制来替代卷积作为特征聚集方法。为此,作者考虑两种self-attention形式:pairwise self-attention和patchwise self-attention。用这两种形式的self-attention机制作为网络的basic block提出SAN网络结构。与经典卷积网络ResNet进行对比,SAN网络具有更少参数和运算量,同时在ImageNet数据集上的分类精确度有较大提升。
ViT/DETR
VIT:
paper:https://arxiv.org/pdf/2010.11929.pdf
github:https://github.com/rwightman/pytorch-image-models/blob/main/timm/models/vision_transformer.py
DETR:
paper:https://arxiv.org/pdf/2005.12872.pdf
github:https://github.com/facebookresearch/detr
vision transformer 中的注意力机制为transformer在encode时的多头注意力机制,因为vision transformer 是将transformer从nlp中用于cv中,尽量少的变动transformer ,所以文中提出(key,query, value)三元组捕捉长距离依赖的建模方式同nlp中的self-attention,如下图所示,key和query通过点乘的方式获得相应的注意力权重,最后把得到的权重和value做点乘得到最终的输出。公式如下:
DETR是Facebook在2020年提出,是transformer在目标检测任务上的应用,利用Transformer中attention机制能够有效建模图像中的长程关系(long range dependency),简化目标检测的pipeline,构建端到端的目标检测器。
3.时间注意力机制Temporal Attention
时域注意力机制在cv领域主要考虑有时序信息的领域,如视频领域中的动作识别方向,其注意力机制主要是在时序列中,关注某一时序即某一帧的信息。
TAM
paper:https://arxiv.org/abs/2005.06803v1
github: https://github.com/liu-zhy/temporal-adaptive-module
由于存在拍摄视角变化和摄像机运动等多个因素,视频数据通常表现出较为复杂的时序动态特性,不同视频在时序维度上呈出不同的运动模式。为了解决这个问题,时序自适应模块(TAM)为每个视频生成特定的时序建模核。该算法针对不同视频片段,灵活高效地生成动态时序核,自适应地进行时序信息聚合。整体结构入下图所示:
TAM将时序自适应核的学习过程分解为局部分支和全局分支。全局分支( G )基于全局时序信息生成视频自适应的动态卷积核以聚合时序信息,这种方式的特点是对时序位置不敏感,忽略了局部间的差异性。而局部分支(L )使用带有局部时序视野的 1D 卷积学习视频的局部结构信息,生成对时序位置敏感的重要性权重,以弥补全局分支存在的不足。
GLTR
paper:https://arxiv.org/abs/1908.10049
github:https://github.com/ljn114514/GLTR
这是一篇用于行人ReID领域的一篇论文,作者提出在短期建模,基于当前帧的相邻几帧,能加强当前帧人物在该时间段的外观和运动情况,当任务发生遮挡时,则需要使用长期建模,增加时间跨度。所以论文在融合帧的特征时,短期建模和长期建模一起用上:
在短期建模时,使用了空洞卷积,增加感受野,在这里则是增加对当前帧的相邻几帧一起进行卷积处理,也就是综合相邻几帧的信息来增强当前帧的信息。在长期建模中,则使用的是transformer中的self-attention 机制。transformer的attention计算是通过所有信息与当前信息的关系计算的,也就是相当于基于当前帧与全部帧的关系,将全部帧的信息选择性的给予到当前帧,是一个长期建模的过程。也是变相的将注意力机制用在建模中。
4.通道和空间注意力机制
通道和空间注意力是基于通道注意力和空间注意力机制,将两者有效的结合在一起,让注意力能关注到两者,又称混合注意力机制,如CBAM,BAM,scSE等,同时基于混合注意力机制的一些关注点,如Triplet Attention 关注各种跨维度的相互作用;Coordinate Attention, DANet关注长距离的依赖;RGA 关注关系感知注意力。还有一种混合注意力机制,为3D的attention :Residual attention,SimAM, Strip Pooling, SCNet等。
CBAM
paper:https://arxiv.org/abs/1807.06521
github:https://github.com/luuuyi/CBAM.PyTorch
CBAM (Convolutional Block Attention Module)是SENet的一种拓展,SENet主要基于通道注意力,CBAM是通道注意力和空间注意力融合的注意力机制。
如上图所示,输入一个h* w* c的特征图,通过channel Attention Module 生成通道注意力权重对输入特征图在通道层添加权重,再通过spatial Attention Module 生成空间注意力权重,对特征图在空间层添加权重,输出特征图。
通道注意力实现如下:
输入特征图h* w* c,先分别通过一个全局最大池化和全局平均池化,分别得到一个1* 1* c的特征图,将两个特征图分别送进两个全连接层,得到两个1* 1* c的特征图,将两个特征图相加,通过sigmoid激活到[0,1]范围内的权重,与输入的特征图在通道层相乘,得到输出特征图。这里的思路基本与SENet相同,除了增加一个池化,增加通道注意力的语义丰富性。作者也实验证明了同时使用两个池化比单独使用其中一种效果要好,可以理解最大池化关注图片的显著信息,平均池化关注图片的全局信息,都是有可用性,同理后面的空间注意力机制也使用了两种池化方式。
空间注意力实现如下:
输入特征图为 H x W x C,分别通过一个通道维度的最大池化和平均池化得到两个H x W x 1的特征图,然后将这两个特征图在通道维度拼接起来,得到一个H x W x 2的特征图,然后再经过一个卷积层,降为1个通道,保持H W 不变,输出feature map为H x W x 1,然后再通过Sigmoid函数生成空间权重系数,与输入feature map相乘得到最终feature map。
同时作者实验证明通道注意力放在空间注意力前面效果更好。
class ChannelAttention(nn.Module):
def __init__(self,channel,reduction=16):
super().__init__()
self.maxpool=nn.AdaptiveMaxPool2d(1)
self.avgpool=nn.AdaptiveAvgPool2d(1)
self.se=nn.Sequential(
nn.Conv2d(channel,channel//reduction,1,bias=False),
nn.ReLU(),
nn.Conv2d(channel//reduction,channel,1,bias=False)
)
self.sigmoid=nn.Sigmoid()
def forward(self, x) :
max_result=self.maxpool(x)
avg_result=self.avgpool(x)
max_out=self.se(max_result)
avg_out=self.se(avg_result)
output=self.sigmoid(max_out+avg_out)
return output
class SpatialAttention(nn.Module):
def __init__(self,kernel_size=7):
super().__init__()
self.conv=nn.Conv2d(2,1,kernel_size=kernel_size,padding=kernel_size//2)
self.sigmoid=nn.Sigmoid()
def forward(self, x) :
max_result,_=torch.max(x,dim=1,keepdim=True)
avg_result=torch.mean(x,dim=1,keepdim=True)
result=torch.cat([max_result,avg_result],1)
output=self.conv(result)
output=self.sigmoid(output)
return output
class CBAMBlock(nn.Module):
def __init__(self, channel=512,reduction=16,kernel_size=49):
super().__init__()
self.ca=ChannelAttention(channel=channel,reduction=reduction)
self.sa=SpatialAttention(kernel_size=kernel_size)
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
b, c, _, _ = x.size()
residual=x
out=x*self.ca(x)
out=out*self.sa(out)
return out+residual
BAM
paper:https://arxiv.org/abs/1807.06514v2
github:https://github.com/Jongchan/attention-module
BAM和CBAM几乎相同时间提出,称为瓶颈注意力机制,主要有两个分支。
上面那个分支和SENet基本上相同,相当于通道注意力机制;
下面的分支为空间注意力,先将特征层通过11的卷积将通道压缩到C/r,再经过两个33的空洞卷积,做特征融合,通道数不变,最后通过一个1*1的卷积将通道数变成1。再将通道注意力机制与空间注意力机制生成的特征图融合成和输入相同大小的特征图,再与输入做跳层连接。
class Flatten(nn.Module):
def forward(self,x):
return x.view(x.shape[0],-1)
class ChannelAttention(nn.Module):
def __init__(self,channel,reduction=16,num_layers=3):
super().__init__()
self.avgpool=nn.AdaptiveAvgPool2d(1)
gate_channels=[channel]
gate_channels+=[channel//reduction]*num_layers
gate_channels+=[channel]
self.ca=nn.Sequential()
self.ca.add_module('flatten',Flatten())
for i in range(len(gate_channels)-2):
self.ca.add_module('fc%d'%i,nn.Linear(gate_channels[i],gate_channels[i+1]))
self.ca.add_module('bn%d'%i,nn.BatchNorm1d(gate_channels[i+1]))
self.ca.add_module('relu%d'%i,nn.ReLU())
self.ca.add_module('last_fc',nn.Linear(gate_channels[-2],gate_channels[-1]))
def forward(self, x) :
res=self.avgpool(x)
res=self.ca(res)
res=res.unsqueeze(-1).unsqueeze(-1).expand_as(x)
return res
class SpatialAttention(nn.Module):
def __init__(self,channel,reduction=16,num_layers=3,dia_val=2):
super().__init__()
self.sa=nn.Sequential()
self.sa.add_module('conv_reduce1',nn.Conv2d(kernel_size=1,in_channels=channel,out_channels=channel//reduction))
self.sa.add_module('bn_reduce1',nn.BatchNorm2d(channel//reduction))
self.sa.add_module('relu_reduce1',nn.ReLU())
for i in range(num_layers):
self.sa.add_module('conv_%d'%i,nn.Conv2d(kernel_size=3,in_channels=channel//reduction,out_channels=channel//reduction,padding=1,dilation=dia_val))
self.sa.add_module('bn_%d'%i,nn.BatchNorm2d(channel//reduction))
self.sa.add_module('relu_%d'%i,nn.ReLU())
self.sa.add_module('last_conv',nn.Conv2d(channel//reduction,1,kernel_size=1))
def forward(self, x) :
res=self.sa(x)
res=res.expand_as(x)
return res
class BAMBlock(nn.Module):
def __init__(self, channel=512,reduction=16,dia_val=2):
super().__init__()
self.ca=ChannelAttention(channel=channel,reduction=reduction)
self.sa=SpatialAttention(channel=channel,reduction=reduction,dia_val=dia_val)
self.sigmoid=nn.Sigmoid()
def init_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, x):
b, c, _, _ = x.size()
sa_out=self.sa(x)
ca_out=self.ca(x)
weight=self.sigmoid(sa_out+ca_out)
out=(1+weight)*x
return out
scSE
paper:https://arxiv.org/abs/1808.08127
github:https://github.com/pprp/SimpleCVReproduction/tree/master/Plug-and-play%20module/attention/scSE(unofficial)
这篇文章主要用于语义分割上,对SE模块进行了改进,提出了SE模块的三个变体cSE、sSE、scSE,并通过实验证明了了这样的模块可以增强有意义的特征,抑制无用特征。一开始作者提出了两个模块:sSE模块和cSE模块,分别为通道注意力和空间注意力:
scSE则是将两者并联起来,有点类似于BAM结构:
Triplet Attention
paper:https://arxiv.org/abs/2010.03045
github:https://github.com/landskape-ai/triplet-attention
triplet Attention 也是在CNN中加入进spatial attention和channel attention的一个即插即用模块,文章认为CBAM建立Channel Attention和Spatial Attention的操作是分别进行的,而二者之间可能有联系,应该同时建模Channel Attention和Spatial Attention。
于是文章中的Triplet其实就是从三个角度去建模attention关系:以输入的feature map大小为(b, c, h, w)为例,围绕channel,height,width可以建模三组关系:
channel to height(c,h)
channel to width(c,w)
height to width(h,w)
相对其他Attention Module而言,Triplet Attention近乎无参,效果提升不是很明显。
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 ZPool(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 AttentionGate(nn.Module):
def __init__(self):
super(AttentionGate, self).__init__()
kernel_size = 7
self.compress = ZPool()
self.conv = 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.conv(x_compress)
scale = torch.sigmoid_(x_out)
return x * scale
class TripletAttention(nn.Module):
def __init__(self, no_spatial=False):
super(TripletAttention, self).__init__()
self.cw = AttentionGate()
self.hc = AttentionGate()
self.no_spatial=no_spatial
if not no_spatial:
self.hw = AttentionGate()
def forward(self, x):
x_perm1 = x.permute(0,2,1,3).contiguous()
x_out1 = self.cw(x_perm1)
x_out11 = x_out1.permute(0,2,1,3).contiguous()
x_perm2 = x.permute(0,3,2,1).contiguous()
x_out2 = self.hc(x_perm2)
x_out21 = x_out2.permute(0,3,2,1).contiguous()
if not self.no_spatial:
x_out = self.hw(x)
x_out = 1/3 * (x_out + x_out11 + x_out21)
else:
x_out = 1/2 * (x_out11 + x_out21)
return x_out
RGA
paper:https://arxiv.org/abs/1904.02998
github:https://github.com/microsoft/Relation-Aware-Global-Attention-Networks
这是一篇cvpr2020由微软提出的关系感知全局注意力机制,用于捕获全局信息。区别于常用的获取注意力的方法,作者将特征图中的每个位置上的feature看作一个node,通过强调node间的对称关系来挖掘全局范围的相关性和语义信息,提出了即插即用在残差块后的注意力模块Relation-aware global attention module(RGA),该模块将注意力同时应用与spatial及channel上。
Residual attention
paper:https://arxiv.org/abs/1704.06904
github:https://github.com/fwang91/residual-attention-network
作者提出了Residual Attention Network,主要通过堆叠多个Attention Modules来构造Residual Attention Network。堆叠结构是混合注意力机制的基本应用。因此,不同类型的注意力能够被不同的注意力模块捕获,而直接叠加注意力模块会导致明显的学习性能下降。因此,提出了attention residual learning mechanism来优化几百层的非常深层的Residual Attention Network。
class ResidualAttention(nn.Module):
def __init__(self, channel=512 , num_class=1000,la=0.2):
super().__init__()
self.la=la
self.fc=nn.Conv2d(in_channels=channel,out_channels=num_class,kernel_size=1,stride=1,bias=False)
def forward(self, x):
b,c,h,w=x.shape
y_raw=self.fc(x).flatten(2) #b,num_class,hxw
y_avg=torch.mean(y_raw,dim=2) #b,num_class
y_max=torch.max(y_raw,dim=2)[0] #b,num_class
score=y_avg+self.la*y_max
return score
SimAM
paper:http://proceedings.mlr.press/v139/yang21o/yang21o.pdf
github:https://github.com/ZjjConan/SimAM
作者提出一种概念简单且非常有效的注意力模块。不同于现有的通道/空域注意力模块,该模块无需额外参数为特征图推导出3D注意力权值。同时推导出了能量函数的解析解加速了注意力权值的计算并得到了一种轻量型注意力模块;
class SimAM(torch.nn.Module):
def __init__(self, channels = None, e_lambda = 1e-4):
super(SimAM, self).__init__()
self.activaton = nn.Sigmoid()
self.e_lambda = e_lambda
def __repr__(self):
s = self.__class__.__name__ + '('
s += ('lambda=%f)' % self.e_lambda)
return s
@staticmethod
def get_module_name():
return "simam"
def forward(self, x):
b, c, h, w = x.size()
n = w * h - 1
x_minus_mu_square = (x - x.mean(dim=[2,3], keepdim=True)).pow(2)
y = x_minus_mu_square / (4 * (x_minus_mu_square.sum(dim=[2,3], keepdim=True) / n + self.e_lambda)) + 0.5
return x * self.activaton(y)
SCNet
paper:http://mftp.mmcheng.net/Papers/20cvprSCNet.pdf
github:https://github.com/MCG-NKU/SCNet
在本文中,没有设计复杂的网络体系结构来增强特征表示,而是引入了自校正卷积作为一种有效的方法来帮助卷积网络通过增加每层的基本卷积变换来学习判别表示。类似于分组卷积,它将特定层的卷积核分为多个部分,但不均匀地每个部分中的卷积核以异构方式被利用。具体而言,自校正卷积不是通过均匀地对原始空间中的输入执行所有卷积,而是首先通过下采样将输入转换为低维嵌入, 采用由一个卷积核变换的低维嵌入来校准另一部分中卷积核的卷积变换。得益于这种异构卷积和卷积核间通信,可以有效地扩大每个空间位置的感受野.
5.时域和空间注意力机制
时域和空间注意力是基于时域注意力和空间注意力机制,综合考虑两者一起提出的注意力机制,也称混合注意力机制,主要用于视频领域,如动作识别或者行人ReID中,主要包括区分时域和空间的注意力:RSTAN;联合产生的时域和空间注意力机制:STA;基于成对关系的注意力:STGCN。
RSTAN
paper:https://ieeexplore.ieee.org/document/8123939
github: https://github.com/PancakeAwesome/ran_two_stream_to_recognize_drives(unofficial)
对视频中的动作识别增加软注意力机制,使用多层循环神经网络 (RNN) 和长短期记忆 (LSTM) 单元。
STA
paper: https://ojs.aaai.org//index.php/AAAI/article/view/4841
github: https://github.com/justchenhao/STANet(unofficial)
STA在空间和时间维度上充分利用了一个目标的那些判别部分,从而通过帧间正则化生成一个二维注意力得分矩阵,以测量不同帧中空间部分的重要性。因此,可以根据挖掘的二维注意力得分矩阵引导的加权求和运算生成更稳健的剪辑级特征表示。
class BAM(nn.Module):
""" Basic self-attention module
"""
def __init__(self, in_dim, ds=8, activation=nn.ReLU):
super(BAM, self).__init__()
self.chanel_in = in_dim
self.key_channel = self.chanel_in //8
self.activation = activation
self.ds = ds #
self.pool = nn.AvgPool2d(self.ds)
print('ds: ',ds)
self.query_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.key_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.value_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1)
self.gamma = nn.Parameter(torch.zeros(1))
self.softmax = nn.Softmax(dim=-1) #
def forward(self, input):
"""
inputs :
x : input feature maps( B X C X W X H)
returns :
out : self attention value + input feature
attention: B X N X N (N is Width*Height)
"""
x = self.pool(input)
m_batchsize, C, width, height = x.size()
proj_query = self.query_conv(x).view(m_batchsize, -1, width * height).permute(0, 2, 1) # B X C X (N)/(ds*ds)
proj_key = self.key_conv(x).view(m_batchsize, -1, width * height) # B X C x (*W*H)/(ds*ds)
energy = torch.bmm(proj_query, proj_key) # transpose check
energy = (self.key_channel**-.5) * energy
attention = self.softmax(energy) # BX (N) X (N)/(ds*ds)/(ds*ds)
proj_value = self.value_conv(x).view(m_batchsize, -1, width * height) # B X C X N
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
out = out.view(m_batchsize, C, width, height)
out = F.interpolate(out, [width*self.ds,height*self.ds])
out = out + input
return out
STGCN
paper: https://ieeexplore.ieee.org/document/9156954/
github: https://github.com/justchenhao/STANet(unofficial)
该论文将GCN运用到Person ReID上面来,将图像信息分块作为节点信息;作者提出一个新的时空图卷积网络(STGCN)。STGCN包括两个GCN分支:空间分支、时间分支。空间分支提取人体的结构信息,时间分支从相邻帧中挖掘判别线索。通过联合优化这些分支,模型提取了与外观信息互补的鲁棒时空信息。
参考文章:http://www.360doc6.net/wxarticlenew/1053604427.html