SE注意力机制详解:通道注意力原理、PyTorch实现与选型实战
发布时间:2026/10/1 9:11:28来源:尧图网络
1. SE注意力机制到底在解决什么问题第一次看到SE这个词很多人脑子里蹦出来的可能是搜索引擎的缩写或者某个硬件接口。放到深度学习语境里SE指的是Squeeze-and-Excitation中文通常翻成“挤压与激励”也有人直接叫“SE注意力”“通道注意力机制”。它是CVPR 2018那篇《Squeeze-and-Excitation Networks》里提出来的结构核心思路特别朴素让网络自己学会判断哪些通道更重要然后把不重要的通道压一压把重要的通道抬一抬。说白了一个卷积层输出几十上百个通道这里面有的通道负责纹理有的负责颜色有的负责边缘还有一大堆通道可能在这一张图上什么都没学到。普通卷积把所有这些通道一视同仁地往下一层送下一层只能被动接受。SE干的事情就是插在中间先看一眼整体再给每个通道打一个0到1之间的分数最后按分数重新加权。这个操作不改变特征图的空间尺寸也不改变通道数量纯粹做一次“通道级别的重新分配”。适合谁看这篇如果你在做图像分类、目标检测、分割或者任何需要CNN主干网络的活SE是一个几乎零成本就能试的即插即用模块。如果你在啃注意力机制这条线SE是理解通道注意力的起点搞懂它之后再看CA、CBAM、甚至自注意力那些东西脉络会清楚很多。我自己的经验是新手直接上手多头自注意力很容易被矩阵维度绕晕从SE切入反而更扎实——它只有一个全局池化加两层全连接参数量小到可以在纸上手算。1.1 从一次特征图“跑偏”说起早期我做分类任务时遇到过一个典型现象模型在训练集上拟合得很快但验证集上某些类别的表现始终上不去尤其是那些依赖局部细节的类别。把中间层特征可视化出来看发现很多通道的响应几乎是平的灰度图上糊成一片但它们的权重照样参与后续卷积。这就相当于开会的时候一堆人没意见却都在投票真正有信息的那几个人声音被稀释掉了。SE解决的就是这个“投票权重”的问题。传统的注意力做法里有人试过手工设计规则比如按通道的方差排序或者按激活值大小筛选。手工规则的问题在于它是固定的换个数据集、换个任务就不灵了。SE的做法是把“判断哪些通道重要”这件事也交给网络去学用一个小的子网络来生成权重。它把特征图先压成一个通道描述向量再从这个向量里学出一组权重最后反乘回去。整个流程是可导的能跟着主干网络一起端到端训练。这里要强调一点SE学到的是样本自适应的权重。同一张网络输入一只猫和输入一辆车SE输出的通道权重分布是不一样的。这是它比固定权重的通道缩放高明的地方也是它后来被大量主干网络吸收的原因。1.2 通道注意力与空间注意力的分工搜索相关资料的时候你一定会撞见这几个词ca注意力机制、cbam注意力机制、通道-空间协同注意力机制。它们和SE的关系可以先粗暴地划一条线——SE只管通道不管空间。通道注意力的逻辑是“哪一类特征重要”。比如做动物分类负责毛发纹理的通道可能比负责背景颜色的通道更重要。空间注意力的逻辑是“图上的哪个位置重要”。比如做行人检测画面中央的行人区域比角落的路灯更值得关注。两者的维度不同做的事情也不同。CBAM就是把这两者串起来了先做一次通道注意力再做一次空间注意力两步都做完再输出。CACoordinate Attention则是把位置信息编码进通道注意力里用一维池化分别沿高度和宽度方向做算是对SE的一种改良让通道权重带上了方向感。我个人的判断是如果你的任务里目标位置基本固定比如人脸识别里的正脸裁切图通道注意力往往就够了加空间注意力收益有限还多算力。如果目标是散落分布的比如遥感图像里的车辆检测那通道加空间的组合更值得试。这个取舍后面第4节我会展开讲。1.3 “挤压”“激励”这两个词到底在说什么名字起得有点唬人其实拆开看很直观。Squeeze挤压就是把每个通道的二维特征图压成一个数用的是全局平均池化。一张 C×H×W 的特征图经过这一步变成 C×1×1也就是C个数每个数代表这个通道在整张图上的平均响应强度。这相当于给每个通道写了一句自我介绍“我在这张图里整体有多活跃。”Excitation激励是拿这C个数去算权重。做法是接两层全连接中间夹一个降维先用一个小的瓶颈层把C维压到C/r维过ReLU再升回C维过Sigmoid。为什么要先降维再升维这是为了控制参数量同时引入非线性。最后Sigmoid把输出压到0到1之间正好可以当权重系数用。Reweight重标定最省事把算出来的C个权重按通道乘回原特征图就行。整个过程没有任何空间上的操作所以SE的计算开销主要来自那个全局池化和两次矩阵乘法跟卷积本身的FLOPs比起来是零头。2. SE模块的三个核心动作逐层拆解讲清楚“是什么”之后得把每一步的细节抠出来。很多人抄代码能跑通但问到“为什么降维比选16”“为什么用平均池化不用最大池化”“权重乘在残差分支的哪个位置”就答不上来了。这一节就是把这些点补上。2.1 Squeeze全局平均池化压掉了什么又保住了什么Squeeze用的全局平均池化GAP是把 H×W 个值取平均。这一步会丢掉所有空间位置信息——一个通道在左上角很亮、右下角全黑和均匀地中等亮度平均下来可能差不多。这是SE的固有局限也是它后来被CA这类方法针对的地方。但丢掉空间信息换来的是全局视野。卷积的感受野是局部的深层网络靠堆层数才能看到整张图而GAP一次就拿到了全图统计。对于“这个通道整体有没有用”这个问题全局平均比局部响应更能说明问题。另外GAP没有参数不会增加过拟合风险这也是它比用全连接直接摊平特征图更稳的原因。关于用平均池化还是最大池化原论文里做过消融两者效果接近平均池化略好。我的理解是平均池化反映的是“整体活跃度”最大池化反映的是“最强响应点”。对于通道重要性判断这种偏全局的决策平均更合适。不过在实际项目里如果你处理的是稀疏目标比如医学影像里很小的一块病灶最大池化有时能带来一点提升值得两个都试一下代价很低。注意Squeeze之后得到的向量维度是 C×1×1如果你的框架里张量是 NCHW 格式记得用自适应平均池化到 (1,1)不要手动写 mean因为 mean 的维度处理容易和其他算子对不齐。2.2 Excitation降维比r怎么选两层全连接为什么比一层好Excitation的结构是FC(C → C/r) → ReLU → FC(C/r → C) → Sigmoid。这里的r就是降维比原论文默认取16。先说为什么要有瓶颈结构。如果直接用一层FC从C维映射到C维参数量是 C²对C512来说就是26万参数一个小小的注意力模块吃掉这么多参数不划算。加一个降维比r16的瓶颈参数量变成 2C²/r直接少了8倍。同时中间夹一个ReLU引入了非线性让权重不再是输入的线性函数表达能力反而更强。r怎么选论文里测了2、4、8、16、32结论是16在精度和参数量之间平衡得比较好但不同主干网络的最优值不完全一样。我在自己的项目里做过几轮对比经验是通道数小的层比如C64r取8或16差别不大通道数很大的层C1024r取16会把中间维度压到64信息瓶颈有点紧取8反而稳一点。所以如果你的网络通道数跨度很大可以考虑按层动态调r或者干脆统一用一个适中的值先跑baseline。还有一个细节两层FC可以用1×1卷积实现也可以直接nn.Linear。用1×1卷积的好处是能直接接在NCHW张量上不用来回view代码更干净。但要注意1×1卷积的输入是 (N,C,1,1)输出也是 (N,C,1,1)乘法时广播对齐就行。2.3 Reweight逐通道乘法以及和残差结构的配合方式Reweight就是一次广播乘法把 (N,C,1,1) 的权重乘到 (N,C,H,W) 的特征图上。这一步的代码通常写成x * scalePyTorch会自动广播。真正需要琢磨的是这个模块插在网络的什么位置。以ResNet的Bottleneck为例一个残差块里有三个卷积1×1降维、3×3卷积、1×1升维。SE应该插在哪个后面原论文的做法是插在残差分支最后一个1×1卷积之后、和捷径相加之前。也就是说SE只作用于残差分支不影响捷径连接。为什么这么放我理解有两个原因。一是这个位置的特征通道数已经恢复到块输出维度SE的权重能覆盖完整的输出通道。二是放在相加之前SE相当于对残差分支做了一次调节捷径连接保持恒等映射梯度回传更顺畅。如果放在相加之后SE就变成对整个块输出做缩放和后面的层职责有些重叠实测下来收益一般。值得一试的变体把SE放在3×3卷积之后、升维之前。这时候通道数是最小的C/4SE的参数量会小很多但效果通常也会打折因为通道数太少注意力分辨力不足。我试过在轻量网络里这么干参数省了但精度掉得比省下来的算力更值。所以默认还是照原论文的位置放。3. 从零实现一个可插拔的SE模块这一节给可直接抄的代码。我用PyTorch写因为它的模块化最方便做即插即用的封装。如果你用TensorFlow或者Paddle逻辑是一模一样的只是API名字不同。3.1 SEBlock的完整实现与参数说明import torch import torch.nn as nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super().__init__() # 瓶颈维度至少为1避免通道数过小时除出0 mid max(channels // reduction, 1) self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channels, mid, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(mid, channels, biasFalse), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) # Squeeze y self.fc(y).view(b, c, 1, 1) # Excitation return x * y # Reweight几个实现上的取舍说一下。biasFalse是常见选择因为前面有池化已经在做去均值再叠偏置意义不大还省参数。不过原论文里没明确说去掉偏置我实测两者差别极小去掉更清爽。inplaceTrue的ReLU能省一点显存注意别在需要保留中间激活的场景下用。mid max(channels // reduction, 1)这一句是防坑的。当你在很浅的层用SE比如通道数只有8、r16的时候8//16等于0中间维度变成0网络直接报错。加了max之后就安全了。这个坑我踩过当时在一堆小网络上批量插SE跑一半崩了排查半天才发现是整除问题。3.2 参数量和计算量的手算过程以C256、r16为例把参数量算一遍你就知道它为什么便宜。第一个Linear256 × 16 4096个参数去偏置。第二个Linear16 × 256 4096个参数。加起来8192个参数。对比一下一个256通道输入输出、3×3卷积的参数量是 256×256×3×3 589824。也就是说SE的参数量只有那个卷积的约1.4%。哪怕在整网层面累积SENet相比ResNet的参数量增幅也就10%左右绝大部分来自通道数大的层。计算量再估一下。GAP是 C×H×W 次加法两次矩阵乘法是 2×C×C/r 次乘加。对于C256、HW56ResNet中间层的典型尺寸GAP是80万次操作矩阵乘法是8192次。而同一层的3×3卷积是 256×256×3×3×56×56数量级在十亿。所以SE带来的额外FLOPs对总计算量影响很小主要瓶颈反而可能是GAP这种内存访问密集的操作在移动端要留意实际延迟。提示如果你的部署目标对延迟敏感别只看FLOPs。GAP和两次全连接在GPU上很轻快但在某些推理芯片上不一定高效最好实测一次端到端延迟别被理论FLOPs骗了。3.3 把SE嵌入ResNet的Bottleneckclass SEBottleneck(nn.Module): expansion 4 def __init__(self, inplanes, planes, stride1, downsampleNone, reduction16): super().__init__() width planes self.conv1 nn.Conv2d(inplanes, width, 1, biasFalse) self.bn1 nn.BatchNorm2d(width) self.conv2 nn.Conv2d(width, width, 3, stridestride, padding1, biasFalse) self.bn2 nn.BatchNorm2d(width) self.conv3 nn.Conv2d(width, planes * self.expansion, 1, biasFalse) self.bn3 nn.BatchNorm2d(planes * self.expansion) self.relu nn.ReLU(inplaceTrue) self.se SEBlock(planes * self.expansion, reduction) self.downsample downsample def forward(self, x): identity x out self.relu(self.bn1(self.conv1(x))) out self.relu(self.bn2(self.conv2(out))) out self.bn3(self.conv3(out)) out self.se(out) # SE插在第三个卷积之后、相加之前 if self.downsample is not None: identity self.downsample(x) return self.relu(out identity)这段代码和我实际项目里的结构基本一致唯一调整的是每一层的宽度定义为了可读性把原ResNet里的 planes、planes*4 拆清楚了。注意SE的channel数必须传planes * expansion也就是块输出通道不是中间通道。传错的话广播会失败报维度不匹配的错。4. 和CA、CBAM、自注意力那一堆到底怎么选注意力机制这几年冒出来一大堆缩写SE、CA、CBAM、ECA、SimAM等等。加上从NLP那边过来的自注意力、多头自注意力、时序注意力机制新手很容易看花眼。这一节按“管什么维度”和“用在什么数据上”两条线把它们理清楚。4.1 通道-空间协同注意力机制的做法差异CBAM是通道注意力加空间注意力的串联。通道那部分用平均池化和最大池化各做一遍分别过一个小MLP结果相加过Sigmoid空间那部分把所有通道在同一个位置的值做池化得到一个单通道的图再用一个大卷积核卷一下过Sigmoid。相比SECBAM多了一路空间权重也多了一份计算量。CACoordinate Attention改的是通道注意力的输入方式。它不用全局平均池化压成一个数而是沿H方向和W方向分别做一维池化得到两组带方向的向量再分别编码。这样通道权重里就保留了位置线索。对于需要定位的任务比如分割和检测CA通常比SE更占优。但它的实现比SE复杂中间还涉及维度变换和拆分代码稍不留神就写错。我一般给的建议是先跑SE当baseline因为最便宜也最容易复现。如果验证集上还有余量且定位精度是关键指标再换CA或者CBAM对比。上来就堆最复杂的模块往往连问题出在哪都定位不了。4.2 自注意力与多头自注意力的原理差异自注意力是从序列建模来的。它的基本操作是把每个位置的特征映射成Query、Key、Value三组向量用Query和所有Key算相似度得到权重再对Value加权求和。这样每个位置都能直接看到序列里其他所有位置不受卷积局部感受野的限制。多头自注意力就是把这个过程并排做多次每一头用不同的投影最后拼接起来。多头的好处是让不同的头去关注不同类型的关联比如一个头关注相邻位置另一个头关注长距离依赖。用在图像上自注意力会带来一个明显的计算量问题序列长度是H×W注意力矩阵是 (HW)×(HW)高分辨率下直接爆炸。所以视觉领域后来出现了一堆改进比如把注意力限制在局部窗口内或者先下采样再算。这跟SE那种“只算C个权重”的小打小闹完全不是一个量级。我的看法是SE和自注意力不是替代关系。SE负责通道层面的重分配是轻量的“调音”自注意力负责全局依赖建模是重型的“重构”。很多现代主干网络两个都装了比如在卷积块后面接SE在某几个阶段后面接自注意力或者混合结构。4.3 时序注意力机制在序列任务中的落地时序注意力机制主要用在时间序列预测、语音、动作识别这类数据上。思路和通道注意力有点神似把一个时间序列的各个时间步当成“通道”给每个时间步算一个重要度分数然后加权求和或者加权输入下一层。具体实现上常见做法是先过一个时间维度的池化或者编码器得到每个时间步的表示再用两层全连接生成权重。你会发现这个结构和SE几乎一样只不过池化维度和加权维度换成了时间维度。所以理解了SE时序注意力基本是照搬思路换维度。我在一个传感器异常检测的项目里用过这个思路。原始做法是把整个滑窗序列直接送进模型结果一些早期的噪声片段对最终判断影响很大。加了时序加权之后模型自己学会了压低平稳期的权重、抬高异常突变附近的权重误报率下来了不少。这里的关键是池化的方式——全局平均会把时间轴压没得改成沿特征维池化保留时间步维度。4.4 一张表把选型问题说清楚方法关注维度额外参数适用场景实现复杂度SE通道低约2C²/r分类、检测主干通用性最好低CA通道方向中需要定位的分割、检测中CBAM通道空间中高目标位置不固定的检测任务中自注意力空间/序列全局高与HW平方相关高分辨率下需改造高多头自注意力空间/序列全局很高Transformer类主干高时序注意力时间步低类似SE时间序列、语音、动作识别低表格里的参数量都是相对量级具体数字要用你自己的通道数和分辨率算。选型的时候我通常问自己三个问题目标是固定位置还是散落分布算力预算够不够当前瓶颈是精度还是速度答案基本能锁定方向。5. 训练中踩过的坑和排查清单代码能跑不代表效果好。这一节记录我在实际项目里遇到过的典型问题以及对应的排查思路。这些内容论文里基本不会写但真上手的时候一定会遇到。5.1 效果不升反降的几种情况第一种插入位置太靠前。SE插在网络的浅层通道数少、特征还没成型注意力权重学出来接近均匀分布等于没加还平白引入了参数和正则压力。我的经验是至少放到第二个stage之后浅层别动。第二种降维比设得太激进。r32甚至更大中间维度被压得很小信息瓶颈过紧权重分辨力下降。解决方法是把r调回8或16或者用分组的方式分摊。第三种和Dropout打架。SE本身是一种软性正则如果你在同一位置又叠了较强的Dropout训练前期模型可能学不出有意义的权重。可以试试比较两者的强度或者把SE的初始化调一下让Sigmoid初始输出接近1相当于先恒等再慢慢学。这个技巧在训练不稳定的时候很有用。第四种小数据集上过拟合。SE是加参数的如果你的训练集只有几千张图加SE可能反而让验证集变差。这时候要么冻住SE的训练前期要么直接放弃用更简单的方法。5.2 常见问题速查表现象可能原因排查方法报维度不匹配SE的通道数传错检查是否传入块输出通道而非中间通道中间维度为0通道数小于降维比加上max(...,1)保护训练loss震荡Sigmoid权重初期过小调整初始化或降低学习率验证集不升插入位置太浅或数据太少尝试后移位置或去掉推理变慢明显GAP在目标硬件上低效实测端到端延迟别只看FLOPs权重几乎全为1模型没学到区分度检查学习率和SE前的BN是否正常这张表是我自己整理的项目备忘每次新任务插SE之前会扫一遍。大部分问题其实都能靠“先跑个最小复现”定位——单独写个脚本构造一个随机输入把SE的输出权重打印出来看取值范围比在完整训练里瞎猜快得多。5.3 几个上不了论文但很实用的技巧权重可视化当调试工具。训练几十个epoch之后把某个batch的SE输出权重取出来画成柱状图。如果所有通道的权重都挤在0.5附近说明这个模块没在干活可能是位置或者学习率的问题。如果权重有明显的峰谷说明它在做区分可以考虑保留。先冻结再放开。迁移学习场景下我习惯先冻结主干只训SE和分类头几个epoch让权重先有个合理的初值再放开全网络微调。这样收敛更稳尤其是在小数据集上。用分组版本省参数。如果通道数特别大比如1024以上可以把Excitation里的全连接改成组卷积的形式或者用1×1卷积配合分组。这样能在几乎不掉精度的前提下再省一笔参数对移动端部署有帮助。别迷信默认参数。r16是论文在ImageNet上给的经验值你的数据分布和分辨率都不一样。花点时间在r8、16之间做个对比通常半小时的训练就能看出趋势。这个投入产出比很高。最后说个我自己的体会。SE这个东西之所以能在几年时间里被大量主干网络采纳不是因为它多先进而是因为它的性价比太高了——几十行代码、不到百分之几的算力增量就能换来稳定的精度提升。它也不是万能的在需要精细定位、需要长距离依赖的任务上单靠通道权重确实不够。明白了它“只管通道、只做全局统计”这两个边界你就知道什么时候该用它什么时候该换别的。我在实际项目里的习惯是先把SE当默认选项跑一遍拿到一个基线再根据验证集的表现决定要不要上更复杂的注意力。这个顺序千万别反否则连基线在哪都不知道后面全是玄学调参。
网站建设高端定制企业官网