共计 3162 个字符,预计需要花费 8 分钟才能阅读完成。
为什么需要 CBAM?
在图像分类、目标检测等计算机视觉任务中,CNN 卷积神经网络往往平等对待所有空间位置和通道特征,而 CBAM(Convolutional Block Attention Module)通过动态计算通道和空间两个维度的注意力权重,让模型更聚焦于关键特征区域,显著提升模型表现。

双注意力机制图解
CBAM 的核心由通道注意力模块(Channel Attention Module)和空间注意力模块(Spatial Attention Module)串联组成,下面分别解析:
通道注意力机制
通道注意力关注 ”what”——哪些通道的特征更重要。其结构如下:
- 输入特征图 $F \in \mathbb{R}^{C×H×W}$ 同时经过全局平均池化和全局最大池化,得到两个 $C×1×1$ 的通道描述符
- 将两个描述符输入共享的 MLP(多层感知机),第一层神经元数为 $C/r$(r 为缩减比例),第二层恢复为 $C$
- 将 MLP 输出的两个特征相加后通过 Sigmoid 激活,得到通道权重 $M_c \in \mathbb{R}^{C×1×1}$
- 最终输出 $F’ = F \otimes M_c$(逐通道相乘)
数学表达式:
$$M_c(F) = \sigma(MLP(AvgPool(F)) + MLP(MaxPool(F)))$$
空间注意力机制
空间注意力关注 ”where”——哪些空间位置更重要。其计算流程:
- 对通道注意力输出 $F’$ 沿通道维度分别做平均池化和最大池化,得到两个 $1×H×W$ 的特征图
- 将两者通道拼接后通过 7×7 卷积核压缩为单通道
- 经 Sigmoid 激活得到空间权重 $M_s \in \mathbb{R}^{1×H×W}$
- 最终输出 $F” = F’ \otimes M_s$(逐像素相乘)
数学表达式:
$$M_s(F’) = \sigma(f^{7×7}([AvgPool(F’); MaxPool(F’)]))$$
与 SE-Net 的差异
相比 SE-Net 只考虑通道注意力,CBAM 的改进在于:
- 增加了空间注意力模块,形成双注意力机制
- 通道注意力中同时使用平均池化和最大池化,保留更丰富信息
- 空间注意力采用更高效的通道压缩方式
PyTorch 实现详解
下面给出完整实现并嵌入 ResNet 的示例:
import torch
import torch.nn as nn
import torch.nn.functional as F
class CBAM(nn.Module):
def __init__(self, channels, reduction_ratio=16):
super(CBAM, self).__init__()
# 通道注意力
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.mlp = nn.Sequential(nn.Linear(channels, channels // reduction_ratio),
nn.ReLU(),
nn.Linear(channels // reduction_ratio, channels)
)
# 空间注意力
self.conv = nn.Conv2d(2, 1, kernel_size=7, padding=3)
def forward(self, x):
# 通道注意力计算
b, c, _, _ = x.shape
avg_out = self.mlp(self.avg_pool(x).view(b, c))
max_out = self.mlp(self.max_pool(x).view(b, c))
channel_att = torch.sigmoid(avg_out + max_out).view(b, c, 1, 1)
x = x * channel_att
# 空间注意力计算
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
spatial_att = torch.cat([avg_out, max_out], dim=1)
spatial_att = torch.sigmoid(self.conv(spatial_att))
return x * spatial_att
# 嵌入 ResNet 的示例
class ResNet_CBAM(nn.Module):
def __init__(self, block, layers, num_classes=1000):
# ... 标准 ResNet 初始化代码 ...
self.layer1 = self._make_layer(block, 64, layers[0])
self.cbam1 = CBAM(256) # 根据实际通道数调整
# ... 其他层定义 ...
def forward(self, x):
# ... 标准前向传播 ...
x = self.layer1(x)
x = self.cbam1(x) # 在残差块后添加 CBAM
# ... 剩余部分 ...
特征可视化方法
理解注意力权重分布最直观的方式是可视化:
import matplotlib.pyplot as plt
def visualize_attention(feature_map, attention_map):
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(feature_map[0].mean(0).detach().cpu(), cmap='viridis')
plt.title("Original Feature")
plt.subplot(1, 2, 2)
plt.imshow(attention_map[0][0].detach().cpu(), cmap='hot')
plt.title("Attention Heatmap")
plt.colorbar()
plt.show()
# 使用示例
feature = model.conv_layers(input_img)
attended = model.cbam(feature)
visualize_attention(feature, model.cbam.spatial_att) # 需要将 spatial_att 保存为成员变量
实践应用指南
数据预处理
CBAM 对输入数据的归一化范围敏感,建议:
- 保持与基准模型一致的归一化方式(通常 ImageNet 的 mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
- 避免过度增强会分散注意力的数据增强(如极端颜色抖动)
超参数设置
- Reduction Ratio 选择 :
- 常用值在 8 -32 之间
- 通道数较少时(<128)建议取较小值(如 8)
-
深层大模型可尝试 16 或 32
-
插入位置 :
- 实验表明在残差连接后添加效果通常更好
- 避免在紧邻下采样层前使用
计算开销分析
以输入尺寸 224×224 为例:
- 基准 ResNet-50 FLOPs:~4.1 GMac
- 添加 CBAM 后增加约 0.05 GMac(约 1.2% 开销)
- 实际训练中每个 CBAM 模块增加约 5 -8% 的显存占用
延伸思考
CBAM 的成功引发了一些有趣的开放性问题:
-
视频分析扩展 :如何设计时序注意力机制与现有空间 - 通道注意力的协同?是否需要在 3D 卷积基础上开发新的池化策略?
-
与 Transformer 融合 :CBAM 的轻量级特性能否与 Vision Transformer 结合?比如用通道注意力优化 patch embedding,或用空间注意力替代部分 self-attention 层以减少计算量。
-
跨模态应用 :这种双注意力机制是否适用于多模态任务(如图文检索)?如何调整以适应不同模态的特征尺度差异?
通过本文的解析,相信你已经掌握了 CBAM 的核心原理和实践方法。建议在具体任务中先从少量 CBAM 模块开始实验,逐步调整位置和参数,观察模型对不同类型注意力机制的响应模式。
