万字长文解读空间、通道注意力机制机制和超详细代码逐行分析(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
相关推荐
我是李武涯2 分钟前
PyTorch Dataloader工作原理 之 default collate_fn操作
pytorch·python·深度学习
AI视觉网奇15 分钟前
Python 检测运动模糊 源代码
人工智能·opencv·计算机视觉
东隆科技16 分钟前
PRIMES推出SFM 2D全扫描场分析仪革新航空航天LPBF激光增材制造
人工智能·制造
无风听海25 分钟前
神经网络之计算图repeat节点
人工智能·深度学习·神经网络
刘晓倩41 分钟前
在PyCharm中创建项目并练习
人工智能
Kratzdisteln43 分钟前
【Python】绘制椭圆眼睛跟随鼠标交互算法配图详解
python·数学·numpy·pillow·matplotlib·仿射变换
Dev7z1 小时前
阿尔茨海默病早期症状影像分类数据集
人工智能·分类·数据挖掘
神码小Z1 小时前
DeepSeek再开源3B-MoE-OCR模型,视觉压缩高达20倍,支持复杂图表解析等多模态能力!
人工智能
maxruan1 小时前
PyTorch学习
人工智能·pytorch·python·学习
吃饭睡觉发paper1 小时前
Learning Depth Estimation for Transparent and Mirror Surfaces
人工智能·机器学习·计算机视觉