各类神经网络学习:(十二)注意力机制(第4/4集),超详细代码解析,并附有图解

上一篇下一篇
注意力机制(3/4集)待编写

一、代码

在 pytorch 中,貌似只有一个多头注意力机制包,其他类型的注意力机制都需要自定义。
这里先给出一些常用的注意力机制代码,位置编码等代码会在后续的 Transformer 讲解中给出。

下方代码的核心都是:生成权重。并且 Q 、 K 、 V Q、K、V QKV 三个参数一般是用在自注意力机制里面的,其他类型的注意力机制有所不同。

首先,在图像领域,不考虑 batch_size 的话,一个图片有 3 3 3 维,分别是 (c,h,w) (通道数,高,宽)。立体结构图如下:
在这里插入图片描述

其中空间维就是 h ∗ w h*w hw 这个平面维度,通道维就是 c c c 这个维度。

①自注意力机制(Self-Attention)

原理之前已经详细给出,这里就不再赘述,只给出精简的做法及图解。

做法及图解

令 ▇ N=batch_size, L=Seq_Len, H=heads, D=heads_dim ▇ 。

  1. 获取输入 X X X 的尺寸信息:其中单个 batch_size 的尺寸为 (seq_len, embed_size) ,其中 embed_size=input_size 。图解如下:

    在这里插入图片描述

  2. 生成初步的 Q 、 K 、 V Q、K、V QKV :通过三个不同的线性层可生成初步的 Q 、 K 、 V Q、K、V QKV ,三者形状均为 (batch_size, seq_len, embed_size)

  3. 多头注意力情况下,会生成新的 Q 、 K 、 V Q、K、V QKV :初步的 Q 、 K 、 V Q、K、V QKV 会沿着特征维度(embed_size 维度)被分割成 heads 份,则新的 Q 、 K 、 V Q、K、V QKV 尺寸均为 (batch_size, seq_len, heads, head_dim) ,简化为 (N, L, H, D)

    图解如下(仅展示单个 batch_size ):

    在这里插入图片描述

  4. 计算注意力分数:(用爱因斯坦求和约定函数来求矩阵点积)单个 batch_size 的等价 “矩阵乘法+嵌套 for 循环” 计算图解为:

    (因为爱因斯坦求和约定函数太抽象,不容易画图)

    在这里插入图片描述

    接着进行缩放,即结果的每个元素除以 d k \sqrt{d_k} dk ,尺寸不变。

  5. 计算注意力权重:沿 seq_len 维度应用 s o f t m a x softmax softmax ,得到 attention

  6. 注意力加权:即 attentionV 相乘。

    图解如下(仅展示单个 batch_size ):

    在这里插入图片描述

  7. 恢复输入尺寸:由于先前分割了多头注意力,所以这里要将多头注意力结果沿 D 维度重新拼接成原尺寸 (batch_size, seq_len, embed_size)

代码

特征维度就是下方代码中的 embed_size ,其实就是 input_size

import torch.nn.functional as F

class SelfAttention(nn.Module):
    def __init__(self, embed_size, heads=8):
        super().__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads
        
        self.values = nn.Linear(embed_size, embed_size)
        self.keys = nn.Linear(embed_size, embed_size)
        self.queries = nn.Linear(embed_size, embed_size)
        self.fc_out = nn.Linear(embed_size, embed_size)
        
    def forward(self, x):
        # x shape: (batch_size, seq_len, embed_size)
        batch_size, seq_len, _ = x.size()
        
        # 生成Q, K, V, 形状均为(batch_size, seq_len, heads, head_dim)
        Q = self.queries(x).view(batch_size, seq_len, self.heads, self.head_dim)
        K = self.keys(x).view(batch_size, seq_len, self.heads, self.head_dim)
        V = self.values(x).view(batch_size, seq_len, self.heads, self.head_dim)
        
        # 计算注意力分数
        # n=batch_size, l=seq_len, h=heads, d=head_dim
        energy = torch.einsum("nlhd,nlhd->nhll", [Q, K])  # 爱因斯坦求和约定,计算 Q 和 K 的点积, 得到能量矩阵
        energy = energy / (self.head_dim ** 0.5)  # 缩放(即除以根号下dk)
        attention = F.softmax(energy, dim=3)  # 沿 seq_len 维度应用 softmax 
        
        # 注意力加权
        out = torch.einsum("nhll,nlhd->nlhd", [attention, V])
        out = out.reshape(batch_size, seq_len, self.embed_size)
        
        return self.fc_out(out)

上述代码参数讲解

  • embed_size:输入特征的维度(如 512 ),其实就是 input_size
  • heads:多头注意力的头数(如 8 ),通过并行化提升模型对不同表示子空间的关注能力。
  • head_dim:每个注意力头的维度,计算为 embed_size // heads(如 512/8=64 )。
  • 线性投影层:
    • valueskeysqueries:将输入映射到 Q 、 K 、 V Q、K、V QKV ,保持输出维度为embed_size
    • fc_out:将多头注意力结果合并回原始维度。
  • energy:能量矩阵,形状为 (n,h,l,l)

上述代码中某些函数讲解

  • torch.einsum:爱因斯坦求和,用来求多维张量点积(是 t r a n s f o r m e r transformer transformer 的核心操作之一)。

    • 核心思想:用下标字母表示张量的维度,并通过重复字母表示求和,省略显式的求和符号(如 ∑ \sum )。

    • (其实有点难以理解,难以捋清,如果看不懂的话,就用矩阵乘法+嵌套 for 循环,下面我已给出)

    • 函数语法:torch.einsum("表达式字符串", 张量1, 张量2, ...)

      • 表达式字符串:用来规定输入张量、输出张量的维度,形如 "nlhd,nlhd->nhll" ,同时规定了计算方式。其中 -> 前的字母表示输入张量的维度,每个张量的维度用逗号分隔; -> 后的字母表示输出张量的维度,若省略,则对重复字母求和,输出无重复字母的维度。

      • 输入张量:在表达式字符串后面用逗号隔开,形如 张量1, 张量2, ...

      • 字母选择:使用任意 小写字母 (如 i, j, k)表示张量的元素下标,也代表了维度,但需保证同一维度在不同张量中字母一致。

      • 计算方式:重复字母对应的维度中,对应元素求和,保留输入张量维度字母中的非重复维度字母,例如 "ik,kj->ij" ,则对输入张量的 k 维度求和,保留 i,j 两个维度。举例如下:有两个矩阵进行内积(点积),分别为 A ( I × K ) 、 B ( K × J ) \large A_{(I×K)}、B_{(K×J)} A(I×K)B(K×J) ,其元素为 A ( i , k ) 、 B ( k , j ) \large A(i,k)、B(k,j) A(i,k)B(k,j) ,对维度 k k k 进行求和运算,生成矩阵为 C ( I × J ) \large C_{(I×J)} C(I×J) ,其元素为 C ( i , j ) \large C(i,j) C(i,j) 。则有
        C ( i , j ) = ∑ k = 0 K − 1 A ( i , k ) ⋅ B ( k , j ) ———————————————— [ 1 2 3 4 ] ∗ [ 5 6 7 8 ] = [ 19 29 43 50 ] C(i,j)=\sum_{k=0}^{K-1}A(i,k)·B(k,j)\\ ————————————————\\ \left[ \begin{matrix} 1 & 2\\ 3 & 4 \\ \end{matrix} \right]*\left[ \begin{matrix} 5 & 6\\ 7 & 8 \\ \end{matrix} \right]=\left[ \begin{matrix} 19 & 29\\ 43 & 50 \\ \end{matrix} \right] C(i,j)=k=0K1A(i,k)B(k,j)————————————————[1324][5768]=[19432950]

    • 等价于 “矩阵乘法+嵌套 for 循环” 代码:

      import torch
      
      # 定义输入张量
      N, L, H, D = 2, 3, 4, 5
      Q = torch.randn(N, L, H, D)
      K = torch.randn(N, L, H, D)
      
      # —————————使用 einsum 计算————————————————————————————————————
      energy_einsum = torch.einsum("nlhd,nlhd->nhll", [Q, K])
      
      # —————————等价矩阵乘法验证—————————————————————————————————————
      energy_manual = []
      for n in range(N):
          for h in range(H):
              # 提取第n个样本、第h个头的 Q 和 K
              Q_head = Q[n, :, h, :]  # 形状 (L, D)
              K_head = K[n, :, h, :]  # 形状 (L, D)
              # 计算点积矩阵
              energy_head = torch.mm(Q_head, K_head.T)  # 形状 (L, L)
              energy_manual.append(energy_head)
      energy_manual = torch.stack(energy_manual).view(N, H, L, L)
      # ———————————————————————————————————————————————————————————
      
      # 检查一致性
      print(torch.allclose(energy_einsum, energy_manual, atol=1e-6))  # 应输出 True
      

②通道注意力机制(SEAttention)

该算法的作用是:获得输入特征层的每一个通道的权值,以此找到最需要关注的通道。

~~ 压缩成一个长条 ~~

结构图

在这里插入图片描述

适用场景

  • 图像领域:图像的多维通道往往意味着颜色,想要提取出最显著的颜色特征,就用此算法。
  • 序列领域:可将样本制作成图像一样的立体块…

做法

  1. 先在输入特征层的高和宽上做平均池化,将单个输入数据池化成 c×1×1 的特征长条;
  2. 接着使用两次全连接层,第一次全连接层压缩通道数,第二次全连接层恢复通道数。
  3. 在完成两次全连接后,再取一次 Sigmoid 将值固定到 0-1 之间,此时我们获得了输入特征层每一个通道的权值(0-1 之间),
  4. 在获得这个权值后,我们将这个权值乘上原输入特征层即可。

代码

class SEAttention(nn.Module):
    def __init__(self, channel, reduction=16):
        super(SEAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction),
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel),
            nn.Sigmoid()
        )
        
    def forward(self, x):
        b, c, _, _ = x.size()  # x shape: (b, c, h, w)
        y = self.avg_pool(x).view(b, c)  # 全局空间压缩, b,c,h,w -> b,c,1,1 -> b,c
        y = self.fc(y).view(b, c, 1, 1)  # 通道重校准, b,c -> b,c//reduction -> b,c -> b,c,1,1
        return x * y.expand_as(x)  # 特征加权

上述代码参数讲解

  • channel:输入数据的通道数;
  • reduction:该参数控制了 SEAttention 模块中通道压缩的程度,决定了压缩后的通道数。
    • 它通过减少中间层的通道数来降低计算复杂度,同时通过通道压缩和恢复的过程增强特征提取能力。
    • 默认值为 16 ,是一个常用的折中选择。

其中个别函数方法讲解

  • nn.AdaptiveAvgPool2d(output_size):二维自适应平均池化层。
    • 官网链接:AdaptiveAvgPool2d — PyTorch 2.6 documentation
    • 参数:output_size 可为元组 (h_out,w_out) ,也可为标量 h_and_w
    • 会根据输入特征图的大小,自动计算池化窗口的大小和步幅,使得输出特征图符合指定尺寸。当输入特征图有多个通道时,每个通道会单独进行池化操作(计算该通道的全局平均值),不会混合不同通道的信息。
  • y.expand_as(x):张量形状扩展方法。
    • 使用此方法可以确保 y 被扩展到与 x 完全相同的形状,这样就能避免错误广播带来的问题。

示例

import torch
from torch import nn


class SEAttention(nn.Module):
    def __init__(self, channel, reduction=16):
        super(SEAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction),
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel),
            nn.Sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.size()  # x shape: (b, c, h, w)
        y = self.avg_pool(x).view(b, c)  # 全局空间压缩, b,c,h,w -> b,c,1,1 -> b,c
        y = self.fc(y).view(b, c, 1, 1)  # 通道重校准, b,c -> b,c//reduction -> b,c -> b,c,1,1
        return x * y.expand_as(x)  # 特征加权


input1 = torch.randn([1, 4, 2, 2])
model = SEAttention(input1.shape[1], 2)  # 获取输入特征层的第二维数, 即通道数
print(model)
output1 = model(input1)
print(output1)

#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
SEAttention(
  (avg_pool): AdaptiveAvgPool2d(output_size=1)
  (fc): Sequential(
    (0): Linear(in_features=4, out_features=2, bias=True)
    (1): ReLU(inplace=True)
    (2): Linear(in_features=2, out_features=4, bias=True)
    (3): Sigmoid()
  )
)
tensor([[[[-0.4923, -0.3609],
          [ 0.2345, -0.4860]],

         [[-0.1664, -0.5443],
          [-0.2697,  0.2866]],

         [[-0.0565,  0.0348],
          [ 0.0265, -0.1163]],

         [[ 0.1896, -0.0806],
          [-0.1932, -0.5070]]]], grad_fn=<MulBackward0>)

③空间注意力机制(SPAttention)

该算法的作用是:获得输入特征层的每一个像素点的权值,以此找到最需要关注的像素点(或区域)。

~~ 压缩成一张饼

结构图

在这里插入图片描述

适用场景

  • 图像领域:想要提取出图像的空间维(h×w维)重要信息,就用此算法(就是常说的一张图片里哪些信息更重要,比如鸟、花)。
  • 序列领域:可将样本制作成图像一样的立体块…

做法

  1. 对输入特征层进行通道维取平均,即计算每个像素点在所有通道的平均值(形成一个通道平均层);
  2. 对输入特征层进行通道维取最大,即计算每个像素点在所有通道的最大值(形成一个通道最大层);
  3. 将上述两结果进行通道维拼接;
  4. 对拼接结果进行无尺寸损失的卷积,输入 2 通道,输出 1 通道,
  5. 将上述结果通过 sigmoid 函数形成 0~1 的权重层;
  6. 将原来的输入特征层与权重层相乘,进行空间维加权。

代码

class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super().__init__()
        assert kernel_size in (3,7), "kernel size must be 3 or 7"
        padding = 3 if kernel_size == 7 else 1  # 7//2=3, 3//2=1
        
        self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        # x shape: (b, c, h, w)
        avg_out = torch.mean(x, dim=1, keepdim=True)  # 通道维, 取平均
        max_out, _ = torch.max(x, dim=1, keepdim=True)  # 通道维, 取最大
        combined = torch.cat([avg_out, max_out], dim=1)  # 通道维, 拼接
        
        attention = self.conv(combined)  # 空间卷积
        attention = self.sigmoid(attention)  # 形成权重层
        return x * attention  # 空间重校准

上述代码参数详解

  • kernel_size:卷积层的卷积核尺寸,强制使用 3×37×7 卷积核;
    • 3×3 擅长捕获局部特征,适合在深层网络中使用。
    • 7×7 卷积核擅长捕获全局特征,适合在网络的早期层中使用。
    • 其他尺寸的卷积核也行,不过经验告诉我们,大部分情况下这两种效果较好。
  • padding:池化层的池化核尺寸,当 kernel_size=7padding=3;当 kernel_size=3padding=1
  • dim=1:指向第二维,在源代码中第二维指的是通道维。
  • keepdim:是否保持输出的维度与输入一致,True 则保持一致,False 则去掉被缩减的维度。
  • 最后返回值中的 * 是逐元素相乘(python会自动进行广播,使得 xattention 的形状相同)

示例

import torch
from torch import nn


class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super().__init__()
        assert kernel_size in (3, 7), "kernel size must be 3 or 7"
        padding = 3 if kernel_size == 7 else 1

        self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        # x shape: (b, c, h, w)
        avg_out = torch.mean(x, dim=1, keepdim=True)  # 通道维, 取平均
        max_out, _ = torch.max(x, dim=1, keepdim=True)  # 通道维, 取最大
        combined = torch.cat([avg_out, max_out], dim=1)  # 通道维, 拼接

        attention = self.conv(combined)  # 空间卷积
        attention = self.sigmoid(attention)  # 形成权重层
        return x * attention  # 空间重校准


model = SpatialAttention()
print(model)
input1 = torch.randn([1, 2, 32, 32])
output1 = model(input1)
print(output1, output1.shape)

#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
SpatialAttention(
  (conv): Conv2d(2, 1, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), bias=False)
  (sigmoid): Sigmoid()
)
tensor([[[[ 0.3043, -0.1115,  0.8072,  ..., -0.3164, -0.9592,  0.1138],
          [ 0.2794,  0.5417, -0.4712,  ..., -0.0697,  0.1992, -0.2221],
          [ 0.7240,  0.7404, -0.4684,  ..., -0.7113, -0.3191, -0.2365],
          ...,
          [ 0.1644, -0.0619, -0.2743,  ...,  0.2799, -0.6323, -0.2177],
          [-0.6202, -0.4627,  0.0088,  ...,  0.5412,  0.4545,  0.4991],
          [ 0.1950, -0.6817, -0.1495,  ...,  0.2904,  0.4762,  0.1911]],

         [[ 0.7864, -1.0805,  1.0262,  ...,  0.0502,  0.3625,  0.1452],
          [ 0.4181, -0.8384,  0.3122,  ..., -0.9477, -0.6478, -0.7619],
          [ 0.0280,  0.1082,  0.4468,  ..., -0.5216,  0.4628,  0.3429],
          ...,
          [ 1.0672,  0.2410,  0.7409,  ..., -0.3077, -0.6315, -0.0043],
          [ 0.1524, -0.5792,  1.3052,  ..., -0.3562,  0.1129, -0.4460],
          [ 0.0162,  0.1711, -0.3322,  ...,  0.6097, -0.3655,  0.2307]]]],
       grad_fn=<MulBackward0>) torch.Size([1, 2, 32, 32])

④ CBAM 模块

全称为:Convolutional Block Attention Module,联合通道和空间注意力

后续改进版通道注意力机制 ECA 也可以替代此处的原通道注意力机制。

结构图

这里的结构图就只给出网上流传最广的,并非自己画的了:

在这里插入图片描述

做法

该模块先使用通道注意力机制,后使用空间注意力机制。

不过其中的通道注意力机制里的池化层也变成了两个,分别是全局自适应平均池化层、全局自适应最大池化层,并且他们共享同一组全连接层,最后会将两者的结果相加,通过 sigmoid 函数生成权重层。

其他的和 SEAttentionSPAttention 没有区别。

代码

import torch
import torch.nn as nn

class ChannelAttention(nn.Module):
    """通道注意力模块"""
    def __init__(self, in_channels, reduction=16):
        super().__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局平均池化
        self.max_pool = nn.AdaptiveMaxPool2d(1)  # 全局最大池化
        
        # 共享的全连接层
        self.fc = nn.Sequential(
            nn.Linear(channel, channel //reduction, False)
            nn.ReLU(),
            nn.Linear(channel //reduction, channel, False)
        )
        self.sigmoid = nn.sigmoid()

    def forward(self, x):
        avg_out = self.avg_pool(x).view([b, c])  # 平均池化
        max_out = self.max_pool(x).view([b, c])  # 最大池化
        
        avg_fc_out = self.fc(avg_out)  # 两次全连接
        max_fc_out = self.fc(max_out)  # 两次全连接
        
        channel_weights = self.sigmoid(avg_fc_out + max_fc_out).view([b, c, 1, 1])  # 融合双路径
        return x * channel_weights  # 通道加权


class SpatialAttention(nn.Module):
    """空间注意力模块"""
    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()
        assert kernel_size in (3, 7), "kernel_size应为3或7"
        padding = 3 if kernel_size == 7 else 1  # 保持空间维度不变
        
        self.conv = 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)  # 通道最大
        combined = torch.cat([avg_out, max_out], dim=1)  # 通道拼接
        
        spatial_weights = self.sigmoid(self.conv(combined))  # 生成空间权重
        return x * spatial_weights  # 空间加权


class CBAM(nn.Module):
    """CBAM模块(通道+空间注意力级联)"""
    def __init__(self, in_channels, reduction=16, kernel_size=7):
        super().__init__()
        self.channel_att = ChannelAttention(in_channels, reduction)
        self.spatial_att = SpatialAttention(kernel_size)

    def forward(self, x):
        x = self.channel_att(x)  # 先应用通道注意力
        x = self.spatial_att(x)  # 再应用空间注意力
        return x

示例

import torch
import torch.nn as nn


class ChannelAttention(nn.Module):
    """通道注意力模块"""

    def __init__(self, channel, reduction=16):
        super().__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局平均池化
        self.max_pool = nn.AdaptiveMaxPool2d(1)  # 全局最大池化

        # 共享的全连接层
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction, False),
            nn.ReLU(),
            nn.Linear(channel // reduction, channel, False)
        )
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        b, c, _, _ = x.size()  # x shape: (b, c, h, w)
        avg_out = self.avg_pool(x).view([b, c])  # 平均池化路径
        max_out = self.max_pool(x).view([b, c])  # 最大池化路径

        avg_fc_out = self.fc(avg_out)  # 平均池化路径
        max_fc_out = self.fc(max_out)  # 最大池化路径

        channel_weights = self.sigmoid(avg_fc_out + max_fc_out).view([b, c, 1, 1])  # 融合双路径
        return x * channel_weights  # 通道加权


class SpatialAttention(nn.Module):
    """空间注意力模块"""

    def __init__(self, kernel_size=7):
        super(SpatialAttention, self).__init__()
        assert kernel_size in (3, 7), "kernel_size应为3或7"
        padding = 3 if kernel_size == 7 else 1  # 保持空间维度不变

        self.conv = 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)  # 通道最大
        combined = torch.cat([avg_out, max_out], dim=1)  # 拼接特征图

        spatial_weights = self.sigmoid(self.conv(combined))  # 生成空间权重
        return x * spatial_weights  # 空间加权


class CBAM(nn.Module):
    """CBAM模块(通道+空间注意力级联)"""

    def __init__(self, in_channels, reduction=16, kernel_size=7):
        super().__init__()
        self.channel_att = ChannelAttention(in_channels, reduction)
        self.spatial_att = SpatialAttention(kernel_size)

    def forward(self, x):
        x = self.channel_att(x)  # 先应用通道注意力
        x = self.spatial_att(x)  # 再应用空间注意力
        return x


if __name__ == '__main__':
    input1 = torch.randn([1, 4, 32, 32])
    model = CBAM(input1.shape[1], 2)
    print(model)
    output1 = model(input1)
    print(output1, output1.shape)

#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
CBAM(
  (channel_att): ChannelAttention(
    (avg_pool): AdaptiveAvgPool2d(output_size=1)
    (max_pool): AdaptiveMaxPool2d(output_size=1)
    (fc): Sequential(
      (0): Linear(in_features=4, out_features=2, bias=False)
      (1): ReLU()
      (2): Linear(in_features=2, out_features=4, bias=False)
    )
    (sigmoid): Sigmoid()
  )
  (spatial_att): SpatialAttention(
    (conv): Conv2d(2, 1, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), bias=False)
    (sigmoid): Sigmoid()
  )
)
......

⑤ ECA 模块

全称:Efficient Channel Attention(高效通道注意力机制)

原通道注意力机制(SEAttention)的改良版

将原来的两个全连接层换成了 1D 无损失卷积,其余的大差不差。

换成一维卷积的原因是:卷积能更好的捕获通道之间的局部相关性,而全连接层其实破坏了这种相关性的获取。并且全连接层的参数比卷积层大得多。

结构图

在这里插入图片描述

做法

  1. 先在输入特征层的高和宽上做平均池化,将单个输入数据池化成 1×c 的特征长条序列;
  2. 接着使用一维无损失卷积,将特征长条序列进行特征提取,得到新的长条序列。
  3. 之后再取一次 Sigmoid 将值固定到 0-1 之间,此时我们获得了输入特征层每一个通道的权值(0-1 之间),
  4. 在获得这个权值后,我们将这个权值乘上原输入特征层即可。

其中卷积层的卷积核是 “基于通道数的自适应卷积核” ,其核大小会随通道数动态调整。

原论文通过实验发现计算公式为:
k ≈ l o g 2 C + b γ k≈\frac{log_2C+b}{γ} kγlog2C+b

  • C C C:通道数。
  • γ γ γ(默认2):缩放因子,控制核大小的增长速度。 b b b(默认1):偏移项,防止通道数过小时核大小降为0。
  • 在某些数据集中,当 γ = 2 , b = 1 γ=2,b=1 γ=2,b=1 时,模型在分类、检测等任务上达到最优精度。

通道数越大,卷积核越大;通道数越小,卷积核越小。

代码

class ECAttention(nn.Module):
    def __init__(self, channel, gamma=2, b=1):
        super(ECAttention, 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

其中个别函数方法讲解

  • nn.Conv1d:一维卷积,其输入输出相当于序列。
    • 输入尺寸: ( N , C i n , L i n ) (N,C_{in},L_{in}) (N,Cin,Lin) C i n C_{in} Cin 表示序列的通道数,可以理解为条数; L i n L_{in} Lin 表示序列长度。
    • 输出尺寸: ( N , C o u t , L o u t ) (N,C_{out},L_{out}) (N,Cout,Lout) ,其中 L o u t L_{out} Lout 是要靠卷积核尺寸、池化核尺寸等参数计算出来的(也可尤其反求池化核尺寸等参数,就看谁先给出了)。
    • 详情请参考官网链接:Conv1d — PyTorch 2.6 documentation

示例

import torch
from torch import nn
import math


class ECAttention(nn.Module):
    def __init__(self, channel, gamma=2, b=1):
        super(ECAttention, 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

if __name__ == '__main__':
    input1 = torch.randn([1, 4, 32, 32])
    model = ECAttention(input1.shape[1], 2)
    print(model)
    output1 = model(input1)
    print(output1, output1.shape)

#--------------------------------------------------------------------------------------------------------------------------
# 输出结果为:
ECAttention(
  (avg_pool): AdaptiveAvgPool2d(output_size=1)
  (conv): Conv1d(1, 1, kernel_size=(1,), stride=(1,), bias=False)
  (sigmoid): Sigmoid()
)
......

二、相关问题:

待更新…

  • L S T M LSTM LSTM 的信息筛选有什么区别?

    L S T M LSTM LSTM 的门控机制是:时序信息的局部过滤器(此时刻对上一时刻信息的筛选能力,距离远的就不太能保留下来),而注意力机制是:全局信息的动态选择器(和距离没多大关系)。 L S T M LSTM LSTM 的目标是解决 R N N RNN RNN 的梯度消失问题,在时间维度上捕捉长期依赖关系,但本质上仍是对序列的局部处理。而注意力机制不受时间顺序限制,能够跨步长、跨模态捕捉全局依赖。

    另外在 N L P NLP NLP 中,注意力机制得到的新的词向量具有句法特征和语义特征(表征更完善,不仅具有上下文关联信息,而且句子更符合语法)。

    (另一个区别是: L S T M LSTM LSTM 是串行结构,而 A t t e n t i o n Attention Attention 是并行结构)

    维度LSTM注意力机制
    关注范围时间序列的局部依赖(相邻时间步)全局或跨步长的关联(如长距离依赖)
    计算方式基于门控的线性筛选基于相似度计算的动态权重分配
    可解释性门控权重反映时序过滤逻辑注意力权重直接显示关键信息位置
    参数化程度固定门控结构(参数与时间步绑定)完全参数化(可学习Query/Key/Value)
  • A t t e n t i o n Attention Attention 的缺点是什么?

    计算量非常大。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值