赞
踩
多种注意力表格:
大神参考仓库链接: 魔鬼面具
对应 name 就是目录,点击即可跳转到对应学习。
注意:博客尽可能的简单的介绍各个注意力主要的思想和图示,并通过代码的解释和测试来详细的理解各个注意力机制的使用。(内容可能不全,希望各位理解)
全称:Squeeze-and-Excitation 挤压和激励
1、主要思想:想通过全局池化和全链接,形成通道上的注意力。然后从通道域的角度赋予图像不同位置不同的权重,得到更重要的特征信息。
简单来说:将原始特征图H 、W 维度上压缩为为1、1(长条形状),再进行一些全链接层, 得到1 * 1 * C的条形,再和原始的特征图进行通道上的相称,得到最终的注意力特征层。
对应公式具体流程如下:
X
^
=
F
s
c
a
l
e
(
)
(
U
,
F
e
x
(
F
s
q
(
U
)
,
W
)
)
F
s
q
(
U
)
=
A
v
g
P
o
o
l
(
U
)
,
F
e
x
=
M
L
P
.
\hat{X} = F_{scale()}(U, F_{ex}(F_{sq}(U), W)) \\ F_{sq}(U) = AvgPool(U) , \ F_{ex} = MLP.
X^=Fscale()(U,Fex(Fsq(U),W))Fsq(U)=AvgPool(U), Fex=MLP.
值得注意的地方:1、论文中对比了平均池化与最大池化的实验结果,发现平均池化效果稍好一些(后面一些注意力机制,两者都用)。2、SE 的如何防止才可以使得网络的性能最优,论文中也在探讨。
2、模块使用地方:SE模块是一个即插即用的模块,可以在卷积模块之后直接插入SE模块,也可以在残差结构里面添加了SE模块。
3、代码
import numpy as np import torch from torch import nn from torch.nn import init class SEAttention(nn.Module): def __init__(self, channel=512, reduction=16): super().__init__() # 对应全局平均池化 self.avg_pool = nn.AdaptiveAvgPool2d(1) # 两层全连接层,用于获取通道注意力机制,最后用 Sigmoid 激活函数,输出 0~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() y = self.avg_pool(x).view(b, c) y = self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x) if __name__ == '__main__': input=torch.randn(50,512,7,7) se = SEAttention(channel=512,reduction=8) output=se(input) print(output.shape)
全称:Bottleneck Attention Module 瓶颈注意模块
1、主要思想:沿着两个不同的路径(通道和空间)推断注意力映射,通道注意力使得网络关注那个通道上的数据更加重要,空间注意力机制使得网络关注那个位置上的数据更加重要。
简单来说:一个分支将原始特征图H 、W 维度上压缩为为1、1(长条形状),再进行一些全链接层, 得到C * 1 * 1的条形。另一个分支将在通道C上进行压缩,压缩为C/R (R为比例)(拍扁一些),再经过一些卷积层,得到1 * H * W的特征图。将C * 1 * 1 对应乘上1 * H * W, 就变成 C * H * W 的 BAM 注意力。再和原始特征图对应相乘,经过残差层即可。对应图示如下:
对应公式具体流程如下:
M
c
(
F
)
=
B
N
(
M
L
P
(
A
v
g
P
o
o
l
(
F
)
)
)
=
B
N
(
W
1
(
W
0
A
v
g
P
o
o
l
(
F
)
+
b
0
)
+
b
1
)
,
M
s
(
F
)
=
B
N
(
f
3
1
×
1
(
f
2
3
×
3
(
f
1
3
×
3
(
f
0
1
×
1
(
F
)
)
)
)
)
,
M
(
F
)
=
σ
(
M
c
(
F
)
+
M
s
(
F
)
)
,
F
′
=
F
+
F
⊗
M
(
F
)
,
1
、其中,
F
∈
R
C
∗
H
∗
W
,
A
v
g
P
o
o
l
(
F
)
∈
R
C
∗
1
∗
1
,
W
0
∈
R
C
/
r
×
C
,
b
0
∈
R
C
/
r
,
W
1
∈
R
C
×
C
/
r
,
b
1
∈
R
C
.
2
、其中,
f
0
1
×
1
∈
R
C
∗
1
∗
1
,
f
1
3
×
3
∈
R
C
/
r
∗
3
∗
3
,
f
2
3
×
3
∈
R
C
/
r
∗
3
∗
3
,
f
3
1
×
1
∈
R
C
/
r
∗
1
∗
1
,
f
0
1
×
1
∗
C
/
r
个,
f
1
3
×
3
∗
C
/
r
个,
f
2
3
×
3
∗
C
/
r
个,
f
0
1
×
1
∗
1
个,
3
、其中,
M
c
(
F
)
+
M
s
(
F
)
在相加之前都被调整为
R
C
×
H
×
W
大小。
4
、其中,
⊗
表示逐元素乘法。
M_c(F) = BN(MLP(AvgPool(F))) = BN(W_1(W_0AvgPool(F) + b_0) + b_1), \\ M_s(F) = BN(f^{1×1}_3(f^{3×3}_2(f^{3×3}_1(f^{1×1}_0(F))))), \\ M(F) = σ(M_c(F) +M_s(F)), \\ F' = F+F⊗M(F), \\ 1、其中,F \in R^{C*H*W},AvgPool(F) \in R^{C*1*1},W_0 ∈ R^{C/r×C}, b_0 ∈ R^{C/r}, W_1 ∈ R^{C×C/r}, b_1 ∈ R^C. \\ 2、其中,f^{1×1}_0 \in R^{C*1*1},f^{3×3}_1 \in R^{C/r*3*3},f^{3×3}_2 \in R^{C/r*3*3}, f^{1×1}_3 \in R^{C/r*1*1}, \\ f^{1×1}_0 * C/r 个, f^{3×3}_1 * C/r 个,f^{3×3}_2 * C/r 个,f^{1×1}_0 * 1 个, \\ 3、其中, M_c(F) +M_s(F)在相加之前都被调整为R^{C×H×W}大小。 \\ 4、其中,⊗表示逐元素乘法。
Mc(F)=BN(MLP(AvgPool(F)))=BN(W1(W0AvgPool(F)+b0)+b1),Ms(F)=BN(f31×1(f23×3(f13×3(f01×1(F))))),M(F)=σ(Mc(F)+Ms(F)),F′=F+F⊗M(F),1、其中,F∈RC∗H∗W,AvgPool(F)∈RC∗1∗1,W0∈RC/r×C,b0∈RC/r,W1∈RC×C/r,b1∈RC.2、其中,f01×1∈RC∗1∗1,f13×3∈RC/r∗3∗3,f23×3∈RC/r∗3∗3,f31×1∈RC/r∗1∗1,f01×1∗C/r个,f13×3∗C/r个,f23×3∗C/r个,f01×1∗1个,3、其中,Mc(F)+Ms(F)在相加之前都被调整为RC×H×W大小。4、其中,⊗表示逐元素乘法。
值得注意的点是:模块有两个超参数:膨胀值 d 和收缩比 r。膨胀值决定了接受域的大小,有助于空间分支的上下文信息聚集。收缩比控制着两个注意力分支的能力和开销。 通过实验验证,设 d = 4, r = 16。
2、模块使用地方:将模块放在模型的每个瓶颈处(特征映射产生降采样的地方),构建一个具有多个参数的分层注意,可以与任何前馈模型以端到端方式进行训练。
3、代码
import numpy as np import torch from torch import nn from torch.nn import init def autopad(k, p=None, d=1): # kernel, padding, dilation """Pad to 'same' shape outputs.""" if d > 1: k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size if p is None: p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad return p 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__() # 添加通道注意力机制的通道数,方便后面的全连接层 gate_channels = [channel] gate_channels += [channel // reduction] * num_layers gate_channels += [channel] # 全局平均池化,用于降维为 B * C * 1 * 1 self.avgpool = nn.AdaptiveAvgPool2d(1) self.ca = nn.Sequential() # 展平,用于全连接层 B * C * 1 * 1 -> B * C 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()) # 最后的全连接层,用于恢复通道数为 B * C. 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) # unsqueeze(-1).unsqueeze(-1) 的作用是为了将 B * C 转换为 B * C * 1 * 1,方便后面的广播。 # expand_as(x) 的作用是为了将 B * C * 1 * 1 广播为 B * C * H * W 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() # 最开始的 1x1 卷积,用于降维为原来的 1/r 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()) # 连续的 3x3 的空洞卷积, 用于捕捉空间信息 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=autopad(3, None, dia_val), dilation=dia_val)) self.sa.add_module('bn_%d' % i, nn.BatchNorm2d(channel // reduction)) self.sa.add_module('relu_%d' % i, nn.ReLU()) # 最后的 1x1 卷积,用于恢复通道数 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 if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) bam = BAMBlock(channel=512, reduction=16, dia_val=2) output = bam(input) print(output.shape)
注意:和BAM的不一样,BAM模块是放在网络的每个瓶颈处,CBAM放在每个卷积块上。
全称:Convolutional Block Attention Module 卷积块注意力模块
1、主要思想:沿着一条路径路径推断注意力映射,先执行通道注意力模块,和原始特征图相乘之后,再执行空间注意力模块,再和原始特征层相乘。通道注意力使得网络关注那个通道上的数据更加重要,空间注意力机制使得网络关注那个位置上的数据更加重要。
简单来说,
对应公式流程如下:
F
′
=
M
c
(
F
)
⊗
F
,
F
′
′
=
M
s
(
F
′
)
⊗
F
′
,
其中,
F
、
F
′
、
F
′
′
∈
R
C
×
H
×
W
,
M
c
(
F
)
∈
R
1
×
H
×
W
,
M
s
(
F
′
)
∈
R
C
×
1
×
1
,
F' =M_c(F) ⊗F, \\ F'' =M_s(F') ⊗F', \\ 其中, F、F'、F'' \in R^{C×H×W}, M_c(F) \in R^{1×H×W}, M_s(F') \in R^{C×1×1},
F′=Mc(F)⊗F,F′′=Ms(F′)⊗F′,其中,F、F′、F′′∈RC×H×W,Mc(F)∈R1×H×W,Ms(F′)∈RC×1×1,
其中⊗表示基于元素的乘法。在乘法过程中,注意值被广播(复制):通道注意值沿着空间维度广播,反之亦然。F”是最终的精炼输出。
细化两个注意力模块:
1、通道注意力模块:在 H * W 的维度上,利用平均池化和最大池化操作聚合特征映射的空间信息,生成两种不同的空间上下文描述符,经过共享MLP 之后,再相加经过 sigmoid,得到最终的通道注意力机制。(SE 注意力中只要的全局平均池化,这里全都要)
也就是说:由于feature map的每个通道都被认为是一个特征检测器,所以通道的注意力集中在给定一个输入图像的“什么”是有意义的。
2、空间注意力机制:在 C 的维度上,分别得到全局平均池化和平均池化的结果,进行叠加,为B * 2 * H * W,再经过卷积提取、压缩一些信息,通过 sigmoid 之后得到 空间注意力机制。(和 BAM 不一样的地方,没有使用 1 * 1 的卷积来降维,而是采用了 池化的方式)
对应公式流程:
通道注意力
M
c
(
F
)
=
σ
(
M
L
P
(
A
v
g
P
o
o
l
(
F
)
)
+
M
L
P
(
M
a
x
P
o
o
l
(
F
)
)
)
=
σ
(
W
1
(
W
0
(
F
a
v
g
c
)
)
+
W
1
(
W
0
(
F
m
a
x
c
)
)
)
,
其中,
σ
为
s
i
g
m
o
i
d
函数,
W
0
∈
R
C
/
r
×
C
,
W
1
∈
R
C
×
C
/
r
,
空间注意力
M
s
(
F
)
=
σ
(
f
7
×
7
(
[
A
v
g
P
o
o
l
(
F
)
;
M
a
x
P
o
o
l
(
F
)
]
)
)
=
σ
(
f
7
×
7
(
[
F
a
v
g
s
;
F
m
a
x
s
]
)
)
,
其中,其中
σ
为
s
i
g
m
o
i
d
函数,
f
7
×
7
为为
7
×
7
的卷积。
通道注意力\\\ M_c(F) = σ(MLP(AvgPool(F)) +MLP(MaxPool(F))) \\ = σ(W_1(W_0(F^c_{avg})) +W_1(W_0(F^c_{max}))), \\ 其中,σ为sigmoid函数,W_0∈R^{C/r×C}, W_1∈R^{C×C/r},\\ 空间注意力\\ M_s(F) = σ(f^{7×7}([AvgPool(F);MaxPool(F)])) \\ = σ(f^{7×7}([F^s_{avg}; F^s_{max}])), \\ 其中,其中σ为sigmoid函数,f^{7×7}为为7×7的卷积。
通道注意力 Mc(F)=σ(MLP(AvgPool(F))+MLP(MaxPool(F)))=σ(W1(W0(Favgc))+W1(W0(Fmaxc))),其中,σ为sigmoid函数,W0∈RC/r×C,W1∈RC×C/r,空间注意力Ms(F)=σ(f7×7([AvgPool(F);MaxPool(F)]))=σ(f7×7([Favgs;Fmaxs])),其中,其中σ为sigmoid函数,f7×7为为7×7的卷积。
值得注意的是:
1、空间注意力的时候,使用的为 7 * 7 的卷积,r 为 16 。
2、在实际实现通道注意力的时候,因为 MLP 是共享的,我们输入有两个,所以这里可以使用 1 * 1 的卷积来实现 MLP 的效果,但是可以很方便的实现参数共享。其次为了减少参数开销,隐藏的激活大小设置为
R
C
/
r
×
1
×
1
R^{C/r×1×1}
RC/r×1×1,其中r是减少率。
3、论文实验证实,同时使用平均池和最大池特性这两个特性比单独使用它们大大提高了网络的表示能力。
4、注意模块的安排。给定一个输入图像,两个注意模块,通道和空间,计算互补注意,分别关注“什么”和“在哪里”。考虑到这一点,两个模块可以以并行或顺序的方式放置。我们发现序列排列比并行排列有更好的结果。对于顺序过程的排列,我们的实验结果表明,信道优先顺序略好于空间优先顺序。
5、在VOC数据集上,作者将SE和CBAM放在每个分类器的前面,在预测之前细化由上采样的全局特征和相应的局部特征组成的最终特征,强制模型自适应地只选择有意义的特征。
2、模块使用地方:CBAM放在每个卷积块上,可以使用在网络的任何地方。
下面的方法是在残差结构内部使用:
3、代码
import numpy as np import torch from torch import nn from torch.nn import init class ChannelAttention(nn.Module): def __init__(self, channel, reduction=16): super().__init__() # 两种全局池化方式 + 共享的两层全连接层(1 * 1 卷积相当于全连接层) 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): # 1 代表通道维度,输入为 B * C * H * W 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=7): 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() out = x * self.ca(x) out = out * self.sa(out) return out if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) kernel_size = input.shape[2] cbam = CBAMBlock(channel=512, reduction=16, kernel_size=kernel_size) output = cbam(input) print(output.shape)
全称:Selective Kernel Attention
背景提要:感受野应该是随着图片的物体或者所需要注意对象的大小的变化而变化的,但是以往的注意力机制,忽略了这一点。inception 网络中使用多个不同的卷积核也是为了使用不同感受野的卷积核。所以网络在 inception 的基础上,加上了合并通道的自选择注意力机制。
1、主要思想:设计了一种称为选择核(SK)单元的构建块,利用这些分支中的信息指导Softmax注意力对不同核大小的多个分支进行融合,对这些分支的关注不同,融合神经元的有效感受野大小也不同。
也就是让网络自己选择,是需要
3
∗
3
3*3
3∗3 的卷积结果呢,还是需要
5
∗
5
5*5
5∗5 的卷积结果呢,还是需要
7
∗
7
7*7
7∗7 的卷积结果呢,还是…。
论文里面仅仅是示例,可以自己增加卷积的大小的那一条分支。
图示如下:
思想步骤解释:
为了使神经元能够自适应地调整其射频大小,我们提出了一种自动选择操作,即在不同核大小地多个核之间进行选择性核卷积。我们通过Split、Fuse和Select来实现SK卷积,如上图所示,我们以分裂两个特征为例。
值得注意的是:作者在使用卷积的时候的,说到了可以使用分组卷积等技巧。
2、模块使用地方:
3、代码
import numpy as np import torch from torch import nn from torch.nn import init from collections import OrderedDict class SKAttention(nn.Module): def __init__(self, channel=512, kernels=[1, 3, 5, 7], reduction=16, group=1, L=32): super().__init__() self.d = max(L, channel // reduction) self.convs = nn.ModuleList([]) for k in kernels: self.convs.append( nn.Sequential(OrderedDict([ ('conv', nn.Conv2d(channel, channel, kernel_size=k, padding=k // 2, groups=group)), ('bn', nn.BatchNorm2d(channel)), ('relu', nn.ReLU()) ])) ) self.fc = nn.Linear(channel, self.d) self.fcs = nn.ModuleList([]) for i in range(len(kernels)): self.fcs.append(nn.Linear(self.d, channel)) self.softmax = nn.Softmax(dim=0) def forward(self, x): bs, c, _, _ = x.size() conv_outs = [] ### split for conv in self.convs: conv_outs.append(conv(x)) feats = torch.stack(conv_outs, 0) # k,bs,channel,h,w ### fuse U = sum(conv_outs) # bs,c,h,w ### reduction channel S = U.mean(-1).mean(-1) # bs,c Z = self.fc(S) # bs,d ### calculate attention weight weights = [] for fc in self.fcs: weight = fc(Z) weights.append(weight.view(bs, c, 1, 1)) # bs,channel attention_weughts = torch.stack(weights, 0) # k,bs,channel,1,1 attention_weughts = self.softmax(attention_weughts) # k,bs,channel,1,1 ### fuse V = (attention_weughts * feats).sum(0) return V if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) se = SKAttention(channel=512, reduction=8) output = se(input) print(output.shape)
全称:Contextual Transformer Attention 上下文自注意力
背景提要:传统的用于视觉任务的transformer结构大多是利用 1 ∗ 1 1 * 1 1∗1 卷积得到 Q u e r y 、 K e y 、 V a l u e Query 、Key 、 Value Query、Key、Value,,然后通过简单的相乘,得到注意力矩阵,这种构造方法并并没有考虑到 K e y Key Key 矩阵相邻位置之间丰富的上下文.( 1 ∗ 1 1 * 1 1∗1 矩阵没有空间上的上下文)
1、主要思想:首先通过3×3卷积上下文编码得到 k e y key key,得到静态的上下文表示。我们进一步将编码后的 K e y Key Key 与原始特征图上的 Q u e r y Query Query 连接起来,通过两个连续的 1 ∗ 1 1 * 1 1∗1 卷积学习动态多头注意矩阵 W e i g h t s Weights Weights。对于 V a l u e Value Value,我们通过 1 ∗ 1 1 * 1 1∗1 的卷积获得,然后将学习到的注意矩阵 W e i g h t s Weights Weights 与输入值 V a l u e s Values Values 相乘,实现输入的动态上下文表示。最后将静态和动态语境表示的相加融合作为输出。
简单图示如下:
细节图示对比如下:
上图(a)是作者介绍的传统的局部多头自注意力机制, 这里插播一下传统的局部多头自注意力机制的计算过程:
假设:输入: X ∈ R H ∗ W ∗ C X \in R^{H *W *C} X∈RH∗W∗C, 有 C h C_h Ch 个注意力头, 每个特征点和 k ∗ k k * k k∗k 范围的点做注意力。
注意,论文中 ⊗ ⊗ ⊗ 被定义为:每个查询与空间中局部 k×k 网格内相应键之间的关系。我这里暂时理解为 每个特征点的描述子为 C k C_k Ck 维度,对应位置相乘为 C k ∗ C k C_k * C_k Ck∗Ck,然后加在一起。
上图(b)即为作者提出的Cot模块的实现过程:
值得注意的是:
1、
K
1
K^1
K1 和
K
2
K^2
K2 的融合,在代码里作者采用的是SE注意力模块进行特征的融合,也可以使用相加的方式。
2、
W
θ
W_θ
Wθ带ReLU激活函数,
W
δ
W_δ
Wδ 不带激活函数.
2、模块使用地方:
作者设计的CoT模块是一个统一的 self-attention 块,可以直接用来代替
3
∗
3
3* 3
3∗3卷积。也就是只要是
3
∗
3
3* 3
3∗3卷积 出现的地方,你都可以替换。
3、代码
import torch from torch import nn from torch.nn import init from torch.nn import functional as F class CoTAttention(nn.Module): def __init__(self, dim=512, kernel_size=3): super().__init__() self.dim = dim self.kernel_size = kernel_size # 用来生成静态上下文 K^1 self.key_embed = nn.Sequential( nn.Conv2d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=4, bias=False), nn.BatchNorm2d(dim), nn.ReLU() ) # 用来生成 V self.value_embed = nn.Sequential( nn.Conv2d(dim, dim, 1, bias=False), nn.BatchNorm2d(dim) ) # 用来对 K^1 + Q 进行注意力计算,也就是使用 1 * 1的卷积来计算注意力 # 这里和传统的自注意力采用q和k相乘不一样,这里采用q和k相加,然后再计算注意力 factor = 4 self.attention_embed = nn.Sequential( nn.Conv2d(2 * dim, 2 * dim // factor, 1, bias=False), nn.BatchNorm2d(2 * dim // factor), nn.ReLU(), nn.Conv2d(2 * dim // factor, kernel_size * kernel_size * dim, 1) ) 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): bs, c, h, w = x.shape k1 = self.key_embed(x) # bs,c,h,w v = self.value_embed(x).view(bs, c, -1) # bs,c,h*w y = torch.cat([k1, x], dim=1) # bs,2c,h,w att = self.attention_embed(y) # bs,c*k*k,h,w att = att.reshape(bs, c, self.kernel_size * self.kernel_size, h, w) att = att.mean(2, keepdim=False).view(bs, c, -1) # bs,c,h*w k2 = F.softmax(att, dim=-1) * v k2 = k2.view(bs, c, h, w) return k1 + k2 if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) cot = CoTAttention(dim=512, kernel_size=3) output = cot(input) print(output.shape)
全称:MobileViT 这个轻量型网络中所提出来的概念。
1、主要思想:使用CNN和transformer混合的方式,使用CNN提取局部特征,使用transformer提取全局特征。理论上网络的感受野为H*W,即全感受野。
具体来说,我们引入了 MobileViT 块,它可以有效地在张量中编码局部和全局信息。与 ViT 及其变体(带或不带卷积)不同,MobileViT 提出了学习全局表示的不同视角。标准卷积涉及三个操作:展开、局部处理和折叠。 MobileViT 块用使用转换器的全局处理取代了卷积中的局部处理。这使得 MobileViT 块具有类似 CNN 和 ViT 的属性,这有助于它用更少的参数和简单的训练方法(例如,基本增强)学习更好的表示.
图示如下:
所提出模型的优点:1、 更好的性能。2、泛化能力强。3、鲁棒性强
值得注意的是:对transformer的输入保留了维度patch,每个patch包含所有像素的位置顺序。这使得encoder的输入包含了每个patch的顺序以及每个patch中所有像素的顺序。
2、模块使用地方:
3、代码
from torch import nn import torch from einops import rearrange class PreNorm(nn.Module): def __init__(self, dim, fn): super().__init__() self.ln = nn.LayerNorm(dim) self.fn = fn def forward(self, x, **kwargs): return self.fn(self.ln(x), **kwargs) class FeedForward(nn.Module): def __init__(self, dim, mlp_dim, dropout): super().__init__() self.net = nn.Sequential( nn.Linear(dim, mlp_dim), nn.SiLU(), nn.Dropout(dropout), nn.Linear(mlp_dim, dim), nn.Dropout(dropout) ) def forward(self, x): return self.net(x) class Attention(nn.Module): def __init__(self, dim, heads, head_dim, dropout): super().__init__() inner_dim = heads * head_dim project_out = not (heads == 1 and head_dim == dim) self.heads = heads self.scale = head_dim ** -0.5 self.attend = nn.Softmax(dim=-1) self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False) self.to_out = nn.Sequential( nn.Linear(inner_dim, dim), nn.Dropout(dropout) ) if project_out else nn.Identity() def forward(self, x): qkv = self.to_qkv(x).chunk(3, dim=-1) q, k, v = map(lambda t: rearrange(t, 'b p n (h d) -> b p h n d', h=self.heads), qkv) dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale attn = self.attend(dots) out = torch.matmul(attn, v) out = rearrange(out, 'b p h n d -> b p n (h d)') return self.to_out(out) class Transformer(nn.Module): def __init__(self, dim, depth, heads, head_dim, mlp_dim, dropout=0.): super().__init__() self.layers = nn.ModuleList([]) for _ in range(depth): self.layers.append(nn.ModuleList([ PreNorm(dim, Attention(dim, heads, head_dim, dropout)), PreNorm(dim, FeedForward(dim, mlp_dim, dropout)) ])) def forward(self, x): out = x for att, ffn in self.layers: out = out + att(out) out = out + ffn(out) return out class MobileViTAttention(nn.Module): def __init__(self, in_channel=3, dim=512, kernel_size=3, patch_size=7): super().__init__() self.ph, self.pw = patch_size, patch_size self.conv1 = nn.Conv2d(in_channel, in_channel, kernel_size=kernel_size, padding=kernel_size // 2) self.conv2 = nn.Conv2d(in_channel, dim, kernel_size=1) self.trans = Transformer(dim=dim, depth=3, heads=8, head_dim=64, mlp_dim=1024) self.conv3 = nn.Conv2d(dim, in_channel, kernel_size=1) self.conv4 = nn.Conv2d(2 * in_channel, in_channel, kernel_size=kernel_size, padding=kernel_size // 2) def forward(self, x): y = x.clone() # bs,c,h,w ## Local Representation y = self.conv2(self.conv1(x)) # bs,dim,h,w ## Global Representation _, _, h, w = y.shape y = rearrange(y, 'bs dim (nh ph) (nw pw) -> bs (ph pw) (nh nw) dim', ph=self.ph, pw=self.pw) # bs,h,w,dim y = self.trans(y) y = rearrange(y, 'bs (ph pw) (nh nw) dim -> bs dim (nh ph) (nw pw)', ph=self.ph, pw=self.pw, nh=h // self.ph, nw=w // self.pw) # bs,dim,h,w ## Fusion y = self.conv3(y) # bs,dim,h,w y = torch.cat([x, y], 1) # bs,2*dim,h,w y = self.conv4(y) # bs,c,h,w return y if __name__ == '__main__': m = MobileViTAttention(in_channel=512) input = torch.randn(1, 512, 49, 49) output = m(input) print(output.shape)
背景提要:注意力机制使神经网络能够准确地关注输入的所有相关元素,已成为提高深度神经网络性能的重要组成部分。计算机视觉研究中广泛使用的注意力机制主要有两种:空间注意力和通道注意力,其目的分别是捕获像素级的成对关系和通道依赖性。虽然将它们融合在一起可能会比它们单独的实现获得更好的性能,但它不可避免地会增加计算开销。
1、主要思想:它采用打乱组合单元来有效地结合两种类型的注意力机制。具体来说,SA 首先将通道维度分组为多个子特征,然后并行处理它们。然后,对于每个子特征,SA 利用洗牌单元来描述空间和通道维度上的特征依赖性。之后,所有子特征被聚合,并采用“通道洗牌”算子来实现不同子特征之间的信息通信。
图示如下:
思想步骤解释:
输入
X
∈
R
C
∗
H
∗
W
X \in R^{C*H*W}
X∈RC∗H∗W
第一步:先将输入划分
g
g
g 个组,接下来将对不同的组进行处理。
X
=
[
X
1
,
⋅
⋅
⋅
,
X
G
]
,
X
k
∈
R
C
/
G
×
H
×
W
,
X = [X_1, · · · ,X_G],X_k ∈ R^{C/G×H×W},
X=[X1,⋅⋅⋅,XG],Xk∈RC/G×H×W,
第二步:再将每个分组内部划分为 两个小组。
X
k
1
,
X
k
2
∈
R
C
/
2
G
×
H
×
W
X_{k1}, X_{k2} ∈ R^{C/2G×H×W}
Xk1,Xk2∈RC/2G×H×W
一个分支用于利用通道之间的相互关系来生成通道注意图,而另一个分支则用于利用特征的空间间关系来生成空间注意图,从而模型可以关注“什么”和“哪里”是有意义的。(类似于每个组内做了 BAM的结构)
第三步:分支内获得通道注意力。
先 全局平均池化
s
=
F
g
p
(
X
k
1
)
=
1
H
×
W
∑
i
=
1
H
∑
j
=
1
W
X
k
1
(
i
,
j
)
,
s=\mathcal{F}_{gp}(X_{k1})=\frac{1}{H\times W}\sum_{i=1}^{H}\sum_{j=1}^{W}X_{k1}(i,j),
s=Fgp(Xk1)=H×W1i=1∑Hj=1∑WXk1(i,j),
其中,
s
∈
R
C
/
2
G
×
1
×
1
s ∈ R^{C/2G×1×1}
s∈RC/2G×1×1,
再 通过 softmax 形成一个门控机制,加在输出上。
X
k
1
′
=
σ
(
F
c
(
s
)
)
⋅
X
k
1
=
σ
(
W
1
s
+
b
1
)
⋅
X
k
1
,
X'_{k1}=\sigma(\mathcal{F}_{c}(s))\cdot X_{k1}=\sigma(W_{1}s+b_{1})\cdot X_{k1},
Xk1′=σ(Fc(s))⋅Xk1=σ(W1s+b1)⋅Xk1,
其中,
W
1
a
n
d
b
1
∈
R
C
/
2
G
×
1
×
1
W_1 \ \ and \ \ b_1 ∈ R^{C/2G×1×1}
W1 and b1∈RC/2G×1×1
第四步:分支内获得空间注意力。
X
k
2
′
=
σ
(
W
2
⋅
G
N
(
X
k
2
)
+
b
2
)
⋅
X
k
2
X'_{k2}=\sigma(W_2\cdot GN(X_{k2})+b_2)\cdot X_{k2}
Xk2′=σ(W2⋅GN(Xk2)+b2)⋅Xk2
其中,
G
N
GN
GN为 Group Norm,
W
2
a
n
d
b
2
∈
R
C
/
2
G
×
1
×
1
W_2 \ \ and \ \ b_2 ∈ R^{C/2G×1×1}
W2 and b2∈RC/2G×1×1,和上面不一样的是卷积的方式不一样。
将组内结果进行拼接。
X
k
′
=
[
X
k
1
′
,
X
k
2
′
]
∈
R
C
/
G
×
H
×
W
.
X'_{k} = [X_{k1}^{\prime},X_{k2}^{\prime}]\in\mathbb{R}^{C/G\times H\times W}.
Xk′=[Xk1′,Xk2′]∈RC/G×H×W.
将所有组的结果进行拼接,并进行通道上的洗牌。
2、模块使用地方:是一个即插即用的模块,放在哪里都行
3、代码
import numpy as np import torch from torch import nn from torch.nn import init from torch.nn.parameter import Parameter class ShuffleAttention(nn.Module): def __init__(self, channel=512, reduction=16, G=8): super().__init__() self.G = G self.channel = channel self.avg_pool = nn.AdaptiveAvgPool2d(1) self.gn = nn.GroupNorm(channel // (2 * G), channel // (2 * G)) self.cweight = Parameter(torch.zeros(1, channel // (2 * G), 1, 1)) self.cbias = Parameter(torch.ones(1, channel // (2 * G), 1, 1)) self.sweight = Parameter(torch.zeros(1, channel // (2 * G), 1, 1)) self.sbias = Parameter(torch.ones(1, channel // (2 * G), 1, 1)) 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) @staticmethod def channel_shuffle(x, groups): b, c, h, w = x.shape x = x.reshape(b, groups, -1, h, w) x = x.permute(0, 2, 1, 3, 4) # flatten x = x.reshape(b, -1, h, w) return x def forward(self, x): b, c, h, w = x.size() # group into subfeatures x = x.view(b * self.G, -1, h, w) # bs*G,c//G,h,w # channel_split x_0, x_1 = x.chunk(2, dim=1) # bs*G,c//(2*G),h,w # channel attention x_channel = self.avg_pool(x_0) # bs*G,c//(2*G),1,1 x_channel = self.cweight * x_channel + self.cbias # bs*G,c//(2*G),1,1 x_channel = x_0 * self.sigmoid(x_channel) # spatial attention x_spatial = self.gn(x_1) # bs*G,c//(2*G),h,w x_spatial = self.sweight * x_spatial + self.sbias # bs*G,c//(2*G),h,w x_spatial = x_1 * self.sigmoid(x_spatial) # bs*G,c//(2*G),h,w # concatenate along channel axis out = torch.cat([x_channel, x_spatial], dim=1) # bs*G,c//G,h,w out = out.contiguous().view(b, -1, h, w) # channel shuffle out = self.channel_shuffle(out, 2) return out if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) se = ShuffleAttention(channel=512, G=8) output = se(input) print(output.shape)
图示如下:
3、代码
import numpy as np import torch from torch import nn from torch.nn import init def spatial_shift1(x): b, w, h, c = x.size() x[:, 1:, :, :c // 4] = x[:, :w - 1, :, :c // 4] x[:, :w - 1, :, c // 4:c // 2] = x[:, 1:, :, c // 4:c // 2] x[:, :, 1:, c // 2:c * 3 // 4] = x[:, :, :h - 1, c // 2:c * 3 // 4] x[:, :, :h - 1, 3 * c // 4:] = x[:, :, 1:, 3 * c // 4:] return x def spatial_shift2(x): b, w, h, c = x.size() x[:, :, 1:, :c // 4] = x[:, :, :h - 1, :c // 4] x[:, :, :h - 1, c // 4:c // 2] = x[:, :, 1:, c // 4:c // 2] x[:, 1:, :, c // 2:c * 3 // 4] = x[:, :w - 1, :, c // 2:c * 3 // 4] x[:, :w - 1, :, 3 * c // 4:] = x[:, 1:, :, 3 * c // 4:] return x class SplitAttention(nn.Module): def __init__(self, channel=512, k=3): super().__init__() self.channel = channel self.k = k self.mlp1 = nn.Linear(channel, channel, bias=False) self.gelu = nn.GELU() self.mlp2 = nn.Linear(channel, channel * k, bias=False) self.softmax = nn.Softmax(1) def forward(self, x_all): b, k, h, w, c = x_all.shape x_all = x_all.reshape(b, k, -1, c) # bs,k,n,c a = torch.sum(torch.sum(x_all, 1), 1) # bs,c hat_a = self.mlp2(self.gelu(self.mlp1(a))) # bs,kc hat_a = hat_a.reshape(b, self.k, c) # bs,k,c bar_a = self.softmax(hat_a) # bs,k,c attention = bar_a.unsqueeze(-2) # #bs,k,1,c out = attention * x_all # #bs,k,n,c out = torch.sum(out, 1).reshape(b, h, w, c) return out class S2Attention(nn.Module): def __init__(self, channels=512): super().__init__() self.mlp1 = nn.Linear(channels, channels * 3) self.mlp2 = nn.Linear(channels, channels) self.split_attention = SplitAttention() def forward(self, x): b, c, w, h = x.size() x = x.permute(0, 2, 3, 1) x = self.mlp1(x) x1 = spatial_shift1(x[:, :, :, :c]) x2 = spatial_shift2(x[:, :, :, c:c * 2]) x3 = x[:, :, :, c * 2:] x_all = torch.stack([x1, x2, x3], 1) a = self.split_attention(x_all) x = self.mlp2(a) x = x.permute(0, 3, 1, 2) return x if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) s2att = S2Attention(channels=512) output = s2att(input) print(output.shape)
论文中所提ParNet以RepVGG的模块为出发点,同时提出了针对非深度网络设计的SSE模块构建了一种新型的模块RepVGG-SSE 。所提方案凭借非常浅的结构取得了非常高的性能,比如:ImageNet的80.7% ,CIFAR10的96%,CIFAR100的81%,MS-COCO的48%。此外,作者还分析了该结构的缩放规则并说明了如何不改变网络提升提升性能。最后,作者还提供了一份证明:非深度网络如何用于构建低延迟识别系统。
1、主要思想:VGG 网咯是比较难以训练的,非深度网络的一个挑战在于:3x3 卷积的感受野非常有限。为解决该问题,在SE注意力基础上构建了一个 Skip-Squeeze-Excitation (SSE)。由于常规的SE会提升网络深度,因此,常规SE并不适用于本文所提出的架构。
网络图示和模块图示如下:
值得注意的是:对于大尺度数据集而言,非深度网络可能不具备足够的非线性能力,进而限制其表达能力。因此,文章采用SiLU替代ReLU 。
3、代码
import numpy as np import torch from torch import nn from torch.nn import init class ParNetAttention(nn.Module): def __init__(self, channel=512): super().__init__() self.sse = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channel, channel, kernel_size=1), nn.Sigmoid() ) self.conv1x1 = nn.Sequential( nn.Conv2d(channel, channel, kernel_size=1), nn.BatchNorm2d(channel) ) self.conv3x3 = nn.Sequential( nn.Conv2d(channel, channel, kernel_size=3, padding=1), nn.BatchNorm2d(channel) ) self.silu = nn.SiLU() def forward(self, x): b, c, _, _ = x.size() x1 = self.conv1x1(x) x2 = self.conv3x3(x) x3 = self.sse(x) * x y = self.silu(x1 + x2 + x3) return y if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) pna = ParNetAttention(channel=512) output = pna(input) print(output.shape)
全称:Coordinate Attention
背景提要:注意力机制用来告诉一个模型“what”和“where”,已经被广泛研究,并被广泛用于提高深度神经网络的性能。但是移动端的发展明显要慢于大型网络的发展。因为移动端的网络对计算开销是有限制的,对网络中添加计算开销大的注意力机制,移动端的网络是负担不起的。在本文中提出了一种新的高效的注意机制,通过在通道注意中嵌入位置信息,使移动端网络模型能够在避免产生大量计算开销的同时增大感受野。为了解决全局池化造成的位置信息丢失,CA将通道注意分解为两个平行的一维特征,编码过程将空间坐标信息整合到生成的通道注意力的特征向量中。
1、主要思想:SENET使用一个全局池化方法进行Squeeze操作,CBAM使用GAP和GMP两个池化进行Squeeze操作更充分提取通道特征。CA与他们相比又进一步特生特征提取方法,CA利用x、y两个方向的全局池化,分别将垂直和水平方向上的输入特征聚合为两个独立的方向感知特征映射,将输入Feature Map的位置信息嵌入到通道注意力的聚合特征向量。这两个嵌入方向特定信息的特征图被分别编码到两个注意图中,每个注意图捕获输入特征图沿一个空间方向的长期依赖关系。然后通过乘法将这两种注意图应用于输入特征图,加强感兴趣区域的表示。CA将所提出的注意方法命名为坐标注意力(Coordinate Attention),因为它的操作区分空间方向(即坐标)并生成坐标感知的注意力的Map。
图示如下:
上图中说明了SE、CBAM和CA模块的结构对比,可以看出来CBAM和CA都用了两组池化获得通道注意力特征,但是它们目的并不一致,CBAM是优化了SE通过GAP和GMP的两种池化操作提升通道特征聚合,而CA则是按照X方向和Y方向分别提取了嵌入方向的注意力特征信息。
思想步骤解释:
不同于只关注重通道重权重的通道注意力机制,我们的坐标注意块也考虑对空间信息进行编码。如上所述,沿水平和垂直方向的注意同时应用于输入张量。两个注意图中的每个元素都反映了感兴趣的对象是否存在于相应的行和列中。这种编码过程可以使我们的坐标注意更准确地定位感兴趣对象的确切位置,从而帮助整个模型更好地识别。
2、模块使用地方:与SE和CBAM等一样,CA可以看作是一个模块,方便嵌入到其他网络模型中,旨在增强移动网络学习特征的表达能力。
3、代码
import torch import torch.nn as nn import torch.nn.functional as F class h_sigmoid(nn.Module): def __init__(self, inplace=True): super(h_sigmoid, self).__init__() self.relu = nn.ReLU6(inplace=inplace) def forward(self, x): return self.relu(x + 3) / 6 class h_swish(nn.Module): def __init__(self, inplace=True): super(h_swish, self).__init__() self.sigmoid = h_sigmoid(inplace=inplace) def forward(self, x): return x * self.sigmoid(x) class CoordAtt(nn.Module): def __init__(self, inp, reduction=32): super(CoordAtt, self).__init__() self.pool_h = nn.AdaptiveAvgPool2d((None, 1)) self.pool_w = nn.AdaptiveAvgPool2d((1, None)) mip = max(8, inp // reduction) self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0) self.bn1 = nn.BatchNorm2d(mip) self.act = h_swish() self.conv_h = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0) self.conv_w = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0) def forward(self, x): identity = x n, c, h, w = x.size() x_h = self.pool_h(x) x_w = self.pool_w(x).permute(0, 1, 3, 2) y = torch.cat([x_h, x_w], dim=2) y = self.conv1(y) y = self.bn1(y) y = self.act(y) x_h, x_w = torch.split(y, [h, w], dim=2) x_w = x_w.permute(0, 1, 3, 2) a_h = self.conv_h(x_h).sigmoid() a_w = self.conv_w(x_w).sigmoid() out = identity * a_w * a_h return out if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) pna = CoordAtt(inp=512) output = pna(input) print(output.shape)
import torch import torch.nn as nn class MHSA(nn.Module): def __init__(self, n_dims, width=14, height=14, heads=4, pos_emb=False): super(MHSA, self).__init__() self.heads = heads self.query = nn.Conv2d(n_dims, n_dims, kernel_size=1) self.key = nn.Conv2d(n_dims, n_dims, kernel_size=1) self.value = nn.Conv2d(n_dims, n_dims, kernel_size=1) self.pos = pos_emb if self.pos: self.rel_h_weight = nn.Parameter(torch.randn([1, heads, (n_dims) // heads, 1, int(height)]), requires_grad=True) self.rel_w_weight = nn.Parameter(torch.randn([1, heads, (n_dims) // heads, int(width), 1]), requires_grad=True) self.softmax = nn.Softmax(dim=-1) def forward(self, x): n_batch, C, width, height = x.size() q = self.query(x).view(n_batch, self.heads, C // self.heads, -1) k = self.key(x).view(n_batch, self.heads, C // self.heads, -1) v = self.value(x).view(n_batch, self.heads, C // self.heads, -1) content_content = torch.matmul(q.permute(0, 1, 3, 2), k) # 1,C,h*w,h*w c1, c2, c3, c4 = content_content.size() if self.pos: content_position = (self.rel_h_weight + self.rel_w_weight).view(1, self.heads, C // self.heads, -1).permute( 0, 1, 3, 2) # 1,4,1024,64 content_position = torch.matmul(content_position, q) # ([1, 4, 1024, 256]) content_position = content_position if ( content_content.shape == content_position.shape) else content_position[:, :, :c3, ] assert (content_content.shape == content_position.shape) energy = content_content + content_position else: energy = content_content attention = self.softmax(energy) out = torch.matmul(v, attention.permute(0, 1, 3, 2)) # 1,4,256,64 out = out.view(n_batch, C, width, height) return out if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) mhsa = MHSA(n_dims=512) output = mhsa(input) print(output.shape)
全称:Efficient Channel Attention
背景提要:更复杂的注意力模块不可避免地增加了模型的复杂性。通过剖析 SENet 中的通道注意力模块,我们凭经验证明避免降维对于学习通道注意力非常重要,适当的跨通道交互可以保持性能,同时显着降低模型复杂性。因此,我们提出了一种无需降维的局部跨通道交互策略,可以通过一维卷积有效地实现。此外,我们开发了一种自适应选择一维卷积核大小的方法,确定局部跨通道交互的覆盖范围。
1、主要思想:使用 1 ∗ 1 ∗ k 1 * 1 * k 1∗1∗k 的卷积代替 SE 注意力机制中的全连接和降维操作,将全连接变成卷积操作,减少了计算量。并提出了 自适应 k 值的选取策略,通过考虑每个通道及其 k 个邻居来捕获局部跨通道交互。
图示如下:
思想步骤解释:
第一步:作者从 SE 出发,说明避免降维。
SE模块的通道注意力通过下式计算:
ω
=
σ
(
f
{
w
1
,
w
2
}
(
g
(
X
)
)
)
,
\omega = \sigma(f_{\{w_1, w_2\}}(g(X))),
ω=σ(f{w1,w2}(g(X))),
其中,
g
(
x
)
=
1
W
H
∑
i
=
1
,
j
=
1
W
,
H
X
i
,
j
g(x) = \frac{1}{WH}\sum_{i =1, j = 1}^{W, H}X_{i,j}
g(x)=WH1∑i=1,j=1W,HXi,j 为全局平均池化。如果
y
=
g
(
x
)
y = g(x)
y=g(x), 则
f
{
w
1
,
w
2
}
(
y
)
f_{\{w_1, w_2\}}(y)
f{w1,w2}(y) 可以表示为
f
{
w
1
,
w
2
}
(
y
)
=
W
2
R
e
L
U
(
W
1
y
)
f_{\{w_1, w_2\}}(y) = W_2ReLU(W_1y)
f{w1,w2}(y)=W2ReLU(W1y),也就是两层全连接。
W
1
W_1
W1 的尺度为
R
C
∗
C
/
r
R^{C*C/r}
RC∗C/r,
W
2
W_2
W2 的尺度为
R
C
/
r
∗
C
R^{C/r*C}
RC/r∗C
作者阐述重新说明 SE 的原理,主要想给出下面的观点: C / r C/r C/r 使得维数降低。 可以降低模型复杂度,但它破坏了通道与其权重之间的直接对应关系。例如,单个 FC 层使用所有通道的线性组合来预测每个通道的权重。但是 SE 中两层全连接首先将通道特征投影到低维空间,然后将其映射回来,使得通道与其权重之间的对应关系是间接的(没有一一对应)。
为了说明 SE 中通道与其权重之间的对应关系是间接的。为了验证效果,论文中将原始 SE 块与其三个变体(即 SE-Var1、SE-Var2 和 SE-Var3)进行比较,所有变体均不执行降维。比较结果如下:
可以从实验对比上看得出,无参数的SE-Var1仍然优于原始网络,表明通道注意力有能力提高深度CNN的性能。同时,SE-Var2独立学习每个通道的权重,在涉及的参数较少的情况下略优于SE块。这可能表明通道及其权重需要直接对应,同时避免降维比考虑非线性通道依赖性更重要。此外,采用单个 FC 层的 SEVar3 的性能优于在 SE 块中进行降维的两个 FC 层。所有上述结果都清楚地表明,避免降维有助于学习有效的通道注意力。因此,我们开发了没有通道降维的 ECA 模块。
==>>> 所以通道降维不行
第二步:局部跨通道交互的方法
给定没有降维的聚合特征
y
∈
R
C
y ∈ R^C
y∈RC,通道注意力可以通过以下方式学习:
ω
=
σ
(
W
y
)
\omega = \sigma(Wy)
ω=σ(Wy)
其中,
W
W
W 是
C
∗
C
C * C
C∗C 的参数矩阵,在 SE-Var2 和 SE-Var3 中,
W
W
W 矩阵的形式如下:(SE-Var2 为 1 对 1 全连接,SE-Var3 为 C 对 1 的全连接)
W
=
{
W
var
2
=
[
w
1
,
1
…
0
⋮
⋱
⋮
0
…
w
C
,
C
]
W
var
3
=
[
w
1
,
1
…
w
1
,
C
⋮
⋱
⋮
w
1
,
C
…
w
C
,
C
]
\mathbf{W}=\left\{
其中 SE-Var2 的
W
v
a
r
2
W_{var2}
Wvar2 是对角矩阵,涉及C个参数; SE-Var3 的
W
v
a
r
3
W_{var3}
Wvar3 是一个全矩阵,涉及 C×C 参数。如方程式所示。主要区别在于SE-Var3考虑了跨渠道交互,而SEVar2则没有,因此SE-Var3取得了更好的性能。这一结果表明跨渠道交互有利于学习渠道注意力。然而,SEVar3 需要大量参数,导致模型复杂度较高,特别是对于大通道数。
所以作者想折中两种方式:SE-Var2 和 SE-Var3 之间可能的折衷方案是将
W
v
a
r
2
W_{var2}
Wvar2 扩展到块对角矩阵,即
W
G
=
[
W
G
1
⋯
0
⋮
⋱
⋮
0
⋯
W
G
G
]
\mathbf{W}_{G}=\left[
是将通道分为
G
G
G组,每个组包括
C
/
G
C/G
C/G通道,并独立学习每个组中的通道注意力,以局部方式捕获跨通道交互。相应地,涉及到
C
2
/
G
C^2/G
C2/G参数。从卷积的角度来看,SE-Var2、SEVar3和上诉方式可以分别视为深度可分离卷积、FC层和组卷积。这里,具有组卷积的 SE 块(SE-GC)由
σ
(
G
C
G
(
y
)
)
=
σ
(
W
G
y
)
σ(GC_G(y)) = σ(W_Gy)
σ(GCG(y))=σ(WGy) 表示。然而,过多的组卷积会增加内存访问成本,从而降低计算效率。
此外,如实验对比表所示,具有不同组的 SE-GC 没有比 SE-Var2 带来任何增益,这表明它不是捕获本地跨通道交互的有效方案。原因可能是SE-GC完全抛弃了不同群体之间的依赖关系。
==>>> 所以组卷积不行。
结合上面的说明:作者采用带状矩阵
W
k
W_k
Wk 来学习通道注意力,
W
k
W_k
Wk 形式如下:
[
w
1
,
1
⋯
w
1
,
k
0
0
⋯
⋯
0
0
w
2
,
2
⋯
w
2
,
k
+
1
0
⋯
⋯
0
⋮
⋮
⋮
⋮
⋱
⋮
⋮
⋮
0
⋯
0
0
⋯
w
C
,
C
−
k
+
1
⋯
w
C
,
C
]
第一:避免了组卷积之间的完全独立。
第二:参数比较小。
第三:移动的考虑多个通道之间的关系。
具体公式如:
ω
i
=
σ
(
∑
j
=
1
k
w
i
j
y
i
j
)
,
y
i
j
∈
Ω
i
k
\omega_{i}=\sigma\bigg(\sum_{j=1}^{k}w_{i}^{j}y_{i}^{j}\bigg),y_{i}^{j}\in\Omega_{i}^{k}
ωi=σ(j=1∑kwijyij),yij∈Ωik
其中,
Ω
i
k
Ω^k_i
Ωik 表示
y
i
y_i
yi 的
k
k
k 个相邻通道的集合。
更有效的方法是让所有通道共享相同的学习参数,即:
ω
i
=
σ
(
∑
j
=
1
k
w
j
y
i
j
)
,
y
i
j
∈
Ω
i
k
\omega_{i}=\sigma\bigg(\sum_{j=1}^{k}w^{j}y_{i}^{j}\bigg),y_{i}^{j}\in\Omega_{i}^{k}
ωi=σ(j=1∑kwjyij),yij∈Ωik
请注意,这种策略可以通过内核大小为 k 的快速一维卷积轻松实现,即:
ω
=
σ
(
C
1
D
k
(
y
)
)
,
\omega=\sigma(\mathrm{C1D}_k(\mathbf{y})),
ω=σ(C1Dk(y)),
其中
C
1
D
C1D
C1D 表示一维卷积,仅涉及k个参数。实验证明,我们的 k = 3 的 ECA 模块实现了与 SE-var3 类似的结果,同时模型复杂度低得多,通过适当捕获本地跨渠道交互来保证效率和有效性。
第三步,确定 k 如何选取:局部跨通道交互覆盖
上述ECA模块旨在适当捕获局部跨道信息交互,因此需要确定通道交互信息的大致范围(即1D卷积的卷积核大小k)。虽然可以针对各种CNN架构中具有不同通道数的卷积块进行手动优化设置信息交互的最佳范围,但是通过手动进行交叉验证调整将花费大量计算资源。而且分组卷积已成功地用于改善CNN架构,在固定group数量的情况下,高维(低维)通道与长距离(短距离)卷积成正比。同理,跨通道信息交互作用的覆盖范围(即一维卷积的内核大小k)与通道维数C应该也是成正比的。换句话说,在k和C之间可能存在映射
ϕ
\phi
ϕ:
C
=
ϕ
(
k
)
.
C=\phi(k).
C=ϕ(k).
最简单的映射是线性函数,即
ϕ
(
k
)
=
γ
∗
k
−
b
\phi(k) = γ ∗ k − b
ϕ(k)=γ∗k−b。然而,以线性函数为特征的关系过于有限。另一方面,众所周知,通道维度 C(即滤波器的数量)通常设置为 2 的幂。因此,我们通过扩展线性函数
ϕ
(
k
)
=
γ
∗
k
−
b
\phi(k) = γ ∗ k − b
ϕ(k)=γ∗k−b 来引入一种可能的解决方案为非线性的,即
C
=
ϕ
(
k
)
=
2
(
γ
∗
k
−
b
)
,
C=\phi(k)=2^{(\gamma*k-b)},
C=ϕ(k)=2(γ∗k−b),
所以,给定通道维度 C,内核大小 k 可以自适应地确定为
k
=
ψ
(
C
)
=
∣
l
o
g
2
(
C
)
γ
+
b
γ
∣
o
d
d
,
k=\psi(C)=\left|\frac{log_2(C)}{\gamma}+\frac{b}{\gamma}\right|_{odd},
k=ψ(C)=
γlog2(C)+γb
odd,
其中
∣
t
∣
o
d
d
|t|_{odd}
∣t∣odd 表示 t 最接近的奇数。在本文中,我们在所有实验中将
γ
γ
γ 和
b
b
b 分别设置为 2 和 1。显然,通过映射
ψ
ψ
ψ,高维通道具有较长范围的相互作用,而低维通道通过使用非线性映射进行较短范围的相互作用。
2、模块使用地方:ECA模块是一个即插即用的模块,可以在卷积模块之后直接插入ECA模块,也可以在残差结构里面添加了ECA模块。
3、代码
import numpy as np import torch from torch import nn from torch.nn import init from collections import OrderedDict 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) if __name__ == '__main__': input = torch.randn(50, 512, 7, 7) eca = ECAAttention(kernel_size=3) output = eca(input) print(output.shape)
全称: Simple, Parameter-Free Attention Module
背景提要:现有的注意力模块通常被继承到每个块中,以改进来自先前层的输出。这种细化步骤通常沿着通道维度(a)或空间维度(b)操作,这些方法生成一维或二维权重,并平等对待每个通道或空间位置中的神经元,
通道注意力:1D注意力,它对不同通道区别对待,对所有位置同等对待;
空域注意力:2D注意力,它对不同位置区别对待,对所有通道同等对待。
这可能会限制他们学习更多辨别线索的能力。因此三维权重(本文)优于传统的一维和二维权重注意力
1、主要思想:基于一些著名的神经科学理论,提出优化能量函数来找出每个神经元的重要性(三维权重)。
思想步骤解释:
为更好的实现注意力,我们需要评估每个神经元的重要性。在神经科学中,信息丰富的神经元通常表现出与周围神经元不同的放电模式。而且,激活神经元通常会抑制周围神经元,即空域抑制。换句话说,具有空域抑制效应的神经元应当赋予更高的重要性,找到这些神经元最简单的实现:测量一个目标神经元和其他神经元之间的线性可分性。
如何理解这个线性可分性:如果当前神经元和其他神经元差异性很明显的话,通过一层 y = w x + b y= wx + b y=wx+b的分类器,我们能够很容易的将其区分开,结果的均值和方差就很小。要是差异不大,很难分开,那结果的均值和方差就比较大。
基于这些神经科学发现,作者为每个神经元定义了以下能量函数:
e
t
(
w
t
,
b
t
,
y
,
x
i
)
=
(
y
t
−
t
^
)
2
+
1
M
−
1
∑
i
=
1
M
−
1
(
y
o
−
x
^
i
)
2
其中:
t
^
=
w
t
t
+
b
t
\hat{t} = w_tt + b_t
t^=wtt+bt 和
x
^
i
=
w
t
x
i
+
b
t
\hat{x}_i = w_tx_i + b_t
x^i=wtxi+bt 是
t
t
t 和
x
i
x_i
xi 的线性变换,其中
t
t
t 和
x
i
x_i
xi 是输入特征
X
∈
R
C
×
H
×
W
X ∈ R^{C×H×W}
X∈RC×H×W 的单个通道中的目标神经元和其他神经元。
i
i
i 是空间维度上的索引,
M
=
H
×
W
M = H×W
M=H×W 是该通道上的神经元数量。
w
t
w_t
wt 和
b
t
b_t
bt 是变换的权重和偏置,我们想要求解的就是
w
t
w_t
wt 和
b
t
b_t
bt。
方程(1)中的所有值都是标量。当
t
^
\hat{t}
t^ 等于
y
t
y_t
yt 且所有其他
x
^
i
\hat{x}_i
x^i 均为
y
o
y_o
yo 时,方程(1)达到最小值,其中
y
t
y_t
yt 和
y
o
y_o
yo 是两个不同的值。
通过最小化方程,方程(1)相当于找到目标神经元 t 与同一通道中所有其他神经元之间的线性可分离性。为了简单起见,我们对
y
t
y_t
yt 和
y
o
y_o
yo 采用二进制标签(即 1 和 -1),并在方程(1)中添加权重正则化器。最终的能量函数由下式给出:
e
t
(
w
t
,
b
t
,
y
,
x
i
)
=
1
M
−
1
∑
i
=
1
M
−
1
(
−
1
−
(
w
t
x
i
+
b
t
)
)
2
+
(
1
−
(
w
t
t
+
b
t
)
)
2
+
λ
w
t
2
理论上,每个通道都有 M 个能量函数 (每个神经元一个)。通过 SGD 等迭代求解器求解所有这些方程的计算量非常大。幸运的是,方程(2)对于
w
t
w_t
wt 和
b
t
b_t
bt 有一个快速闭式解,可以通过以下方式轻松获得:
w
t
=
−
2
(
t
−
µ
t
)
(
t
−
µ
t
)
2
+
2
σ
t
2
+
2
λ
,
b
t
=
−
1
2
(
t
+
µ
t
)
w
t
.
其中:
µ
t
=
1
M
−
1
∑
i
=
1
M
−
1
x
i
µ_t = \frac{1}{M−1} \sum^{M−1}_{i=1}x_i
µt=M−11∑i=1M−1xi 和
σ
t
2
=
1
M
−
1
∑
i
M
−
1
(
x
i
−
μ
t
)
2
σ^2_t = \frac{1}{M−1}\sum^{M−1}_i (x_i−μ_t)^2
σt2=M−11∑iM−1(xi−μt)2 是对该通道中除 t 之外的所有神经元计算的平均值和方差。
由于方程(3)和方程(4)所示的现有解是在单个通道上获得的,因此可以合理地假设单个通道中的所有像素遵循相同的分布(均值和方差一样)。考虑到这一假设,可以计算所有神经元的平均值和方差,并为该通道上的所有神经元重复使用。它可以显着降低计算成本,避免迭代计算每个位置的
µ
µ
µ 和
σ
σ
σ。
因此,最小能量可以通过以下公式计算:
e
t
∗
=
4
(
σ
^
2
+
λ
)
(
t
−
μ
^
)
2
+
2
σ
^
2
+
2
λ
,
其中:
µ
^
=
1
M
∑
i
=
1
M
x
i
\hat{µ} = \frac{1}{M} \sum^{M}_{i=1}x_i
µ^=M1∑i=1Mxi 和
σ
^
2
=
1
M
∑
i
M
(
x
i
−
μ
^
)
2
\hat{σ}^2 = \frac{1}{M}\sum^{M}_i (x_i−\hat{μ})^2
σ^2=M1∑iM(xi−μ^)2 是该通道所有神经元的平均值和方差。
公式(5)表明能量 e t ∗ e^*_t et∗ 越低,神经元 t t t 与周围神经元的区别越明显,对视觉处理越重要。因此,每个神经元的重要性可以通过 1 / e t ∗ 1/e^*_t 1/et∗ 得到。
哺乳动物大脑中的注意力调节通常表现为对神经元反应的增益(即缩放)效应。因此,我们使用缩放运算符而不是加法来进行特征细化。我们模块的整个细化阶段是:
X
^
=
s
i
g
m
o
i
d
(
1
E
)
⊙
X
=
σ
(
(
t
−
μ
^
)
2
+
2
σ
^
2
+
2
λ
4
(
σ
^
2
+
λ
)
)
⊙
X
=
σ
(
(
t
−
μ
^
)
2
4
(
σ
^
2
+
λ
)
+
0.5
)
⊙
X
其中:
E
E
E 将所有
e
t
∗
e^*_t
et∗ 跨通道和空间维度进行分组。添加
s
i
g
m
o
i
d
sigmoid
sigmoid 是为了限制
E
E
E 值过大,不会影响每个神经元的相对重要性,因为
s
i
g
m
o
i
d
sigmoid
sigmoid 是单峰函数。
论文给出的代码流程图如下:
值得注意的是:论文中唯一的超参数 λ \lambda λ 在实验的时候为 1 e − 4 1e-4 1e−4。
2、模块使用地方:一个即插即用的模块,可以在卷积模块之后直接插入simAM模块。
3、代码
import torch import torch.nn as nn class SimAM(torch.nn.Module): def __init__(self, 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 # 计算(t-u)^2 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) if __name__ == '__main__': input = torch.randn(3, 64, 7, 7) model = SimAM() print(model) outputs = model(input) print(outputs.shape)
全称:
背景提要:
1、主要思想:
简单图示如下:
思想步骤解释:
值得注意的是:
2、模块使用地方:
3、代码
import torch
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。