CBAM注意力机制结构图自绘版:从零实现到性能优化指南

1次阅读
没有评论

共计 2533 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景与痛点

传统 CNN 通过堆叠卷积层提取特征,但存在两个明显问题:

CBAM 注意力机制结构图自绘版:从零实现到性能优化指南

  1. 特征表达粗粒度:卷积核平等对待所有通道和空间位置,无法区分重要特征区域
  2. 计算资源浪费:对低信息量区域投入与关键区域相同的计算力

CBAM(Convolutional Block Attention Module)通过双路注意力机制实现特征重标定:

  • 通道注意力:学习各通道的权重,实现跨通道交互
  • 空间注意力:捕获空间上下文信息,突出重要区域

结构解析(自绘示意图关键点)

通道注意力模块

  1. 输入处理 :特征图 F 经过全局平均池化(GAP) 和全局最大池化(GMP)
  2. 共享 MLP:两层全连接层(中间有 ReLU),输出通道权重向量
  3. 第一层缩减通道数(r=16 为典型值)
  4. 第二层恢复原通道数
  5. 权重融合:将 GAP 和 GMP 路径的结果相加后通过 Sigmoid

空间注意力模块

  1. 通道压缩:沿通道方向应用 GAP 和 GMP,得到 2×H×W 特征图
  2. 卷积融合:7×7 卷积核处理拼接后的特征,生成空间权重矩阵
  3. 归一化:Sigmoid 激活产生最终空间注意力图

PyTorch 完整实现

import torch
import torch.nn as nn

class ChannelAttention(nn.Module):
    def __init__(self, in_planes, ratio=16):
        super().__init__()
        # 共享 MLP 结构(使用 1x1 卷积替代全连接)self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.fc1 = nn.Conv2d(in_planes, in_planes//ratio, 1, bias=False)
        self.relu = nn.ReLU()
        self.fc2 = nn.Conv2d(in_planes//ratio, in_planes, 1, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = self.fc2(self.relu(self.fc1(self.avg_pool(x))))
        max_out = self.fc2(self.relu(self.fc1(self.max_pool(x))))
        out = avg_out + max_out  # 元素级相加
        return self.sigmoid(out)

class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super().__init__()
        assert kernel_size in (3,7), "内核尺寸需为 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)[0]  # 通道最大
        x = torch.cat([avg_out, max_out], dim=1)  # 拼接成 2 通道
        x = self.conv(x)
        return self.sigmoid(x)

class CBAM(nn.Module):
    def __init__(self, channels, ratio=16, kernel_size=7):
        super().__init__()
        self.ca = ChannelAttention(channels, ratio)
        self.sa = SpatialAttention(kernel_size)

    def forward(self, x):
        x = x * self.ca(x)  # 通道注意力加权
        x = x * self.sa(x)  # 空间注意力加权
        return x

性能分析

以 ResNet50 的 conv4_x 层为例(输入 256 通道,特征图尺寸 14×14):

模块 参数量 计算量(FLOPs)
原始卷积层 1.1M 3.8G
增加 CBAM 后 +0.017M +0.23G
增幅比例 1.5% 6%

避坑指南

  1. 维度不匹配:
  2. 现象:矩阵乘法时出现 ”shape not aligned” 错误
  3. 解法:检查通道注意力中 MLP 的输入 / 输出维度,确保 ratio 能被通道数整除

  4. 梯度消失:

  5. 现象:训练后期注意力图趋于恒定值
  6. 解法:适当降低学习率,或在注意力权重计算前添加 LayerNorm

  7. 效果反降:

  8. 现象:添加 CBAM 后准确率反而下降
  9. 解法:确认插入位置是否合理(建议在高层特征使用),避免过早引入注意力

延伸思考

  1. 动态比例调节:
  2. 当前通道压缩比 ratio 为固定值,可尝试根据网络深度动态调整
  3. 实验思路:浅层用较小 ratio(保留更多通道信息),深层用较大 ratio

  4. 注意力组合优化:

  5. 现有 CBAM 是串行结构(先通道后空间)
  6. 可探索并行结构或交替注意力机制
  7. 参考方案:将两个注意力图相乘而非顺序作用

嵌入现有网络示例

class ResBlock_CBAM(nn.Module):
    def __init__(self, in_channels):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels, in_channels, 3, padding=1)
        self.cbam = CBAM(in_channels)  # 插入 CBAM 模块
        self.conv2 = nn.Conv2d(in_channels, in_channels, 3, padding=1)

    def forward(self, x):
        residual = x
        x = F.relu(self.conv1(x))
        x = self.cbam(x)  # 在残差连接前加入注意力
        x = self.conv2(x)
        return F.relu(x + residual)

通过自绘结构图与代码对照学习,建议读者先在 CIFAR-10 等小数据集上验证效果,逐步理解注意力机制如何增强特征表达能力。实际部署时可结合 TensorRT 等工具对 CBAM 进行算子融合优化,减少推理时的额外开销。

正文完
 0
评论(没有评论)