注意力机制介绍
注意力机制(Attention Mechanism)源于对人类视觉的研究。在认知科学中,由于信息处理的瓶颈,人类会选择性地关注所有信息的一部分,同时忽略其他可见的信息。为了合理利用有限的视觉信息处理资源,人类需要选择视觉区域中的特定部分,然后集中关注它。例如,人们在阅读时,通常只有少量要被读取的词会被关注和处理。综上,注意力机制主要有两个方面:决定需要关注输入的哪部分;分配有限的信息处理资源给重要的部分。
注意力机制的分类:
1、SE 注意力模块(2019)
论文名称:《Squeeze-and-Excitation Networks》
论文地址:https://arxiv.org/pdf/1709.01507.pdf
代码地址:GitCode - 开发者的代码家园https://github.com/miraclewkf/SENet-PyTorchGitCode - 开发者的代码家园
1.1 原理
SEnet(Squeeze-and-Excitation Network)考虑了特征通道之间的关系,在特征通道上加入了注意力机制。
SEnet通过学习的方式自动获取每个特征通道的重要程度,并且利用得到的重要程度来提升特征并抑制对当前任务不重要的特征。
SEnet通过Squeeze模块和Excitation模块实现所述功能。
如图所示,首先作者通过Squeeze操作,对空间维度进行压缩,直白地说就是对每个特征图做全局池化,平均成一个实数值。该实数从某种程度上来说具有全局感受野。作者提到该操作能够使得靠近数据输入的特征也可以具有全局感受野,这一点在很多的任务中是非常有用的。
紧接着就是Excitation操作,由于经过Squeeze操作后,网络输出了 大小的特征图,作者利用权重 来学习 个通道直接的相关性。在实际应用时有的框架使用全连接,有的框架使用 的卷积实现。该过程中作者先对 个通道降维再扩展回 通道。好处就是一方面降低了网络计算量,一方面增加了网络的非线性能力。
最后一个操作时将Excitation的输出看作是经过特征选择后的每个通道的重要性,通过乘法加权的方式乘到先前的特征上,从而实现提升重要特征,抑制不重要特征这个功能。
SE模块的灵活性在于它可以直接应用到现有的网络结构中。以Inception和Resnet为例。对于Inception网络,没有残差结构,这里对整个Inception模块应用SE模块。对于Resnet,SE模块嵌入到残差结构的残差学习分支中。具体如下图所示:
1.2 代码
# SE class SE(nn.Module): def __init__(self, c1, c2, ratio=16): super(SE, self).__init__() #c*1*1 self.avgpool = nn.AdaptiveAvgPool2d(1) self.l1 = nn.Linear(c1, c1 // ratio, bias=False) self.relu = nn.ReLU(inplace=True) self.l2 = nn.Linear(c1 // ratio, c1, bias=False) self.sig = nn.Sigmoid() def forward(self, x): b, c, _, _ = x.size() y = self.avgpool(x).view(b, c) y = self.l1(y) y = self.relu(y) y = self.l2(y) y = self.sig(y) y = y.view(b, c, 1, 1) return x * y.expand_as(x)
2、CBAM 注意力模块(2018)
论文题目:《CBAM:Convolutional Block Attention Module》
论文地址:https://arxiv.org/pdf/1807.06521.pdf
2.1 原理
CBAM(Convolutional Block Attention Module)结合了特征通道和特征空间两个维度的注意力机制。核心在于:应用Channel Attention Module(通道注意模块)和Spatial Attention Module(空间注意模块)结合,对输入进来的特征层分别进行通道注意力和空间注意力模块的处理。
CBAM通过学习的方式自动获取每个特征通道的重要程度,和SEnet类似。此外还通过类似的学习方式自动获取每个特征空间的重要程度。并且利用得到的重要程度来提升特征并抑制对当前任务不重要的特征。
CBAM提取特征通道注意力的方式基本和SEnet类似,如下Channel Attention中的代码所示,其在SEnet的基础上增加了max_pool的特征提取方式,其余步骤是一样的。将通道注意力提取后的特征作为空间注意力模块的输入。
CBAM提取的特征空间注意力的方式:经过Channel Attention后,最终将经过通道重要性选择后的特征图送入特征空间注意力模块,和通道注意力模块类似,空间注意力是以通道为单位进行最大池化和平均池化,并将两者的结果进行concat,之后再一个卷积降成 的特征图空间权重,再将该权重和输入特征进行点积,从而实现空间注意力机制。
2.2 代码
# CBAM class ChannelAttention(nn.Module): def __init__(self, in_planes, ratio=16): super(ChannelAttention, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.max_pool = nn.AdaptiveMaxPool2d(1) self.f1 = nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False) self.relu = nn.ReLU() self.f2 = nn.Conv2d(in_planes // ratio, in_planes, 1, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): avg_out = self.f2(self.relu(self.f1(self.avg_pool(x)))) max_out = self.f2(self.relu(self.f1(self.max_pool(x)))) out = self.sigmoid(avg_out + max_out) return out 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 # (特征图的大小-算子的size+2*padding)/步长+1 self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): # 1*h*w avg_out = torch.mean(x, dim=1, keepdim=True) max_out, _ = torch.max(x, dim=1, keepdim=True) x = torch.cat([avg_out, max_out], dim=1) #2*h*w x = self.conv(x) #1*h*w return self.sigmoid(x) class CBAM(nn.Module): def __init__(self, c1, c2, ratio=16, kernel_size=7): # ch_in, ch_out, number, shortcut, groups, expansion super(CBAM, self).__init__() self.channel_attention = ChannelAttention(c1, ratio) self.spatial_attention = SpatialAttention(kernel_size) def forward(self, x): out = self.channel_attention(x) * x # c*h*w # c*h*w * 1*h*w out = self.spatial_attention(out) * out return out
3、ECA 注意力模块(2020)
论文题目:《ECA-Net:Efficient Channel Attention for Deep Convolutional Neural Networks》
论文地址:https://arxiv.org/pdf/1910.03151.pdf
代码地址:https://github.com/BangguWu/ECANet
3.1 原理
先前的大多数方法致力于开发更复杂的注意力模块,以实现更好的性能,这不可避免地增加了模型的复杂性。为了克服性能和复杂性之间的矛盾,作者提出了一种有效的通道关注(ECA)模块,该模块只增加了少量的参数,却能获得明显的性能增益。
SE注意力机制首先对输入特征图进行了通道压缩,而这样压缩降维对于学习通道之间的依赖关系有不利影响,基于此理念,ECA注意力机制避免降维,用1维卷积高校实现了局部跨通道交互,提取通道间的依赖关系。具体步骤如下:
(1)将输入特征图进行全局平均池化操作;
(2)进行卷积核大小为k的 1 维卷积操作,并经过Sigmoid激活函数得到各个通道的权重w,如公式所示:
(3)将权重与原始输入特征图对应元素相乘,得到最终输出特征图。
3.2 代码
class ECA(nn.Module): """Constructs a ECA module. Args: channel: Number of channels of the input feature map k_size: Adaptive selection of kernel size """ def __init__(self, c1,c2, k_size=3): super(ECA, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): # feature descriptor on the global spatial information y = self.avg_pool(x) y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1) # Multi-scale information fusion y = self.sigmoid(y) return x * y.expand_as(x)
4、CA 注意力模块(2021)
论文题目:《Coordinate Attention for Efficient Mobile Network Design》
论文地址:https://arxiv.org/pdf/2103.02907.pdf
4.1 原理
先前的轻量级网络的注意力机制大都采用SE模块,仅考虑了通道间的信息,忽略了位置信息。尽管后来的BAM和CBAM尝试在降低通道数后通过卷积来提取位置注意力信息,但卷积只能提取局部关系,缺乏长距离关系提取的能力。为此,论文提出了新的高效注意力机制Coordinate Attention(CA),能够将横向和纵向的位置信息编码到Channel Attention中,使得移动网络能够关注大范围的位置信息又不会带来过多的计算量。
Channel Attention的优势主要有以下几点:
- 不仅获取了通道间信息,还考虑了方向相关的位置信息,有助于模型更好地定位和识别目标;
- 足够灵活和轻量,能够简单地插入到移动网络的核心结构中;
- 可以作为预训练模型用于多种任务中,如检测和分割,均有不错的性能提升。
4.2 代码
# CA 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, oup, 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, oup, kernel_size=1, stride=1, padding=0) self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0) def forward(self, x): identity = x n, c, h, w = x.size() #c*1*W x_h = self.pool_h(x) #c*H*1 #C*1*h x_w = self.pool_w(x).permute(0, 1, 3, 2) y = torch.cat([x_h, x_w], dim=2) #C*1*(h+w) 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
5、SimAM 注意力模块(2021)
论文题目:《SimAM:A Simple,Parameter-Free Attention Module for Convolutional Neural Networks》
论文地址:http://proceedings.mlr.press/v139/yang21o/yang21o.pdf
代码地址: https://github.com/ZjjConan/SimAM
5.1 原理
在这篇论文中,我们提出了一个概念上简单但非常有效的注意力模块,用于卷积神经网络。与现有的基于通道和空间的注意力模块不同,我们的模块通过推断特征图中的三维注意力权重来工作,而无需向原始网络添加参数。具体而言,我们基于一些著名的神经科学理论,并提出了优化能量函数以找到每个神经元重要性的方法。我们进一步推导了能量函数的快速闭合形式解,并展示了该解可以用不到10行代码实现。
该模块的另一个优点是,大多数运算符是基于定义的能量函数解的选择,避免了过多的结构调整的工作。对各种视觉任务的定量评估表明,所提出的模块具有灵活性和有效性,可以提高许多ConvNets的表征能力。
如图所示:
(a)通道注意力:1D注意力,它对不同通道区别对待,对所有位置同等对待;
(b)空域注意力:2D注意力,它对不同位置区别对待,对所有通道同等对待。
(1)现有注意力模块的另一个重要影响因素:权重生成方法。现有注意力往往采用额外的子网络生成注意力权值,比如SE的GAP+FC+ReLU+FC+Sigmoid。我们认为,注意力机制的实现应该由神经计算中的一些统一原则来指导。因此,我们基于一些成熟的神经科学理论提出了一种新的方法SimAM。
(2)在神经科学中,信息丰富的神经元通常表现出与周围神经元不同的放电模式。而且,激活神经元通常会抑制周围神经元,即空域抑制。换句话说,具有空域抑制效应的神经元应当赋予更高的重要性。最简单的寻找重要神经元的方法:度量神经元之间的线性可分性。
5.2 代码
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 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)
6、S2-MLPv2 注意力模块(2021)
论文题目:《S^2-MLPv2:IMPROVED SPATIAL-SHIFT MLP ARCHITECTURE FOR VISION》
论文地址:https://arxiv.org/pdf/2108.01072.pdf
6.1 原理
最近,基于多层感知机(MLP)的视觉骨干网络开始出现。相比于CNN和视觉Transformer,具有较少归纳偏差的基于MLP的视觉架构在图像识别中取得了有竞争力的性能。其中,采用直接的空间位移MLP(S^2-MLP)在性能上优于包括MLP-mixer和ResMLP在内的开创性工作。最近,使用具有金字塔结构的较小补丁,Vision Permutator(ViP)和Global Filter Network(GFNet)在性能上由于S2-MLP。
在本文中,我们改进了S2-MLP视觉骨干网络。我们沿通道维度扩展特征图,并将扩展后的特征图分成几个部分。我们对这些分割部分进行不同的空间位移操作。同时,我们利用分割注意力操作来融合这些分割部分。此外,与之前的方法类似,我们采用较小规模的补丁,并使用金字塔结构来提高图像识别准确性。我们将改进后的空间位移MLP视觉骨干网络成为S2-MLPv2。使用5500万个参数,我们的中等规模模型S2-MLPv2-Medium在ImageNet-1K基准测试中,使用224x224的图像,在没有自注意力和外部训练数据的情况下,实现了83.6%的Top-1准确率。
图:S2-MLP和提出的S2-MLPv2之间的空间位移操作的比较。在S2-MLP中,通道被平均分成四个部分,每个部分沿不同的方向进行位移。在位移后的通道上进行MLP操作。相比之下,在S2-MLPv2中, 通道特征图被扩展为 通道特征图。然后,扩展的特征图沿通道维度被平均分成三个部分。对于每个部分,我们进行不同的空间位移操作。然后,通过分割注意力操作,将位移后的部分合并以生成 通道特征图。
6.2 代码
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) a = torch.sum(torch.sum(x_all, 1), 1) hat_a = self.mlp2(self.gelu(self.mlp1(a))) hat_a = hat_a.reshape(b, self.k, c) bar_a = self.softmax(hat_a) attention = bar_a.unsqueeze(-2) out = attention * x_all 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
7、NAM Attention 注意力模块(2021)
论文题目:《NAM:Normalization-based Attention Module》
论文地址:https://arxiv.org/pdf/2111.12419.pdf
代码地址:GitHub - Christian-lyc/NAM
7.1 原理
识别较不显著的特征对于模型压缩至关重要。然而,在革命性的注意力机制中,这一点尚未得到研究。在本研究中,我们提出了一种新颖的基于规范化的注意力模块(NAM),它抑制了较不显著的权重。它对注意力模块应用权重稀疏惩罚,从而使其在保持类似性能的同时更加计算高效。在Resnet和Mobilenet上与其他三种注意力机制进行比较表明,我们的方法具有更高的准确性。
之前的很多研究聚焦于如何通过注意力算子来获取显著性的特征。这些方法成功的发现了特征的不同维度之间的互信息量。但是,缺乏对权值的贡献因子的考虑,而这个贡献因子可以进一步的抑制不显著的特征。因此,我们瞄准了利用权值的贡献因子来提升注意力的效果。我们使用了Batch Normalization的缩放因子来表示权值的重要程度。这样可以避免如SE,BAM和CBAM一样增加全连接层和卷积层。这样,我们提出了一个新的注意力方式:基于归一化的注意力(NAM)。
7.2 代码
import torch.nn as nn import torch from torch.nn import functional as F class Channel_Att(nn.Module): def __init__(self, channels): super(Channel_Att, self).__init__() self.channels = channels self.bn2 = nn.BatchNorm2d(self.channels, affine=True) def forward(self, x): residual = x x = self.bn2(x) weight_bn = self.bn2.weight.data.abs() / torch.sum(self.bn2.weight.data.abs()) x = x.permute(0, 2, 3, 1).contiguous() x = torch.mul(weight_bn, x) x = x.permute(0, 3, 1, 2).contiguous() x = torch.sigmoid(x) * residual # return x class NAMAttention(nn.Module): def __init__(self, channels): super(NAMAttention, self).__init__() self.Channel_Att = Channel_Att(channels) def forward(self, x): x_out1 = self.Channel_Att(x) return x_out1
8、Criss-Cross Attention 注意力模块(2020)
论文题目:《CCNet: Criss-Cross Attention for Semantic Segmentation》
论文地址:https://arxiv.org/pdf/1811.11721.pdf
8.1 原理
在语义分割和物体检测等视觉理解问题中,上下文信息至关重要。我们提出了一种名为Criss-Cross Network(CCNet)的方法,以非常有效和高校的方式获取完整的图像上下文信息。
具体来说,对于每个像素,一个新颖的交叉注意力模块收集其交叉路径上所有像素的上下文信息。通过进一步的循环操作,每个像素最终可以捕获完整的图像依赖关系。此外,我们提出了一种类别一致的损失,用于强制交叉注意力模块产生更具辨别力的特征。
总体而言,CCNet具有以下优点:
(1)对GPU内存友好。与非局部块相比,提出的循环交叉注意力模块使用的GPU内存减少了11倍;
(2)高计算效率。循环交叉注意力显著减少了非局部块的Flops,约为其85%。
(3)具备最先进的性能。我们在包括Cityscapes、ADE20K、LIP以及COCO等语义分割基准数据集上进行了大量的实验。特别是,在Citysscapes测试集、ADE20K验证集和LIP验证集上,我们的CCNet分别实现了81.9%、45.76%和55.47%的mIoU得分,这些是最新的最先进结果。
8.2 代码
import torch import torch.nn as nn import torch.nn.functional as F from torch.nn import Softmax def INF(B, H, W): return -torch.diag(torch.tensor(float("inf")).repeat(H), 0).unsqueeze(0).repeat(B * W, 1, 1) class CrissCrossAttention(nn.Module): """ Criss-Cross Attention Module""" def __init__(self, in_dim): super(CrissCrossAttention, self).__init__() 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.softmax = Softmax(dim=3) self.INF = INF self.gamma = nn.Parameter(torch.zeros(1)) def forward(self, x): m_batchsize, _, height, width = x.size() proj_query = self.query_conv(x) proj_query_H = proj_query.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height).permute(0, 2, 1) proj_query_W = proj_query.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width).permute(0, 2, 1) proj_key = self.key_conv(x) proj_key_H = proj_key.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height) proj_key_W = proj_key.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width) proj_value = self.value_conv(x) proj_value_H = proj_value.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height) proj_value_W = proj_value.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width) energy_H = (torch.bmm(proj_query_H, proj_key_H) + self.INF(m_batchsize, height, width)).view(m_batchsize, width, height, height).permute(0, 2, 1, 3) energy_W = torch.bmm(proj_query_W, proj_key_W).view(m_batchsize, height, width, width) concate = self.softmax(torch.cat([energy_H, energy_W], 3)) att_H = concate[:, :, :, 0:height].permute(0, 2, 1, 3).contiguous().view(m_batchsize * width, height, height) att_W = concate[:, :, :, height:height + width].contiguous().view(m_batchsize * height, width, width) out_H = torch.bmm(proj_value_H, att_H.permute(0, 2, 1)).view(m_batchsize, width, -1, height).permute(0, 2, 3, 1) out_W = torch.bmm(proj_value_W, att_W.permute(0, 2, 1)).view(m_batchsize, height, -1, width).permute(0, 2, 1, 3) # print(out_H.size(),out_W.size()) return self.gamma * (out_H + out_W) + x
9、GAM Attention 注意力模块(2021)
论文题目:《Global Attention Mechanism: Retain Information to Enhance Channel-Spatial Interactions》
论文地址:https://arxiv.org/pdf/2112.05561v1.pdf
9.1 原理
各种注意力机制已经被研究用于改善各种计算机视觉任务的性能。然而,先前的方法忽视了在通道和空间方面保留信息以增强跨维度交互的重要性。因此,我们提出了一种全局注意力机制,通过减少信息损失和放大全局交互表示来提升深度神经网络的性能。我们引入了3D排列和多层感知机用于通道注意,同时还引入了卷积空间注意的子模块。对于在CIFAR-100和ImageNet-1K上进行的图像分类任务的评估表明,我们的方法在Resnet和轻量级MoblieNet上稳定优于几种最近的注意力机制。
通道注意子模块使用3D排列来跨三个维度保留信息。然后,它使用两层MLP(多层感知机)放大跨维度的通道-空间依赖关系。(MLP是一个编码-解码器结构,具有与BAM相同的减少比例r)。通道注意子模块如图2所示:
在空间注意子模块中,为了关注空间信息,我们使用两个卷积层进行空间信息融合。我们还使用与通道注意子模块相同的减少比例r,与BAM相同。同时,最大池化会减少信息并对结果产生负面影响。为了进一步保留特征图,我们去除了池化操作。结果是,空间注意模块有时会显著增加参数的数量。为了防止参数数量显著增加,我们在ResNet50中采用了具有通道随机洗牌的组卷积。不带组卷积的空间注意子模块如图3所示:
9.2 代码
import numpy as np import torch from torch import nn from torch.nn import init class GAMAttention(nn.Module): def __init__(self, c1, c2, group=True, rate=4): super(GAMAttention, self).__init__() self.channel_attention = nn.Sequential( nn.Linear(c1, int(c1 / rate)), nn.ReLU(inplace=True), nn.Linear(int(c1 / rate), c1) ) self.spatial_attention = nn.Sequential( nn.Conv2d(c1, c1 // rate, kernel_size=7, padding=3, groups=rate) if group else nn.Conv2d(c1, int(c1 / rate), kernel_size=7, padding=3), nn.BatchNorm2d(int(c1 / rate)), nn.ReLU(inplace=True), nn.Conv2d(c1 // rate, c2, kernel_size=7, padding=3, groups=rate) if group else nn.Conv2d(int(c1 / rate), c2, kernel_size=7, padding=3), nn.BatchNorm2d(c2) ) def forward(self, x): b, c, h, w = x.shape x_permute = x.permute(0, 2, 3, 1).view(b, -1, c) x_att_permute = self.channel_attention(x_permute).view(b, h, w, c) x_channel_att = x_att_permute.permute(0, 3, 1, 2) x = x * x_channel_att x_spatial_att = self.spatial_attention(x).sigmoid() x_spatial_att = channel_shuffle(x_spatial_att, 4) # last shuffle out = x * x_spatial_att return out
10、Selective Kernel Attention 注意力模块(2019)
论文题目:《Selective Kernel Networks》
论文地址:https://arxiv.org/pdf/1903.06586.pdf
代码地址:https://github.com/developer0hye/SKNet-PyTorch/tree/master
10.1 原理
在标准的卷积神经网络中,每层人工神经元的感受野被设计为具有相同大小。神经科学界已经广泛认识到,视觉皮层神经元的感受野大小会受到刺激的调节,然而在构建CNN时很少考虑这一点。我们提出了一种动态选择机制,使得CNN中的每个神经元可以根据多个输入信息的尺度自适应地调整其感受野大小。我们设计了一个称为Selective Kernel(SK)单元的构建块,其中使用softmax注意力将具有不同核大小的多个分支进行融合,这种注意力受到这些分支中的信息的指导。对这些分支的不同注意力融合层中神经元的有效感受野的不同大小。多个SK单元堆叠成一个深度网络,称为Selective Kernel Networks(SKNets)。在ImageNet和CIFAR基准测试中,我们经验证明SKNet在模型复杂度较低的情况下胜过了现有的最先进架构。详细分析表明,SKNet中的神经元能够捕捉具有不同尺度的目标对象,从而验证了神经元根据输入自适应地调整感受野大小的能力。
SKNet(Selective Kernel Network)是一种用于图像分类和目标检测任务的深度神经网络架构,其核心创新是引入了选择性的多尺度卷积核(Selective Kernel)以及一种新颖的注意力机制,从而在不增加网络复杂性的情况下提升了特征提取的能力。SKNet的设计旨在解决多尺度信息融合的问题,使网络能够适应不同尺度的特征。
10.2 代码
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
11、A^2-Net 注意力模块(2018)
论文题目:《 -Nets: Double Attention Networks》
论文地址:https://arxiv.org/pdf/1810.11579.pdf
11.1 原理
学习捕捉远距离关系对于图像/视频识别是基础性的。现有的CNN模型通常依靠增加深度来建模这些关系,这在很大程度上效率低下。在这项工作中,我们提出了双重注意力块,这是一种新颖的组件,它可以从输入图像/视频的整个时空空间聚合和传播有效信息的全局特征,使得后续的卷积层可以高效地访问整个空间的特征。
该组件设计了一个双重注意力机制的两个步骤:
(1)通过二阶注意力池将整个空间的特征聚集到一个紧凑的集合中;
(2)通过另一个注意力机制自适应地选择和分配特征到每个位置。
所提出的双重注意力块易于采用,并可以方便地插入到现有的深度神经网络中。我们进行了大量的消融研究和实验证明其性能。在图像识别任务中,使用我们的双重注意力块装备的ResNet-50在ImageNet-1k数据集上胜过了更大的ResNet-152架构,参数数量减少了40%以上,FLOPs也减少了。在动作识别任务中,我们提出的模型在Kinetics和UCF-101数据集上取得了最先进的结果,并具有比最近的研究工作更高的效率。
11.2 代码
from torch.nn import init class DoubleAttention(nn.Module): def __init__(self, in_channels, c_m, c_n, reconstruct=True): super().__init__() self.in_channels = in_channels self.reconstruct = reconstruct self.c_m = c_m self.c_n = c_n self.convA = nn.Conv2d(in_channels, c_m, 1) self.convB = nn.Conv2d(in_channels, c_n, 1) self.convV = nn.Conv2d(in_channels, c_n, 1) if self.reconstruct: self.conv_reconstruct = nn.Conv2d(c_m, in_channels, kernel_size=1) self.init_weights() 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, h, w = x.shape assert c == self.in_channels A = self.convA(x) # b,c_m,h,w B = self.convB(x) # b,c_n,h,w V = self.convV(x) # b,c_n,h,w tmpA = A.view(b, self.c_m, -1) attention_maps = F.softmax(B.view(b, self.c_n, -1), dim=1) attention_vectors = F.softmax(V.view(b, self.c_n, -1), dim=1) # step 1: feature gating global_descriptors = torch.bmm(tmpA, attention_maps.permute(0, 2, 1)) # b.c_m,c_n # step 2: feature distribution tmpZ = global_descriptors.matmul(attention_vectors) # b,c_m,h*w tmpZ = tmpZ.view(b, self.c_m, h, w) # b,c_m,h,w if self.reconstruct: tmpZ = self.conv_reconstruct(tmpZ) return tmpZ