Day 41:模型注意力机制 — SE / CBAM / ECA

今日目标 :掌握三种轻量级注意力机制的设计思想,理解通道注意力与空间注意力的区别,学会在端侧模型中加入注意力

预计阅读 :10分钟 | 动手操作:35分钟


一、CNN为什么需要注意力?

复制代码
CNN的默认行为:所有通道一视同仁
  Conv2d(256, 256, 3) → 所有256个通道同等重要
  但实际中:有些通道对当前任务更重要!

注意力机制的作用:
  让模型学会"关注"重要的通道/区域
  抑制不重要的通道/区域
  
  类比:你看一张图片时
    不是均匀地看每个像素
    而是聚焦于关键区域(人脸、文字)
    注意力机制就是让CNN也学会"聚焦"

三种注意力维度:
  通道注意力 (Channel Attention): "哪些通道更重要?"
  空间注意力 (Spatial Attention): "哪些位置更重要?"
  混合注意力: 两者结合
python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(42)

二、核心知识点

2.1 SE (Squeeze-and-Excitation):通道注意力鼻祖

python 复制代码
"""
SE-Net (2018)
论文:https://arxiv.org/abs/1709.01507

核心思想:让模型自动学习每个通道的重要性

Squeeze (压缩):
  全局平均池化 → 把每个通道的H×W压缩成1个标量
  (C, H, W) → (C, 1, 1)
  这个标量代表该通道的"全局信息"

Excitation (激励):
  两层全连接 → 学习通道之间的关系
  FC(C) → ReLU → FC(C) → Sigmoid
  输出: 每个通道的权重 (0-1之间)

Scale (加权):
  原始特征 × 通道权重
  重要通道的权重 → 1
  不重要通道的权重 → 0

SE的参数量:
  两个FC层: C×(C/r) + (C/r)×C = 2C²/r
  r是缩减率 (默认16)
  如C=256, r=16: 2×256²/16 = 8192个参数
  几乎可以忽略不计!

SE是最常用的轻量级注意力
MobileNet v3、EfficientNet都用了SE
"""

class SEBlock(nn.Module):
    """
    Squeeze-and-Excitation Block
    
    参数量极少,即插即用
    可以插入任何CNN的任意位置
    """
    def __init__(self, channels, reduction=16):
        super().__init__()
        self.channels = channels
        
        # Squeeze: 全局平均池化
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        
        # Excitation: 两层FC
        self.fc = 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):
        """
        x: (B, C, H, W)
        """
        B, C, H, W = x.shape
        
        # Squeeze: (B, C, H, W) → (B, C, 1, 1) → (B, C)
        y = self.avg_pool(x).view(B, C)
        
        # Excitation: (B, C) → (B, C) → (B, C, 1, 1)
        y = self.fc(y).view(B, C, 1, 1)
        
        # Scale: 逐通道加权
        return x * y


def se_demo():
    """演示SE的作用"""
    x = torch.randn(2, 64, 32, 32)
    se = SEBlock(64, reduction=16)
    y = se(x)
    
    params = sum(p.numel() for p in se.parameters())
    
    print("SE Block演示:")
    print(f"  输入: {x.shape}")
    print(f"  Squeeze: (2,64,32,32) → (2,64,1,1) 全局平均池化")
    print(f"  Excitation: FC(64→4) → ReLU → FC(4→64) → Sigmoid")
    print(f"  Scale: 逐通道乘以权重")
    print(f"  输出: {y.shape}")
    print(f"  参数量: {params} (几乎可以忽略)")
    print(f"\n  端侧AI: SE参数量极少,强烈推荐!")

se_demo()

2.2 CBAM:通道 + 空间双重注意力

python 复制代码
"""
CBAM (2018)
论文:https://arxiv.org/abs/1807.06521

核心思想:SE只有通道注意力,CBAM加了空间注意力

CBAM = 通道注意力 + 空间注意力

通道注意力 (类似SE但更强):
  同时用全局平均池化 + 全局最大池化
  平均池化: 整体信息
  最大池化: 最显著特征
  共享MLP → 两个输出相加 → Sigmoid

空间注意力:
  沿着通道维度做平均池化 + 最大池化
  (C, H, W) → (2, H, W)
  然后7×7卷积 → 1个通道 → Sigmoid
  输出: 每个位置的权重

CBAM的顺序:
  先通道注意力 → 再空间注意力
  论文证明这个顺序效果最好
"""

class ChannelAttention(nn.Module):
    """CBAM的通道注意力模块"""
    def __init__(self, channels, reduction=16):
        super().__init__()
        # 共享MLP
        self.mlp = nn.Sequential(
            nn.Conv2d(channels, channels // reduction, 1, bias=False),
            nn.ReLU(inplace=True),
            nn.Conv2d(channels // reduction, channels, 1, bias=False),
        )
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        # 平均池化
        avg_out = self.mlp(F.adaptive_avg_pool2d(x, 1))
        # 最大池化
        max_out = self.mlp(F.adaptive_max_pool2d(x, 1))
        # 相加
        return self.sigmoid(avg_out + max_out)


class SpatialAttention(nn.Module):
    """CBAM的空间注意力模块"""
    def __init__(self, kernel_size=7):
        super().__init__()
        self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        # 沿通道维度做平均和最大池化
        avg_out = torch.mean(x, dim=1, keepdim=True)  # (B, 1, H, W)
        max_out, _ = torch.max(x, dim=1, keepdim=True)  # (B, 1, H, W)
        # 拼接
        y = torch.cat([avg_out, max_out], dim=1)  # (B, 2, H, W)
        # 卷积 + Sigmoid
        return self.sigmoid(self.conv(y))


class CBAM(nn.Module):
    """
    CBAM = Channel Attention + Spatial Attention
    
    顺序:输入 → 通道注意力 → 空间注意力 → 输出
    """
    def __init__(self, channels, reduction=16, kernel_size=7):
        super().__init__()
        self.channel_attn = ChannelAttention(channels, reduction)
        self.spatial_attn = SpatialAttention(kernel_size)
    
    def forward(self, x):
        # 1. 通道注意力
        x = x * self.channel_attn(x)
        # 2. 空间注意力
        x = x * self.spatial_attn(x)
        return x


def cbam_demo():
    x = torch.randn(2, 64, 32, 32)
    cbam = CBAM(64, reduction=16)
    y = cbam(x)
    
    params = sum(p.numel() for p in cbam.parameters())
    
    print("CBAM演示:")
    print(f"  输入: {x.shape}")
    print(f"  Step 1 - 通道注意力: 平均池化+最大池化 → 共享MLP → Sigmoid")
    print(f"  Step 2 - 空间注意力: 沿通道平均+最大 → 7×7卷积 → Sigmoid")
    print(f"  输出: {y.shape}")
    print(f"  参数量: {params}")
    print(f"\n  vs SE: 多了空间注意力,精度更高,但参数量略多")

cbam_demo()

2.3 ECA:极简通道注意力

python 复制代码
"""
ECA-Net (2020)
论文:https://arxiv.org/abs/1910.03151

核心思想:SE的两个FC层是多余的!用1D卷积替代

SE的问题:
  两个FC层 → 降维再升维 → 信息损失
  而且FC层把通道间的关系"全连接"了
  实际上只需要局部跨通道交互

ECA的改进:
  用1D卷积替代FC!
  1D卷积的kernel_size = 自适应计算的k
  只和相邻的几个通道交互
  不需要降维!

ECA的优势:
  1. 参数量更少(只有k个参数)
  2. 不需要降维(无信息损失)
  3. 自适应kernel_size
  4. 效果和SE相当甚至更好
"""

class ECABlock(nn.Module):
    """
    ECA: Efficient Channel Attention
    
    用1D卷积替代FC,参数量极低
    端侧AI首选注意力模块!
    """
    def __init__(self, channels, gamma=2, b=1):
        super().__init__()
        # 自适应计算kernel_size
        # k = |log2(C)/γ + b/γ|_odd
        t = int(abs((torch.log2(torch.tensor(channels, dtype=torch.float32)) + b) / gamma))
        kernel_size = t if t % 2 else t + 1  # 确保是奇数
        
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.conv = nn.Conv1d(1, 1, kernel_size=kernel_size,
                              padding=kernel_size // 2, bias=False)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        """
        x: (B, C, H, W)
        """
        B, C, H, W = x.shape
        
        # Squeeze: (B, C, H, W) → (B, C, 1, 1)
        y = self.avg_pool(x)
        
        # 1D卷积: (B, C, 1) → (B, 1, C) → conv → (B, C, 1)
        y = y.squeeze(-1).transpose(-1, -2)  # (B, 1, C)
        y = self.conv(y)  # 1D卷积
        y = y.transpose(-1, -2).unsqueeze(-1)  # (B, C, 1, 1)
        
        # Sigmoid
        y = self.sigmoid(y)
        
        return x * y


def eca_demo():
    x = torch.randn(2, 64, 32, 32)
    eca = ECABlock(64)
    y = eca(x)
    
    params = sum(p.numel() for p in eca.parameters())
    
    print("ECA Block演示:")
    print(f"  输入: {x.shape}")
    print(f"  流程: 全局平局池化 → 1D卷积 → Sigmoid → 加权")
    print(f"  输出: {y.shape}")
    print(f"  参数量: {params} (极低!)")
    print(f"\n  核心创新: 用1D卷积替代FC")
    print(f"  不需要降维 → 无信息损失")
    print(f"  自适应kernel_size → 根据通道数自动调整")
    print(f"\n  端侧AI: ECA是最轻量的注意力!强烈推荐!")

eca_demo()

三、动手实践:三种注意力全面对比

python 复制代码
def attention_comparison():
    """
    SE / CBAM / ECA 全面对比
    """
    comparison = {
        'SE': {
            '注意力类型': '通道',
            '核心操作': 'GAP → FC → ReLU → FC → Sigmoid',
            '参数量': '2C²/r (约8192)',
            '降维': '是 (C → C/r → C)',
            '信息损失': '有 (降维)',
            '精度': '⭐⭐⭐',
            '端侧推荐': '⭐⭐⭐⭐⭐',
        },
        'CBAM': {
            '注意力类型': '通道 + 空间',
            '核心操作': 'SE + 7×7卷积空间注意力',
            '参数量': '2C²/r + 98 (约8290)',
            '降维': '是',
            '信息损失': '有',
            '精度': '⭐⭐⭐⭐',
            '端侧推荐': '⭐⭐⭐⭐',
        },
        'ECA': {
            '注意力类型': '通道',
            '核心操作': 'GAP → 1D Conv → Sigmoid',
            '参数量': 'k (约3-5)',
            '降维': '否',
            '信息损失': '无',
            '精度': '⭐⭐⭐⭐',
            '端侧推荐': '⭐⭐⭐⭐⭐',
        },
    }
    
    print("注意力机制全面对比:")
    print(f"{'':<10s} {'SE':<20s} {'CBAM':<20s} {'ECA':<20s}")
    print('-' * 70)
    for attr in ['注意力类型', '核心操作', '参数量', '降维', '信息损失', '精度', '端侧推荐']:
        se_val = comparison['SE'][attr]
        cbam_val = comparison['CBAM'][attr]
        eca_val = comparison['ECA'][attr]
        print(f"{attr:<10s} {se_val:<20s} {cbam_val:<20s} {eca_val:<20s}")

attention_comparison()

四、端侧AI注意力选型

python 复制代码
def edge_attention_selection():
    """
    端侧AI不同场景的注意力选型
    """
    guide = {
        'MobileNet v3': {
            '用的注意力': 'SE',
            '位置': '倒残差块的最后一层之后',
            '效果': '+1.5% Top-1',
            '代价': '参数增加 < 5%',
        },
        'EfficientNet': {
            '用的注意力': 'SE',
            '位置': '每个MBConv块中',
            '效果': '核心组件',
            '代价': '参数增加约10%',
        },
        'YOLOv5/v8': {
            '用的注意力': '无 (或可选SE/CBAM)',
            '位置': 'CSP模块中',
            '效果': '可选,+0.5-1% mAP',
            '代价': '参数增加约3%',
        },
        '端侧自定义模型': {
            '推荐': 'ECA (最轻量)',
            '次选': 'SE (最成熟)',
            '不推荐': 'CBAM (空间注意力在端侧优势不大)',
        },
    }
    
    print("端侧AI注意力选型:")
    for model, config in guide.items():
        print(f"\n  [{model}]")
        for k, v in config.items():
            if k != '推荐' and k != '次选' and k != '不推荐':
                print(f"    {k}: {v}")
            else:
                print(f"    {k}: {v}")

edge_attention_selection()

五、常见坑点

坑1:SE的reduction_ratio选多大

python 复制代码
# reduction ratio (r) 控制FC层的压缩程度
# r越大 → 中间层越小 → 参数越少
# r越小 → 中间层越大 → 表达能力越强

# 推荐值:
#   r=16: 标准推荐 (论文默认)
#   r=8:  通道数较少时 (< 128)
#   r=4:  通道数很少时 (< 64)
#   r=24: 追求极致轻量

# 端侧推荐:r=16 或 r=24

坑2:注意力放在哪里

python 复制代码
# 注意力模块可以插入任何位置
# 但不同位置效果不同

# 推荐位置:
# 残差块的加法之后 (最常见)
#   1×1 Conv → 3×3 Conv → 1×1 Conv → SE → + shortcut
#   这是MobileNet v3和EfficientNet的做法

# ❌ 不推荐:
# 每个卷积层后面都加 → 参数太多,过拟合
# 只在网络开头加 → 效果有限

# ✅ 推荐:
# 每个stage的最后一个block加SE
# 不是所有层都加!

坑3:ECA的kernel_size自适应

python 复制代码
# ECA的kernel_size是自适应计算的
# k = |log2(C)/γ + b/γ|_odd

# 如果手动设置:
# C=64 → k=3
# C=128 → k=3
# C=256 → k=5
# C=512 → k=5
# C=1024 → k=7

# 通道数越多,k越大
# 因为通道间的关系更复杂,需要更大的感受野

坑4:注意力不是万能的

python 复制代码
# 注意力机制不是"加了一定好"
# 场景1:通道数很少(<32)→ 注意力效果有限
# 场景2:模型已经很小 → 注意力可能过拟合
# 场景3:训练数据很少 → 注意力可能过拟合

# 端侧AI建议:
# 1. 先在baseline上跑 → 看精度
# 2. 加SE/ECA → 看精度提升
# 3. 如果提升 < 0.5% → 不加(节省参数)
# 4. 如果提升 > 1% → 加上(性价比高)

六、今日作业

  1. 手写SE:实现SE Block,插入到简单CNN中,对比有无SE的精度
  2. 手写ECA:实现ECA Block,理解1D卷积替代FC的设计
  3. 三种注意力对比:在MNIST或CIFAR-10上对比SE/CBAM/ECA的效果
  4. 打卡 :评论区发你的理解,格式:"Day 41/100 打卡:注意力机制已掌握!"

今日小结

复制代码
今天你学会了:
✅ 注意力机制的核心:让模型学会"关注"重要信息
✅ 通道注意力 vs 空间注意力
✅ SE: Squeeze(全局池化) → Excitation(FC) → Scale
✅ CBAM: 通道注意力 + 空间注意力
✅ ECA: 用1D卷积替代FC,最轻量
✅ SE参数量 ~2C²/r,ECA参数量 ~k
✅ 端侧AI:ECA首选,SE最成熟
✅ 4个经典坑点

明日预告

Day 42:模型量化基础(INT8/FP16)

对称量化、非对称量化、PTQ vs QAT、TensorRT量化


🔥 关注我,每天解锁一个端侧AI技能!

微信公众号:xxx | 小红书:xxx | CSDN:xxx

评论区打卡,一起坚持100天!


附:小红书图文版

封面标题建议:CNN注意力 | SE/CBAM/ECA,三分钟搞懂!👁️

P1 --- 封面

标题:注意力机制

副标题:SE / CBAM / ECA 轻量级注意力

P2 --- 为什么需要注意力

CNN默认:所有通道一视同仁

实际:有些通道更重要!

注意力:让模型自动学习"关注"什么

通道注意力:哪些通道重要?

空间注意力:哪些位置重要?

P3 --- SE (最简单)

Squeeze: 全局平均池化 → 1个标量

Excitation: FC→ReLU→FC→Sigmoid

Scale: 逐通道乘以权重

参数量极少(~8K),即插即用!

P4 --- CBAM (最强)

SE + 空间注意力

通道注意力:平均+最大池化 → MLP

空间注意力:沿通道平均+最大 → 7×7卷积

精度最高,但参数量略多

P5 --- ECA (最轻量)

SE的FC层是多余的!

用1D卷积替代 → 只有k个参数

不需要降维,无信息损失

端侧AI首选!

P6 --- 端侧选型

ECA: 最轻量,首选

SE: 最成熟,稳定

CBAM: 精度最高,略重

MobileNet/EfficientNet都在用SE!

评论区打卡 Day 41/100

标签:#注意力机制 #SE #CBAM #ECA #轻量级网络


CSDN发布提示:CSDN版本建议在SE部分放Squeeze-Excitation的流程图,在CBAM部分放通道注意力+空间注意力的双分支结构图,在ECA部分放1D卷积替代FC的对比图,在三者对比部分放对比表。

相关推荐
朝朝辞暮i1 小时前
第 22 课:Conditional 1D U-Net——Diffusion Policy 怎样理解一整段未来动作?
人工智能·pytorch·深度学习·transformer·vla
别动我齐刘海1 小时前
Linux 系统调试学习2
linux·运维·c++·人工智能·学习·机器学习·机器人
171320330计算机毕设编程1 小时前
计算机毕业设计 基于YOLO改进算法的车辆特征识别系统设计与实现--lw
java·大数据·python·sql·算法
Web极客码1 小时前
Python Ollama 本地 Agent 入门与订单查询实践
开发语言·jvm·python
探客木木夕1 小时前
结构超图重构神经网络模型:脉冲权重节点与四阶段闭环框架
人工智能·深度学习·神经网络
地理探险家1 小时前
从零实现图片自动分类:数据准备、模型训练与单图预测
人工智能·深度学习·机器学习·分类·数据挖掘·数据集
snhna1 小时前
DeerFlow 2.x 源码拆解:多智能体委派、Skills、沙箱与 Linux 部署实战
人工智能·python
Wx-bishekaifayuan2 小时前
springboot桂味美食分享系统40540-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
Metaphor6922 小时前
如何快速拆分超大 Excel 文件【Python 教程】
python·excel