万字长文解读空间、通道注意力机制机制和超详细代码逐行分析(SE,CBAM,SGE,CA,ECA,TA)

目录

  • 一、SE(Squeeze-and-excitation)注意力
    • 什么是SE注意力?
    • SE注意力的步骤
        1. 压缩(Squeeze)
        1. 激励(Excitation)
        1. 重标定(Scale/Reweight)
    • 结构
    • 代码
  • 二、CBAM(Convolutional Block Attention Module)
    • 什么是CBAM注意力?
    • CBAM的组成部分
        1. 通道注意力(Channel Attention)
        1. 空间注意力(Spatial Attention)
        1. 混合注意力
    • 代码
    • 缺陷
        1. 计算复杂度高
        1. 通道和空间注意力分离处理的局限
        1. 空间注意力的局限
        1. 适应性较差
        1. 空间维度的特征提取受限
    • 总结
  • 三、SGE
    • 什么是SGE注意力?
    • 工作流程
    • 代码详解
    • 缺陷
        1. 计算成本较高
        1. 可能会丢失全局信息
        1. 对于检测任务的表现有限
        1. 依赖于组的选择
  • 四、CA(Coordinate Attention)
    • 关键词
    • 什么是CA注意力?
    • 工作原理
    • 结构
    • 代码详解
      • 步骤 1:分离空间信息
      • 步骤 2:融合空间特征
      • 步骤 3:生成坐标注意力
      • 步骤 4:应用注意力并输出
    • 缺陷
        1. 依赖全局信息,适合大尺度特征图:
        1. 额外的计算开销:
        1. 信息融合方式的局限性:
        1. 对特征分组数的敏感性:
    • 总结
  • 五、Polarized Self-Attention(PSA)注意力
    • 关键词
    • 研究目的
    • 思想
        1. 滤波(Filtering)
        1. High Dynamic Range(HDR)
    • 结构
        1. 并联
        1. 串联
    • 代码
    • 总结
  • 六、Efficient channel attention-ECA注意力
    • 关键词
    • 提出目的
    • 核心思想
    • 工作流程
    • 为什么 ECA 更高效?
    • 代码
        1. 全局平均池化
        1. 自适应卷积核
        • 自适应卷积核大小计算
        • 前向传播
        1. sigmoid归一化
        1. 加权
    • ECA的优缺点
      • 优点:
      • 缺点:
  • 七、Triplet attention (TA)
    • 关键词
    • 什么是TA注意力机制
    • 核心思想
    • 主要机制
    • 代码详解
      • ZPool 类
      • AttentionGate 类
      • TripletAttention 类

注意力机制根据施加的维度大致可以分为两类:通道注意力和空间注意力。

  • 对于通道注意力机制,代表性的工作有SENe、ECANet[;
  • 对于空间注意力机制,代表性的工作有Self-Attention。
  • 空间和通道两个维度的双重注意力机制也被提出,代表工作有CBAM,DANet。

一、SE(Squeeze-and-excitation)注意力

什么是SE注意力?

想象一下你在看一幅图像。图像中有很多不同的特征,比如颜色、纹理、形状等等。在某些情况下,一些特征比其他特征更重要。例如,在识别猫的图像时,猫的耳朵和眼睛可能比它的尾巴更重要。SE注意力机制就是一种方法,可以让模型自动"关注"这些重要特征,而"忽略"不那么重要的部分。

SE注意力的步骤

SE注意力机制的过程可以简单地分为三个步骤:压缩、激励和重标定。下面我们用简单的例子来解释这三个步骤。

1. 压缩(Squeeze)

想象你有一张图像,由不同的颜色通道(如红色、绿色和蓝色)组成。我们首先想要找出每个颜色通道的重要性。

使用***全局平均池化(Global Average Pooling)***,我们将每个通道的所有像素值取平均,得到每个通道的一个"分数"。比如,对于红色通道,我们会计算所有像素的平均值,得到一个代表红色的重要性的分数。

这样,我们就从一个复杂的特征图中"压缩"成了一个较小的向量 ,这个向量中每个值代表一个通道的重要性

在代码中使用nn.AdaptiveAvgPool2d来实现。

python 复制代码
self.avg_pool = nn.AdaptiveAvgPool2d(1)

2. 激励(Excitation)

接下来,我们想要根据这些"分数"来调整每个通道的权重。

我们使用一个小的神经网络(可以想象成一个简单的计算模型)来学习这些分数之间的关系。神经网络会将这些分数输入进来,并通过一些计算(比如使用ReLU和Sigmoid函数)生成每个通道的最终权重。

给定输入特征,SE首先对每个通道独立的使用全局平均池化,然后使用两个具有非线性的全连接(FC)层 ,紧接着用一个Sigmoid函数来生成通道权重。两个FC层旨在捕获非线性跨通道交互,涉及到降维以控制模型复杂度。

但实证研究表明,降维会给通道注意力预测带来副作用,并且捕获所有通道之间的依赖关系是低效且不必要的。(后续的ECA注意力给出解决方案)

这个过程实际上就是在"激励"重要的通道,让它们的分数更高,而"抑制"不重要的通道。

在代码中使用Linear和激活层交替叠加来实现。

python 复制代码
self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel, bias=False),
            nn.Sigmoid()
        )

3. 重标定(Scale/Reweight)

最后,我们将这些权重应用回原始的特征图。

将每个通道的特征图乘以对应的权重(x*weight),得到一个新的特征图。这意味着,重要的通道会被"放大",而不重要的通道会被"减弱"。

结构

SE注意力机制的整个过程可以理解为:

  1. 压缩:从复杂的特征图中提取每个通道的重要性分数。
  2. 激励:通过神经网络学习到每个通道的权重。
  3. 重标定:调整特征图,使得重要特征更加突出。

这种方法让神经网络能够更好地聚焦于最重要的特征,进而提升模型的性能。

代码

python 复制代码
"""
Original paper addresshttps: https://arxiv.org/pdf/1709.01507.pdf
Code originates from https://github.com/moskomule/senet.pytorch/blob/master/senet/se_module.py
"""
import torch
from torch import nn
 
class SELayer(nn.Module):
    def __init__(self, channel, 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 forward(self, x):
        b, c, _, _ = x.size()
        # [8, 64, 40,40]->[8,64,1,1]->[8,64]
        y = self.avg_pool(x).view(b, c)
        # fc中第一个nn.Linear降维,通道数从64变为4,形状变为[8,4]
        # ReLU 激活函数应用于 [8, 4]
        # 第二个 nn.Linear 层将通道数恢复到 64,结果为 [8, 64]
        # view(b, c, 1, 1)将 y 重新调整为 [8, 64, 1, 1],以便进行广播操作。
        y = self.fc(y).view(b, c, 1, 1)
        # y.expand_as(x) 将 y 的形状从 [8, 64, 1, 1] 扩展为 [8, 64, 40, 40],与 x 的形状匹配
        # 将 y 与输入 x 逐通道相乘,得到输出特征图,形状为 [8, 64, 40, 40]
        return x * y.expand_as(x)
 
 
if __name__ == '__main__':
    input = torch.randn(8, 64, 40, 40)
    se = SELayer(channel=64, reduction=8)
    output = se(input)
    print(output.shape)

二、CBAM(Convolutional Block Attention Module)

什么是CBAM注意力?

CBAM是一种结合了通道注意力空间注意力的机制,旨在让卷积神经网络在处理特征时能够更加专注于重要的信息。可以把CBAM想象成一个"聪明的助手",它帮助模型更好地识别哪些特征是重要的,哪些是可以忽略的。

CBAM的组成部分

CBAM分为两个主要部分:通道注意力(Channel Attention)和空间注意力(Spatial Attention)。我们将一步一步来了解这两个部分。

1. 通道注意力(Channel Attention)

目的: 让模型学习哪些通道更重要。

  • 全局平均池化和全局最大池化: 首先,我们对输入特征图进行全局平均池化和全局最大池化。这两个步骤将每个通道的信息压缩成一个数值。平均池化关注通道的平均值,而最大池化则关注通道中的最大值。
  • 合并特征: 将这两个池化结果拼接在一起,形成一个新的特征向量。
    全连接层:将这个特征向量输入到一个小型的全连接网络中,以学习通道之间的关系。经过两层全连接层,得到每个通道的权重。
  • 重标定: 将得到的权重应用到原始特征图的通道上,强调重要通道,抑制不重要的通道。
python 复制代码
class ChannelAttention(nn.Module):
    """
    CBAM混合注意力机制的通道注意力
    """

    def __init__(self, in_channels, ratio=16):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.fc = nn.Sequential(
            nn.Conv2d(in_channels, in_channels // ratio, 1, bias=False),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels // ratio, in_channels, 1, bias=False)
        )

        self.sigmoid = nn.Sigmoid()

   def forward(self, x):
       avg_out = self.fc(self.avg_pool(x))
       max_out = self.fc(self.max_pool(x))
       out = avg_out + max_out
       out = self.sigmoid(out)
       return out * x

2. 空间注意力(Spatial Attention)

目的: 让模型学习在特征图中的哪些区域更重要。

  • 最大池化和平均池化: 对经过通道注意力处理的特征图进行通道压缩,可以使用最大池化和平均池化,将每个位置的通道信息压缩成一个特征图。
  • 生成空间特征:将两个池化后的特征图拼接在一起,形成一个新的空间特征图。
  • 卷积层:将这个空间特征图输入到一个卷积层中,以生成空间注意力权重。
  • Sigmoid激活: 类似于通道注意力模块,对生成的空间注意力权重应用Sigmoid激活函数,将权重限制在0到1之间。
  • 重标定:将空间权重应用到特征图的空间上,强调重要的空间区域,抑制不重要的区域。
python 复制代码
class SpatialAttention(nn.Module):
    """
    CBAM混合注意力机制的空间注意力
    """

    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()

        assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
        padding = 3 if kernel_size == 7 else 1
        self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        out = torch.cat([avg_out, max_out], dim=1)
        out = self.sigmoid(self.conv1(out))
        return out * x

3. 混合注意力

CBAM就是将通道注意力模块和空间注意力模块的输出特征逐元素相乘,得到最终的注意力增强特征。这个增强的特征将用作后续网络层的输入,以在保留关键信息的同时,抑制噪声和无关信息。

原文实验证明先进行通道维度的整合,再进行空间维度的整合,模型效果更好。

代码

python 复制代码
class CBAM(nn.Module):
    """
    CBAM混合注意力机制
    """

    def __init__(self, in_channels, ratio=16, kernel_size=3):
        super(CBAM, self).__init__()
        self.channelattention = ChannelAttention(in_channels, ratio=ratio)
        self.spatialattention = SpatialAttention(kernel_size=kernel_size)

    def forward(self, x):
        x = self.channelattention(x)
        x = self.spatialattention(x)
        return x

缺陷

1. 计算复杂度高

CBAM在进行通道和空间注意力计算时,使用了全局池化和卷积操作。虽然相对简单的池化和卷积比自注意力机制(如Transformer)更轻量,但对于需要极低延迟的应用或嵌入式设备,CBAM的多步处理流程(先通道后空间注意力)仍然增加了计算复杂度。尤其在处理高分辨率特征图时,这种复杂性更加明显。

2. 通道和空间注意力分离处理的局限

CBAM是将通道注意力和空间注意力分开计算的,先计算通道注意力,再计算空间注意力。然而这种方式没有充分考虑通道和空间之间的耦合关系。 由于通道和空间信息是相互关联的,分离计算会丢失一些原始特征中的细粒度信息。在某些精细特征识别任务中,比如图像分割或关键点检测,这种方式可能会限制模型性能。

3. 空间注意力的局限

CBAM中的空间注意力机制仅关注每个像素位置的"存在性"而非其具体位置关系。 它使用池化操作和卷积生成空间注意力,但不保留像素间的精确位置信息。因此,在需要精确空间位置关系的任务中(如小目标检测或物体的精细分割),CBAM可能不够理想,缺少类似坐标注意力(Coordinate Attention)那样的空间信息建模能力。

4. 适应性较差

CBAM是一个固定结构,难以根据不同的任务或输入自动调整。对于不同任务,可能需要不同的注意力侧重(例如某些任务对通道信息更敏感,而某些任务对空间信息更敏感)。然而CBAM不能动态调节其通道和空间注意力的权重或重要性, 导致模型在适应性上有所欠缺。

5. 空间维度的特征提取受限

CBAM中采用的空间注意力机制主要通过二维卷积来捕获局部空间信息,而不像一些自注意力机制那样可以有效地捕获全局上下文信息。在全局特征建模上可能略显不足,这在需要大范围上下文信息的任务中可能会导致性能下降。

总结

通道注意力:通过全局平均池化和最大池化来学习每个通道的重要性,然后对原始特征图的通道进行加权。

空间注意力:通过对特征图进行通道压缩,生成空间特征,然后生成空间注意力权重,再对特征图的空间位置进行加权。

三、SGE

特征分组思想: 降低一次性处理的通道个数,分组后的特征图通常会更小,计算时也仅需要存储和处理每个组的较小部分数据,从而减少了内存占用和数据传输的开销。

什么是SGE注意力?

论文:Spatial Group-wise Enhance: Improving Semantic Feature Learning in Convolutional Networks

SGE注意力机制的核心思想是通过对特征图的通道进行分组,然后针对每个分组独立地增强空间特征。SGE先将输入的特征图分成多个子组,然后为每个子组计算一个加权的空间注意力,这样可以使得不同子组的特征在空间上得到不同的关注。

工作流程

  1. 特征分组:把通道划分成多个组。每组中的通道会独立处理,这样可以减少计算复杂度。
  2. 空间增强:对每个组内的特征图在空间维度上计算注意力权重(加权),从而增强那些对任务更重要的区域。

代码详解

python 复制代码
 
# Spatial Group-wise Enhance主要是用在语义分割上,所以在检测上的效果一般,没有带来多少提升
import torch
from torch import nn
from torch.nn import init
 
 
class SpatialGroupEnhance(nn.Module):
 
    def __init__(self, groups):
        super().__init__()
        self.groups = groups
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.weight = nn.Parameter(torch.zeros(1, groups, 1, 1))
        self.bias = nn.Parameter(torch.zeros(1, groups, 1, 1))
        self.sig = nn.Sigmoid()
        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=2,c=32,h=w=512,group=8
        b, c, h, w = x.shape
        # 
        x = x.view(b * self.groups, -1, h, w)  # bs*g,dim//g,h,w
        
        xn = x * self.avg_pool(x)  # bs*g,dim//g,h,w
        xn = xn.sum(dim=1, keepdim=True)  # bs*g,1,h,w
        t = xn.view(b * self.groups, -1)  # bs*g,h*w
 
        t = t - t.mean(dim=1, keepdim=True)  # bs*g,h*w
        std = t.std(dim=1, keepdim=True) + 1e-5
        t = t / std  # bs*g,h*w
        t = t.view(b, self.groups, h, w)  # bs,g,h*w
 
        t = t * self.weight + self.bias  # bs,g,h*w
        t = t.view(b * self.groups, 1, h, w)  # bs*g,1,h*w
        x = x * self.sig(t)
        x = x.view(b, c, h, w)
        return x
 
if __name__ == '__main__':
    input = torch.randn(2, 32, 512, 512)
    SGE = SpatialGroupEnhance(groups=8)
    output = SGE(input)
    print(output.shape)
  1. 分组
    x = x.view(b * self.groups, -1, h, w):将输入的特征图通过 view 开成多个组(每组大小为 c // groups),这里 b 是批量大小,c 是通道数,h 和 w 是特征图的空间尺寸。

b=2,c=32,h=w=512,group=8, 这一步会把特征图转换成形状为[2*8,32/8,512,512]=[16,4,512,512],-1代表自动计算c//g的值

  1. 全局平均池化
    xn = x * self.avg_pool(x) :对每个通道组进行全局平均池化 ,并与原特征图相乘。这个操作会根据每个通道组的全局信息来增强它们的空间特征。

平均池化self.avg_pool(x):[16,4,512,512]->[16,4,1,1],

池化后与原始特征图相乘来增强特征:[16,4,512,512]*[16,4,1,1]->[16,4,512,512]

  1. 沿着通道维度(dim=1)对特征进行求和并转换为二维向量
    (1) xn.sum(dim=1, keepdim=True): 将每个通道组的结果沿通道维度求和。形状由[16,4,512,512]->[16,1,512,512]
    (2) t = xn.view(b * self.groups, -1): 将 xn 展开成二维张量,准备后续的标准化操作。

使用 .view(b * self.groups, -1) 将其展平为 (b * self.groups, h * w),即 [16, 512 * 512]=[16,262144]

  1. 计算标准化
    对 t 进行标准化,减去均值并除以标准差,以确保数值稳定性
    (1) t = t - t.mean(dim=1, keepdim=True): 对每一行(即每个通道组)进行均值去除。
    (2) std = t.std(dim=1, keepdim=True) + 1e-5: 计算每一行(即每个通道组)的标准差,并加上一个小常数防止除以零。
    (3) t = t / std: 对 t 进行标准化。

形状为[16, 262144]

  1. 把t的维度恢复为4维向量并进行缩放和偏移处理
    (1)t = t.view(b, self.groups, h, w) :将标准化后的结果恢复成 (b, groups, h, w) 的形状。
    (2)t = t * self.weight + self.bias:将标准化后的 t 乘以学习到的权重,并加上偏置。(不改变形状)

[16, 262144] -> [2,8,512,512]-> [2,8,512,512]

  1. 转变形状以便与原始特征图相乘。
    t = t.view(b * self.groups, 1, h, w):将 t 转换为 (b * self.groups, 1, h, w) 形状,以便与原始特征图相乘。

[2,8,512,512]->[16,1,512,512]

  1. 注意力权重与特征图相乘并回复原来形状
    (1)x = x * self.sig(t) :将计算得到的空间注意力权重 t 通过Sigmoid激活函数进行规范化,将 t 转换到 [0, 1] 范围内,并将其与原特征图相乘。 [16, 4, 512, 512]*[16,1,512,512]->[16, 4, 512, 512]
    (2)x = x.view(b, c, h, w):最后将增强后的特征图恢复到原来的形状。[16, 4, 512, 512]->[2, 32, 512, 512]

缺陷

1. 计算成本较高

分组操作 :SGE将通道分成多个组进行处理,虽然这种方式有助于提高特征表达能力,但在分组后需要对每个组分别进行操作。这意味着需要执行更多的计算来分别处理每个子集,增加了计算成本,尤其是在大规模输入图像时。
计算的增加:在计算标准差和进行归一化等操作时,可能需要对每个组进行处理,这样的处理方式使得计算复杂度呈现线性增长,尤其在每个组的数量较多时。

2. 可能会丢失全局信息

局部信息强化:SGE的设计思想是增强局部的空间特征,通过对每个组独立进行操作来提升空间分布。这样做的目的是增强局部信息的表示能力,但同时可能会忽视一些全局特征。因为每个组只关注自己内部的特征,而没有直接对整个输入进行全局信息的捕捉,这可能导致全局上下文信息的丢失。

3. 对于检测任务的表现有限

SGE主要是在语义分割任务中得到较好效果,因为语义分割要求对每个像素点的细粒度信息进行优化。而在检测任务中,目标的定位和识别更多依赖于全局特征的聚合与跨尺度特征的融合。因此,SGE在检测任务中可能没有带来预期的提升。

4. 依赖于组的选择

超参数设置:SGE中groups的选择是一个关键的超参数,它决定了分组的数量。这个超参数的选择对模型的表现有很大影响。如果组数选择不合适(过多或过少),可能会影响模型的学习效果,导致过拟合或欠拟合。因此,在实际应用中需要通过调参来找到合适的groups数量,增加了模型的调优难度。

四、CA(Coordinate Attention)

关键词

坐标映射,融合空间信息

什么是CA注意力?

在传统的卷积神经网络中,卷积操作只能局部处理特征图的信息,而不像人类一样能全面理解图像中的空间布局。

即使使用了像SE(Squeeze-and-Excitation)等注意力机制来增强模型对通道的感知,它们通常忽略了空间维度的信息;CBAM中引入了大尺度的卷积核提取空间特征,但忽略了长程依赖问题。

而Coordinate Attention则通过特别的方式融入了空间信息,使得网络能够更好地理解图像中每个位置的特征,并在此基础上做出更精准的决策。

工作原理

Coordinate Attention机制的核心在于通过坐标系统来增强特征图的空间感知力。它通过以下两个步骤来完成:

1. 坐标映射: CA通过构建基于空间坐标的特征图,将图像的每个位置的坐标信息与原始特征进行结合。这样,网络不仅能学习每个像素的特征,还能学习它在空间上的位置关系。

2. 空间上下文融合: 通过对坐标信息进行特征融合,CA让网络学习到图像中不同位置之间的依赖关系。这样,模型不仅仅是对单个像素进行处理,而是对整个空间结构的理解更加精细。

结构

CA的结构通常由以下几个部分组成:

  1. 生成坐标信息:生成空间坐标,即图像中每个位置的具体坐标(例如:横坐标和纵坐标)。这帮助网络了解每个像素的空间位置。

  2. 特征与坐标融合:坐标信息和原始特征图进行融合,通过特定的操作将这些坐标信息注入到特征中,让网络能够理解每个像素和其周围环境的关系。

  3. 注意力机制:CA使用注意力机制来强调对空间重要性的学习,帮助网络更加关注重要的空间区域,同时忽略不重要的部分。

代码详解

python 复制代码
class CoordAtt(nn.Module):
    def __init__(self, inp, oup, groups=32):
        super(CoordAtt, self).__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))

        mip = max(8, inp // groups)

        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.conv2 = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
        self.conv3 = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
        self.relu = h_swish()

    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.relu(y) 
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)

        x_h = self.conv2(x_h).sigmoid()
        x_w = self.conv3(x_w).sigmoid()
        x_h = x_h.expand(-1, -1, h, w)
        x_w = x_w.expand(-1, -1, h, w)

        y = identity * x_w * x_h

        return y

假设输入的特征图大小为[8,256,48,60]

步骤 1:分离空间信息

x_h = self.pool_h(x):对输入 x 进行高度方向的池化,得到 (batch_size, channels, height, 1) 的特征图。

shape->[8,256,48,1]

x_w = self.pool_w(x).permute(0, 1, 3, 2):在宽度方向上进行池化后,使用 .permute(0, 1, 3, 2) 将其转置为 (batch_size, channels, 1, width)

shape->[8,256,1,60]

步骤 2:融合空间特征

y = torch.cat([x_h, x_w], dim=2):将 x_h 和 x_w 在高度和宽度方向进行拼接,得到 (batch_size, channels, height + width, 1)。

shape->[8,256,108,1]

y = self.conv1(y) 和 y = self.bn1(y):使用卷积和批归一化处理特征,使它们更易于激活和学习。

该卷积将通道数从 256 缩减到 mip,假设 groups=32,则 mip = max(8, 256 // 32) = 8。因此 y 的形状变为 [8, 8, 108, 1]

y = self.relu(y):通过激活函数 h_swish() 进一步增强特征

shape->[8,8,108,1]

步骤 3:生成坐标注意力

x_h, x_w = torch.split(y, [h, w], dim=2):将 y 分割为两部分,分别用于高度和宽度方向的特征。

x_h->[8,8,48,1]和x_w->[8,8,60,1]

x_w = x_w.permute(0, 1, 3, 2):再次转置 x_w 以匹配宽度方向的注意力图形状。

x_w->[8,8,1,60]

x_h = self.conv2(x_h).sigmoid() 和 x_w = self.conv3(x_w).sigmoid():通过卷积和 sigmoid 激活生成 (batch_size, channels, height, 1) 和 (batch_size, channels, 1, width) 的注意力权重。

卷积操作将 x_h 的通道数从 8 调整回 256,并通过 sigmoid 激活,因此 x_h 的形状为 [8, 256, 48, 1]

卷积操作将 x_w 的通道数从 8 调整回 256,并通过 sigmoid 激活,因此 x_h 的形状为 [8, 256, 1,60]

扩展:x_h 和 x_w 分别通过 .expand() 扩展至 (batch_size, channels, height, width)。

x_h = x_h.expand(-1, -1, 48, 60):将 x_h 扩展为 [8, 256, 48, 60]

x_w = x_w.expand(-1, -1, 48, 60):将 x_w 扩展为 [8, 256, 48, 60]

步骤 4:应用注意力并输出

y = identity * x_w * x_h:将 x_h 和 x_w 的注意力权重作用到原始输入 x 上,生成最终的增强特征。

y_shape: [8, 256, 48, 60]

缺陷

1. 依赖全局信息,适合大尺度特征图:

CA通过全局池化来获取特征图的全局信息,以实现对每个通道和空间位置的精确注意。然而,这种方式在小尺度特征图上可能无法有效捕捉局部信息。对于特征图分辨率较低的场景,CA的效果可能会减弱。

2. 额外的计算开销:

CA需要进行两个独立方向(高度和宽度)的全局池化以及多个卷积操作,这增加了计算复杂度,尤其在大通道数或高分辨率的特征图上,可能导致推理时间增加。

3. 信息融合方式的局限性:

CA主要通过沿高度和宽度方向的全局池化来分解空间注意力,但这种分解方式可能会忽略部分复杂的空间关系。相较于同时考虑空间和通道的3D注意力机制(如非局部注意力),CA在某些场景下可能无法充分表达复杂的空间依赖关系。

4. 对特征分组数的敏感性:

CA的设计中通常会涉及到通道分组,分组数量的选择对性能影响较大。如果分组设置不当,可能会导致效果下降或者注意力权重难以收敛。此外,分组的方式可能无法很好地适应所有输入数据特征。

总结

Coordinate Attention机制通过引入空间坐标信息,使得网络能够在处理特征图时不仅关注每个像素的特征值,还能结合其空间位置,理解每个像素与其他像素的关系。这种机制提升了网络对空间结构的感知,尤其在图像中的不同位置有重要信息时,能够有效地增强模型的性能。

五、Polarized Self-Attention(PSA)注意力

关键词

双重注意力、极化

研究目的

PSA和CBAM一样是双重注意力机制。引入了一种"极化"策略,将通道信息和空间信息分离开来分别处理,从而更高效地捕捉特征。

和CBAM一样,对通道注意力和空间注意力进行组合。不同的是CBAM等双重注意力的结构,大多都是用全连接层、卷积层来获取注意力权重,所以在挖掘信息的时候可能不是非常有效。而本文采用的是Self-Attention来获取注意力权重,充分了利用了Self-Attention结构的建模能力,并且作者对Q也进行了特征降维,所以在保证计算量的情况下,实现了一种非常有效的长距离建模。

思想

为了解决同时对空间和通道建模时,如果不进行维度缩减,就会导致计算量、显存爆炸的问题。作者在PSA中采用了一种极化滤波(polarized filtering)的机制。先在一个方向上对特征进行压缩,然后对损失的强度范围进行提升。

1. 滤波(Filtering)

使得一个维度的特征(比如通道维度)完全坍塌,同时让正交方向的维度(比如空间维度)保持高分辨率。

  • 将输入特征图的某一个维度完全压缩。例如,在通道注意力中会将空间维度进行降维,而在空间注意力中则会将通道维度降维。

  • 这种压缩操作的目的是减少计算量,避免同时处理高维的空间和通道特征所带来的显存和计算负担。

  • 这样可以专注于对一个特定方向上的特征进行建模,保持高分辨率的输出,便于捕获复杂的空间或通道信息。

2. High Dynamic Range(HDR)

在极化滤波的过程中,由于压缩操作,特征图可能会损失部分信息强度,为了补偿这一损失,PSA引入了HDR增强来扩大注意力范围,具体步骤如下:

  • Softmax:首先在attention机制中较小的特征图上使用Softmax操作,这样做可以确保对重要的特征赋予更高的注意力权重。
  • Sigmoid映射:再使用Sigmoid函数对特征进行动态映射,将原本压缩后的特征值重新映射到适当的范围,使得模型能够恢复一些丢失的细节信息。
  • 这相当于增加了注意力机制的响应范围,确保在滤波之后的特征图依旧包含足够的细节和对比度。

结构

1. 并联

2. 串联

代码

【Yolov5】涨点亲测有效,Yolov5添加PSA极化自注意力机制

python 复制代码
class PSA_Channel(nn.Module):
    def __init__(self, c1) -> None:
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = nn.Conv2d(c1, c_, 1)
        self.cv2 = nn.Conv2d(c1, 1, 1)
        self.cv3 = nn.Conv2d(c_, c1, 1)
        self.reshape1 = nn.Flatten(start_dim=-2, end_dim=-1)
        self.reshape2 = nn.Flatten()
        self.sigmoid = nn.Sigmoid()
        self.softmax = nn.Softmax(1)
        self.layernorm = nn.LayerNorm([c1, 1, 1])

    def forward(self, x): # shape(batch, channel, height, width)
        x1 = self.reshape1(self.cv1(x)) # shape(batch, channel/2, height*width)
        x2 = self.softmax(self.reshape2(self.cv2(x))) # shape(batch, height*width)
        y = torch.matmul(x1, x2.unsqueeze(-1)).unsqueeze(-1) # 高维度下的矩阵乘法(最后两个维度相乘)
        return self.sigmoid(self.layernorm(self.cv3(y))) * x

class PSA_Spatial(nn.Module):
    def __init__(self, c1) -> None:
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = nn.Conv2d(c1, c_, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1)
        self.reshape1 = nn.Flatten(start_dim=-2, end_dim=-1)
        self.globalPooling = nn.AdaptiveAvgPool2d(1)
        self.softmax = nn.Softmax(1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x): # shape(batch, channel, height, width)
        x1 = self.reshape1(self.cv1(x)) # shape(batch, channel/2, height*width)
        x2 = self.softmax(self.globalPooling(self.cv2(x)).squeeze(-1)) # shape(batch, channel/2, 1)
        y = torch.bmm(x2.permute(0,2,1), x1) # shape(batch, 1, height*width)
        return self.sigmoid(y.view(x.shape[0], 1, x.shape[2], x.shape[3])) * x

class PSA(nn.Module):
    def __init__(self, in_channel, parallel=True) -> None:
        super().__init__()
        self.parallel = parallel
        self.channel = PSA_Channel(in_channel)
        self.spatial = PSA_Spatial(in_channel)

    def forward(self, x):
        if(self.parallel):
            return self.channel(x) + self.spatial(x)
        return self.spatial(self.channel(x))

总结

这篇文章提出了一种极化自注意力机制,通过在空间和通道维度内部保持比较高的resolution,并采用了Softmax-Sigmoid联合的非线性函数,使得带有极化自注意力机制的模型,能够在pixel-wise的任务上获得更好的性能。

六、Efficient channel attention-ECA注意力

关键词

自适应卷积大小、无需降维

提出目的

通道缩减 在提取深度视觉表征时可能带来副作用,因此避免降维对学习通道注意力很重要,ECA提出一种无需降维的局部跨通道交互策略,适当的跨通道交互可以在显著降低模型复杂度的同时提高性能。

核心思想

传统的SE注意力机制中会对每个通道计算全局信息,并通过两个全连接层生成通道的权重。然而,全连接层会增加参数量,并且会导致不同通道之间的信息缺失。ECA提出了一种更高效的方法,即通过一维卷积替代全连接层,并仅使用一个可调节的卷积核大小来捕获通道之间的依赖关系。

主要是对SENet通道注意力机制进行改进,去掉了全连接层,在全局平均池化后街上一个一维卷积层。

工作流程

1. 全局平均池化

ECA首先对输入特征图的每个通道进行全局平均池化,得到每个通道的全局特征。

假设输入特征图形状为 (B, C, H, W)(批量大小、通道数、高度、宽度),经过全局平均池化后,特征图变成 (B, C, 1, 1),代表每个通道的全局信息。

2. 自适应大小的一维卷积

在没有降维的通道全局平均池化之后,ECANet使用一维卷积来实现跨通道信息交互,而卷积核的大小通过函数来自适应,内核大小 k 表示局部跨通道交互的覆盖范围,即有多少邻居参与了一个通道的注意力预测。 例如,使用核大小为3的卷积能够考虑到一个通道与其相邻两个通道之间的依赖。

3. 激活和重加权

经过卷积后的结果通过 Sigmoid 激活函数,生成每个通道的注意力权重。这些权重用于调整原输入特征图的通道,即每个通道的特征被相应的注意力权重重新加权。

为什么 ECA 更高效?

  • 没有全连接层:ECA避免了全连接层的使用,从而减少了大量参数和计算量。
  • 局部依赖:一维卷积核大小可以灵活调整,能够很好地控制不同通道之间的依赖范围,保持通道间必要的相互关系,同时减少了计算量。
  • 无需额外参数:卷积核大小可根据通道数自动调整,不需要额外参数,这使得模型更加轻量化。

代码

python 复制代码
import torch
from torch import nn
import math
 
class eca_block(nn.Module):
    def __init__(self, channel, gamma=2, b=1):
        super(eca_block, self).__init__()
        kernel_size = int(abs((math.log(channel, 2) + b) / gamma))
        kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
        padding = kernel_size // 2
 
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.conv = nn.Conv1d(1, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()
 
    def forward(self, x):
        b, c, h, w = x.size()
 
        avg = self.avg_pool(x).view([b, 1, c])
        out = self.conv(avg)
        out = self.sigmoid(out).view([b, c, 1, 1])
        return out * x
 
model = eca_block(512)
print(model)
inputs = torch.ones([2, 512, 26, 26])
outputs = model(inputs)

1. 全局平均池化

avg = self.avg_pool(x): 平均池化,输入从[2, 512, 26, 26]-> [2,512,1,1]

view([b, 1, c]: 特征重新调整为三维,便于后续一维卷积操作。

[2,512,1,1]-> [2,1,512]

2. 自适应卷积核

自适应卷积核大小计算
python 复制代码
kernel_size = int(abs((math.log(channel, 2) + b) / gamma))
kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1
padding = kernel_size // 2

计算了一维卷积的 kernel_size,用于捕获通道之间的依赖关系。卷积核大小基于输入的 channel 数,通过公式 kernel_size = |log2(channel) + b| / gamma 计算得出。

kernel_size 应为奇数,因此如果计算结果是偶数,则加 1,以确保卷积核居中对齐。padding 是 kernel_size // 2,用于在一维卷积操作时保持特征图大小不变。

前向传播

out = self.conv(avg): 输入 [2, 1, 512] 通过一维卷积 self.conv 处理,卷积核大小为 kernel_size(根据 channel 动态计算)。

卷积输出的形状保持不变,为 [2, 1, 512]。

3. sigmoid归一化

out = self.sigmoid(out): 将一维卷积结果通过 Sigmoid 激活函数,使其在 [0, 1] 范围内,形状仍为 [2, 1, 512]。

view([b, c, 1, 1]) : 将其调整为 [2, 512, 1, 1],以便与输入 x 逐通道相乘。

4. 加权

out * x: 将注意力权重 out(形状 [2, 512, 1, 1])与输入 x(形状 [2, 512, 26, 26])逐通道相乘,生成最终输出。

输出形状为 [2, 512, 26, 26],与输入形状一致,但经过了注意力增强。

ECA的优缺点

优点:

  • 计算高效:没有全连接层和额外参数,使得它在计算资源有限的设备上表现良好。
  • 性能优异:在多个视觉任务中表现出色,尤其是对于轻量级模型。

缺点:

  • 局部依赖限制:一维卷积只能关注局部的通道信息,对非常深层的通道特征的全局依赖考虑较少

七、Triplet attention (TA)

关键词

多维交互,不降低维度

什么是TA注意力机制

TA通过三个不同的视角来分析输入的数据,就好比三个人从不同的角度来观察同一幅画,然后共同决定哪些部分最值得注意。三重注意力机制的

主要思想是在网络中引入了一种新的注意力模块,这个模块包含三个分支,分别关注图像的不同维度。

比如说,一个分支可能专注于图像的宽度,另一个分支专注于高度,第三个分支则聚焦于图像的深度,即色彩和纹理等特征。这样一来,网络就能够更全面地理解图像内容,就像是得到了一副三维眼镜,能够看到图片的立体效果。

核心思想

通过结合跨空间和跨通道的信息 增强深度学习模型对目标特征的关注。它的核心理念是对输入特征图沿三个不同的维度计算注意力,分别是水平维度、垂直维度和通道维度。通过这种方式,TA 能够更全面地捕获空间和通道信息的交互,从而改善模型的特征表达能力。

主要机制

Triplet Attention 主要通过以下三步来实现多维注意力:

  • 水平注意力(Horizontal Attention):将输入特征图沿水平方向进行池化(通常是全局平均池化),并计算通道注意力。这样可以捕获特征图在水平维度上的全局信息。

  • 垂直注意力(Vertical Attention):类似地,将输入特征图沿垂直方向池化,并计算通道注意力。通过垂直维度的关注,模型能够了解特征图中垂直方向的全局信息。

  • 通道注意力(Channel Attention):直接对特征图的通道维度应用注意力,捕捉通道间的相关性。

将这些三个分支的注意力机制整合起来,形成对每个像素点的加权组合,从而在多维度上关注特征的显著部分。

代码详解

python 复制代码
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

ZPool 类

目标:从输入特征图中提取通道维度的最大值和平均值,并将它们拼接起来,生成一个新的特征图。

操作

  1. 对输入 x 沿通道维度(dim=1)进行最大池化和平均池化。
  2. 将两个池化结果在通道维度上拼接,得到一个新的特征图。
python 复制代码
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 )

形状变化:

输入:x 的形状为 [8, 64, 40, 40]。

最大池化:torch.max(x,1)[0] 计算每个位置在通道维度上的最大值,结果形状为 [8, 40, 40]。

平均池化:torch.mean(x,1) 计算每个位置在通道维度上的平均值,结果形状为 [8, 40, 40]。

拼接:torch.cat 将最大值和平均值在通道维度上拼接,最终输出形状为 [8, 2, 40, 40]

AttentionGate 类

python 复制代码
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

目标:生成一个注意力图,通过该图对输入特征图进行加权,强调重要特征。

操作:

  1. 使用 ZPool 提取通道维度上的最大值和平均值,得到形状为 [8, 2, 40, 40] 的特征图。

  2. 通过 BasicConv 卷积层将通道数从 2 转换为 1,作用类似于在特征图的不同统计信息间进行融合和压缩。通过 7x7 的卷积核(较大的感受野),模型能够捕捉到局部区域的重要性,进一步生成一个注意力图。

  3. 通过 Sigmoid 激活函数将注意力图的值压缩到 [0, 1] 范围内。

    将注意力图与原始输入 x 逐元素相乘,得到加权后的输出特征图。

TripletAttention 类

python 复制代码
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
相关推荐
迅易科技25 分钟前
借助腾讯云质检平台的新范式,做工业制造企业质检的“AI慧眼”
人工智能·视觉检测·制造
古希腊掌管学习的神1 小时前
[机器学习]XGBoost(3)——确定树的结构
人工智能·机器学习
ZHOU_WUYI2 小时前
4.metagpt中的软件公司智能体 (ProjectManager 角色)
人工智能·metagpt
靴子学长3 小时前
基于字节大模型的论文翻译(含免费源码)
人工智能·深度学习·nlp
梧桐树04293 小时前
python常用内建模块:collections
python
AI_NEW_COME3 小时前
知识库管理系统可扩展性深度测评
人工智能
Dream_Snowar4 小时前
速通Python 第三节
开发语言·python
海棠AI实验室4 小时前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
hunteritself4 小时前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
IT古董5 小时前
【机器学习】机器学习的基本分类-强化学习-策略梯度(Policy Gradient,PG)
人工智能·机器学习·分类