Attentional Feature Fusion 注意力特征融合
Posted 总是提示已注册
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Attentional Feature Fusion 注意力特征融合相关的知识,希望对你有一定的参考价值。
Attentional Feature Fusion 注意力特征融合
最近看到一篇比较不错的特征融合方法,基于注意力机制的
AAF
,与此前的SENet
、SKNet
等很相似,但AFF
性能优于它们,并且适用于更广泛的场景,包括短和长跳连接以及在Inception
层内引起的特征融合。AFF
是由南航提出的注意力特征融合,即插即用!
本篇博客主要参考自知乎作者 OucQxw
,知乎原文地址:https://zhuanlan.zhihu.com/p/424031096
论文下载地址:https://arxiv.org/pdf/2009.14082.pdf
Github代码地址:https://github.com/YimianDai/open-aff
一、Motivation
特征融合是指来自不同层次或分支的特征的组合,是现代神经网络体系结构中无所不在的一部分。它通常通过简单线性的操作(例如:求和或者串联来实现),但这可能不是最佳的选择。本文提出了一个统一的通用方案,即注意力特征融合( AFF
),该方案适用于大多数常见场景,包括短和长跳连接以及在 Inception
层内引起的特征融合。
为了更好地融合语义和尺度不一致的特征,我们提出了多尺度通道注意力模块
( MS-CAM
),该模块解决了融合不同尺度特征时出现的问题。我们还证明了初始特征融合可能会成为瓶颈,并提出了迭代注意力特征融合模块(iAFF
)来缓解此问题。
- 近年发展的
SKNet
和ResNeSt
注意力特征融合存在的问题:
- 场景限制:
SKNet
和ResNeSt
只关注同一层的特征选择,无法做到跨层特征融合。 - 简单的初始集成 :为了将得到的特征提供给注意力模块,
SKNet
通过相加来进行特征融合,而这些特征在规模和语义上可能存在很大的不一致性,对融合权值的质量也有很大的影响,使得模型表现受限。 - 偏向上下文聚合尺度:
SKNet
和ResNeSt
中的融合权值是通过全局通道注意机制生成的,对于分布更全局的信息,该机制更受青睐,但是对于小目标效果就不太好。是否可以通过神经网络动态地融合不同尺度的特征?
- 本文的贡献,针对于上述三个问题,提出以下解决办法:
- 注意特征融合模块(
AFF
),适用于大多数常见场景,包括由short and long skip connections以及在Inception层内引起的特征融合。 - 迭代注意特征融合模块(
IAFF
),将初始特征融合与另一个注意力模块交替集成。 - 引入多尺度通道注意力模块(
MSCAM
),通过尺度不同的两个分支来提取通道注意力。
二、Method
- Multi-scale Channel Attention Module (
MS-CAM
)
MS-CAM 主要是延续 SENet
的想法,再于 CNN 上结合 Local / Global
的特征,并在空间上用 Attention 来 融合多尺度信息 。
MS-CAM
有 2 个较大的不同:
MS-CAM
通过逐点卷积(1x1卷积)来关注通道的尺度问题,而不是大小不同的卷积核,使用点卷积,为了让MS-CAM
尽可能的轻量化。MS-CAM
不是在主干网中,而是在通道注意力模块中局部本地和全局的特征上下文特征。
上图为 MS-CAM
的结构图,X
为输入特征,X'
为融合后的特征,右边两个分支分别表示全局特征的通道注意力和局部特征的通道注意力,局部特征的通道注意力的计算公式 L(X)
如下:
实现的代码如下:
class MS_CAM(nn.Module):
'''
单特征进行通道注意力加权,作用类似SE模块
'''
def __init__(self, channels=64, r=4):
super(MS_CAM, self).__init__()
inter_channels = int(channels // r)
# 局部注意力
self.local_att = nn.Sequential(
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
# 全局注意力
self.global_att = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
xl = self.local_att(x)
xg = self.global_att(x)
xlg = xl + xg
wei = self.sigmoid(xlg)
return x * wei
- Attentional Feature Fusion(
AFF
)
给定两个特征 X,
Y
进行特征融合( Y
代表感受野更大的特征)。
AFF
的计算方法如下:
对输入的两个特征 X
, Y
先做初始特征融合,再将得到的初始特征经过 MS-CAM
模块,经过 sigmod
激活函数,输出值为 0~1 之间,作者希望对 X
、Y
做加权平均,就用 1 减去这组 Fusion weight
,可以作到 Soft selection
,通过训练,让网络确定各自的权重。
实现的代码如下:
class AFF(nn.Module):
'''
多特征融合 AFF
'''
def __init__(self, channels=64, r=4):
super(AFF, self).__init__()
inter_channels = int(channels // r)
# 局部注意力
self.local_att = nn.Sequential(
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
# 全局注意力
self.global_att = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
self.sigmoid = nn.Sigmoid()
def forward(self, x, residual):
xa = x + residual
xl = self.local_att(xa)
xg = self.global_att(xa)
xlg = xl + xg
wei = self.sigmoid(xlg)
xo = x * wei + residual * (1 - wei)
return xo
- iterative Attentional Feature Fusion (
iAFF
)
在注意力特征融合模块中,X
, Y
初始特征的融合仅是简单对应元素相加,然后作为注意力模块的输入会对最终融合权重产生影响。作者认为如果想要对输入的特征图有完整的感知,只有将初始特征融合也采用注意力融合的机制,一种直观的方法是使用另一个 attention
模块来融合输入的特征。
公式跟 AFF
的计算一样,仅仅是多加一层attention。
实现的代码如下:
class iAFF(nn.Module):
'''
多特征融合 iAFF
'''
def __init__(self, channels=64, r=4):
super(iAFF, self).__init__()
inter_channels = int(channels // r)
# 局部注意力
self.local_att = nn.Sequential(
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
# 全局注意力
self.global_att = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
# 第二次局部注意力
self.local_att2 = nn.Sequential(
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
# 第二次全局注意力
self.global_att2 = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(channels, inter_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(inter_channels),
nn.ReLU(inplace=True),
nn.Conv2d(inter_channels, channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(channels),
)
self.sigmoid = nn.Sigmoid()
def forward(self, x, residual):
xa = x + residual
xl = self.local_att(xa)
xg = self.global_att(xa)
xlg = xl + xg
wei = self.sigmoid(xlg)
xi = x * wei + residual * (1 - wei)
xl2 = self.local_att2(xi)
xg2 = self.global_att(xi)
xlg2 = xl2 + xg2
wei2 = self.sigmoid(xlg2)
xo = x * wei2 + residual * (1 - wei2)
return xo
三、Experiments
这里展示部分实验结果,详细的实验结果请参考原论文。
- 为了验证Multi-scale 的作法是否有效,作者设置了Global + Global 和Local + Local两种方法,与Global + Local对比,发现全局+局部的效果还是最优的。
- 在各种主流网络中,使用本论文中提出的特征融合方法,用于短跳连接、长跳连接、同一层的特征融合中,效果均优于之前的模型。
- 不同的图像分类数据集上,在原有的网络模型中加入本文提出的特征融合方法,并与其原模型进行比较,发现准确率和网络的参数大小都得到了不错的性能提升。
[论文理解] Attentional Pooling for Action Recognition
Attentional Pooling for Action Recognition
简介
这是一篇NIPS的文章,文章亮点是对池化进行矩阵表示,使用二阶池的矩阵表示,并将权重矩阵进行低秩分解,从而使分解后的结果能够自底向上和自顶向下的解释,并巧用attention机制来解释,我感觉学到了很多东西,特别是张量分解等矩阵论的知识点。
基础概念
低秩分解
目的:去除冗余并减少模型的权值参数
方法:使用两个K*1的卷积核代替掉一个K*K的卷积核
原理:权值向量主要分布在一些低秩子空间,使用少量的基就可以恢复权值矩阵
数学公式(本文):
[
W in R^{f imes f}那么W矩阵可以分解为W = ab^T其中a、b in R^{f imes 1}]
这样就用两个1*f的矩阵去表示原来的f*f的矩阵,本文中是将矩阵做 rank-1分解,也就是分解后的矩阵a和b的秩为1,当然也需要做多组实验确定分解的秩为多少最合适。
普通池化
普通池化可以用下面的公式来表示(n = 16*16 = 256,是特征宽高乘积,f为特征通道数):
[
score_{pool}(X) = 1^TXw这里X in R^{n imes f}, 1 in R^{n imes 1}, w in R^{f imes 1}写开来就是1^{T}X = egin{bmatrix}1 1 cdots 1end{bmatrix}_{n imes 1}^{T} imes egin{bmatrix}x_{1,1} & cdots & x_{1.f} x_{2,1} & cdots & x_{2,f} vdots & vdots & vdots x_{n,1} & cdots & x_{n,f}end{bmatrix}_{n imes f}
]
可以理解为先对特征进行在空间维度上进行全局求和,得到f个结果,然后再对这些结果利用权值矩阵加权求和就得到最终的pooling结果,pooling的结果为一个scalar。
一般avgpooling可以将该式特殊化,也就是X为n*1的张量,对每个通道执行同样的操作,1和w矩阵都是常值。maxpooling的1矩阵不为全1,最大值对应的那个位置为1.
二阶池
本文提出了二阶池的方法,具体如下:
这里我直接从论文中拷出来了,没有自己手打。
这里文章说到二阶池对fine-grained classification的结果有帮助,然后把W做低秩分解,公式就变成了:
其中利用到了矩阵Tr的特点就不解释了。
那么这样分解有什么好处呢? 我觉得这就是本文的一个精髓,可以自顶向下和自底向上来解释公式,公式的可解释性为本文加分很多。
自底向上解释
我们看到公式(6)里先算的是Xb,这里得到的结果是一个n*1的矩阵,这个矩阵刚好可以看成一个attention map,那么作者对他的解释就是由底层特征到高层特征映射过程中生成的attention map,用于评估位置特征。并且,这里的b是针对每个类别都一样的,所以可以自底向上解释,而a是每个类别要学习一个特定的a,所以a的解释是自顶向下的。
自顶向下解释
如上所说,自顶向下解释主要是对a的解释,从上面的公式其实已经可以自底向上解释了,但是作者又做了一步化简:
这里我们看到公式被化成了(8)式,这样其实更加直观,Xa得到的是自顶向下与类别相关的结果,而Xb得到的则是自底向上的与类别无关的结果,两者做矩阵乘,得到最终的结果。这种分解方法我感觉很奇妙,而且解释性非常好。
拓展-张量分解
详见博客:http://www.xiongfuli.com/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/2016-06/tensor-decomposition-cp.html
CP分解
可以对张量做低秩近似,所以这里也可以做很多工作。
Tucker分解
也是一样的。上面那篇博客讲的很详细。也是可以做低秩近似。
所以这个方向是个很神奇的方向,我觉得后面可以做很多东西。需要深厚的数学功底。
网络结构
一张图带过吧。因为本位是做human pose的,所以网络结构是针对pose的。结构非常简单,用了两种方法,我们看一下method 2吧。
直接映射到17个channel的特征层,前十六个是pose map,用于预测关键点的,最后一个是attention map,这里的attention map是pose map的by-product,也就是说利用pose map去帮助分类,所以attention map再与Xa作用,最终的结果做分类,这样的一个思路。
后面实验就不看了,我也不是做这个的。。
结论
二阶池对局部特征的描述更加丰富。
低秩分解可以用来做attention。
Coding
自己实现一下文章中的method 2吧。
'''
@Descripttion: This is Aoru Xue's demo, which is only for reference.
@version:
@Author: Aoru Xue
@Date: 2019-10-27 13:11:23
@LastEditors: Aoru Xue
@LastEditTime: 2019-10-27 13:18:40
'''
import torch
import torch.nn as nn
from torchsummary import summary
from torch.autograd import Variable
class AttentionalPolling(nn.Module):
def __init__(self):
super(AttentionalPolling, self).__init__()
self.conv = nn.Conv2d(128,16,kernel_size = 1)
self.a = Variable(torch.randn(1,10,128,1))
self.b = Variable(torch.randn(1,128,1))
def forward(self,x):
feat = self.conv(x)
# (64*64,128) @ (128,1) -> (64*64,1)
#print(x.permute(0,2,3,1).view(-1,64*64,128).size())
xb = x.permute(0,2,3,1).contiguous().view(-1,64*64,128) @ self.b
#print(xb.size())
xa = x.permute(0,2,3,1).contiguous().view(-1,1,64*64,128) @ self.a
xa = xa.permute(0,1,3,2).contiguous().view(-1,10,1,4096)
xb = xb.view(-1,1,4096,1)
output = xa @ xb
print(output.size())
return output.view(-1,10)
if __name__ == "__main__":
net = AttentionalPolling()
summary(net,(128,64,64),device = "cpu") # feature X
'''
torch.Size([2, 10, 1, 1])
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 16, 64, 64] 2,064
================================================================
Total params: 2,064
Trainable params: 2,064
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 2.00
Forward/backward pass size (MB): 0.50
Params size (MB): 0.01
Estimated Total Size (MB): 2.51
----------------------------------------------------------------
'''
论文原文:https://arxiv.org/pdf/1711.01467v2.pdf
以上是关于Attentional Feature Fusion 注意力特征融合的主要内容,如果未能解决你的问题,请参考以下文章
[深度学习学习笔记]注意力机制-Attentional mechanism
[论文理解] Attentional Pooling for Action Recognition
Multi-Factors Aware Dual-Attentional Knowledge Tracing(未完成)
Multi-Factors Aware Dual-Attentional Knowledge Tracing(未完成)