4、注意力机制:SE、CBAM、CA、SimAM模块的原理与在YOLO中的集成方法

注意力机制,说白了就是让网络学会「看哪里」。
小目标检测最头疼的问题是什么?背景杂乱、目标太小、特征容易被淹没。
注意力机制就是干这个的——它告诉网络:「别瞎看,重点在这儿!」

我最早接触注意力是在分类任务上,当时觉得效果也就那样。
直到做小目标检测,才发现这东西简直是救星。
你想想看,一张1080p的图里,目标可能只有十几个像素,不加注意力,网络根本不知道往哪瞅。

这一章,咱们把四个主流注意力模块掰开揉碎讲清楚:
SE、CBAM、CA、SimAM。
每个我都会讲原理、画结构、给代码,最后告诉你YOLO里怎么集成。

四种注意力机制核心对比 SE (Squeeze-Excitation) 通道注意力 全局池化 → FC → 激活 对通道重标定 CBAM 通道 + 空间注意力 通道注意力 → 空间注意力 串行级联 CA (Coordinate Attention) 坐标感知注意力 水平+垂直方向编码 保留位置信息 SimAM 无参数注意力 基于神经科学理论 无需额外参数 模块 参数量 小目标适用性 推理速度 SE 低 中等 快 CBAM 中 高 中等 CA 低 高 快 SimAM 零 中等 最快 💡 小目标检测推荐优先级:CA > CBAM > SE > SimAM CA保留位置信息,对小目标最友好;SimAM无参数但效果略逊

4.1 SE模块:通道维度的「注意力放大器」

SE,全称Squeeze-and-Excitation Networks,2017年提出。
它的核心思想很简单:让网络自己学习每个通道的重要性。

打个比方,你有一堆特征图,有的通道包含目标信息,有的全是背景噪声。
SE做的就是:把有用的通道放大,没用的通道抑制。

SE的工作原理

  1. Squeeze(压缩):全局平均池化,把每个通道压缩成一个数值
  2. Excitation(激励):两个全连接层 + 激活函数,学习通道间关系
  3. Scale(缩放):把学到的权重乘回原特征图

关键点:SE的压缩比r(reduction ratio)很关键。
我习惯设r=16,既减少参数量,又不损失太多信息。
太小了参数量大,太大了信息丢失严重。

SE的PyTorch实现

class SEBlock(nn.Module):
    def __init__(self, channels, reduction=16):
        super().__init__()
        self.squeeze = nn.AdaptiveAvgPool2d(1)
        self.excitation = nn.Sequential(
            nn.Linear(channels, channels // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channels // reduction, channels, bias=False),
            nn.Sigmoid()
        )
    
    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.squeeze(x).view(b, c)
        y = self.excitation(y).view(b, c, 1, 1)
        return x * y.expand_as(x)

YOLO中集成SE

我一般把SE加在Backbone的每个CSP块后面,或者Neck的FPN层之间。
具体位置:

  • Backbone末端:增强高层语义特征的通道选择
  • Neck的PANet层:改善多尺度特征融合时的通道对齐

我的经验:SE对小目标的提升有限,因为它只关注通道,不关注位置。
但胜在轻量,几乎不增加推理时间。
如果模型已经很大了,加SE性价比很高。

4.2 CBAM:通道+空间,双管齐下

CBAM(Convolutional Block Attention Module)是SE的升级版。
它不光做通道注意力,还加了空间注意力。
说白了就是:既告诉网络「哪些通道重要」,又告诉「哪些位置重要」。

我记得第一次在YOLOv5上试CBAM,mAP涨了1.2个点。
当时还挺惊讶的,毕竟就加了一个小模块。

CBAM的结构

  1. 通道注意力模块:类似SE,但用了最大池化+平均池化并行
  2. 空间注意力模块:对通道维度做池化,然后卷积生成空间权重
  3. 串行连接:先通道后空间(验证串行优于并行)

CBAM的PyTorch实现

class CBAMBlock(nn.Module):
    def __init__(self, channels, reduction=16, kernel_size=7):
        super().__init__()
        # 通道注意力
        self.channel_att = nn.Sequential(
            nn.AdaptiveAvgPool2d(1),
            nn.AdaptiveMaxPool2d(1),
            nn.Flatten(),
            nn.Linear(channels, channels // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channels // reduction, channels, bias=False),
            nn.Sigmoid()
        )
        # 空间注意力
        self.spatial_att = nn.Sequential(
            nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False),
            nn.Sigmoid()
        )
    
    def forward(self, x):
        # 通道注意力
        b, c, h, w = x.size()
        y = self.channel_att(x).view(b, c, 1, 1)
        x = x * y.expand_as(x)
        # 空间注意力
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        spatial = torch.cat([avg_out, max_out], dim=1)
        spatial = self.spatial_att(spatial)
        return x * spatial

注意:CBAM的空间注意力用了7×7卷积,对小目标来说感受野太大了。
我曾经试过改成3×3,对小目标检测反而更好。
如果你做小目标,建议把kernel_size改成3。

YOLO中集成CBAM

CBAM我一般放在:

  • Neck的每个上采样/下采样之后:增强特征融合时的空间定位
  • 检测头之前:让检测头看到更「干净」的特征

4.3 CA模块:给小目标装上「GPS」

CA(Coordinate Attention)是2021年提出的。
它解决了SE和CBAM的一个共同问题:位置信息丢失。

你想想看,SE全局池化后,位置信息全没了。
CBAM虽然加了空间注意力,但用的是卷积,对大目标还行,小目标就模糊了。
CA的做法很巧妙:分别对高度和宽度方向做编码。

CA的核心创新

  1. 水平方向池化:对每一行做平均池化,得到C×H×1的特征
  2. 垂直方向池化:对每一列做平均池化,得到C×1×W的特征
  3. 拼接+卷积:把两个方向的特征拼起来,学习坐标感知的注意力
  4. 分离+缩放:再分开成两个方向,分别乘回原特征图

为什么CA对小目标友好?
小目标的位置信息极其重要。
CA保留了每个像素的「坐标感」,网络能精确知道目标在哪儿。
我实测在VisDrone数据集上,CA比CBAM高了0.8个mAP。

CA的PyTorch实现

class CABlock(nn.Module):
    def __init__(self, channels, reduction=32):
        super().__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))
        
        mid_channels = max(8, channels // reduction)
        self.conv1 = nn.Conv2d(channels, mid_channels, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(mid_channels)
        self.act = nn.ReLU(inplace=True)
        
        self.conv_h = nn.Conv2d(mid_channels, channels, kernel_size=1, bias=False)
        self.conv_w = nn.Conv2d(mid_channels, channels, kernel_size=1, bias=False)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        b, c, h, w = x.size()
        x_h = self.pool_h(x)  # b,c,h,1
        x_w = self.pool_w(x).permute(0, 1, 3, 2)  # b,c,1,w -> b,c,w,1
        
        y = torch.cat([x_h, x_w], dim=2)  # b,c,h+w,1
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)
        
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)  # b,c,1,w
        
        att_h = self.sigmoid(self.conv_h(x_h))
        att_w = self.sigmoid(self.conv_w(x_w))
        
        return x * att_h * att_w

YOLO中集成CA

CA我强烈建议加在:

  • Backbone的每个stage输出后:让特征提取阶段就保留位置信息
  • 检测头之前:最后一关强化位置感知

我的配置建议:
CA的reduction我设32,比SE的16更大。
因为CA有两个方向,中间特征维度mid_channels = max(8, channels//32)。
这样参数量控制得很好,不会拖慢速度。

4.4 SimAM:不用参数的注意力

SimAM(Simple Attention Module)是2021年的工作。
它的理念很激进:不需要额外参数。

怎么做到的?
SimAM基于神经科学中的「神经元抑制」理论。
它计算每个神经元和周围神经元的差异,差异大的就是重要的。

SimAM的原理

说白了,就是找「与众不同」的神经元。
如果一个像素和周围像素差别很大,那它很可能就是目标边缘或关键点。
SimAM用能量函数计算每个位置的「重要性分数」。

class SimAMBlock(nn.Module):
    def __init__(self, channels=None, e_lambda=1e-4):
        super().__init__()
        self.act = nn.Sigmoid()
        self.e_lambda = e_lambda
    
    def forward(self, x):
        b, c, h, w = x.size()
        n = h * w - 1
        
        # 计算每个像素与均值的差异
        x_mean = x.mean(dim=[2, 3], keepdim=True)
        x_minus_mean = x - x_mean
        x_var = (x_minus_mean ** 2).mean(dim=[2, 3], keepdim=True)
        
        # 能量函数
        d = (x_minus_mean ** 2) / (4 * (x_var + self.e_lambda)) + 0.5
        att = self.act(1.0 / d)
        
        return x * att

注意:SimAM虽然无参数,但效果不如CA和CBAM稳定。
我试过在YOLOv8上集成,mAP提升只有0.3-0.5个点。
但它有个巨大优势:零参数量,零FLOPs增加。
适合对模型大小有严格要求的场景。

4.5 四种注意力机制对比总结

模块 注意力类型 参数量 小目标效果 推理速度 推荐场景
SE 通道 低 中等 快 轻量模型、通道选择
CBAM 通道+空间 中 高 中等 通用场景、精度优先
CA 坐标感知 低 最高 快 小目标检测、位置敏感任务
SimAM 无参数 零 中等 最快 模型压缩、边缘部署

4.6 YOLO中集成注意力机制的通用方法

不管用哪种注意力,集成到YOLO里的套路都差不多。
我以YOLOv8为例,给一个通用模板:

# 在ultralytics/nn/modules/下新建attention.py
# 把上面四个模块的代码放进去

# 在ultralytics/nn/modules/__init__.py中导入
from .attention import SEBlock, CBAMBlock, CABlock, SimAMBlock

# 在ultralytics/nn/tasks.py中修改parse_model
# 在需要的位置添加注意力层
def parse_model(d, ch, verbose=True):
    # ... 原有代码 ...
    if m in {SEBlock, CBAMBlock, CABlock, SimAMBlock}:
        args = [ch[f], *args]
    # ... 后续代码 ...

我的集成建议:
1. 不要每个层都加注意力,选3-5个关键位置就行
2. 小目标检测优先用CA,其次是CBAM
3. 如果模型已经很大了,用SE或SimAM做轻量化增强
4. 训练时学习率可以调低一点,注意力模块收敛较慢

嗯,这一章的内容就到这儿。
四种注意力机制,各有各的脾气。
SE轻量、CBAM全面、CA精准、SimAM零成本。
选哪个,看你手头的任务和资源。