尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

注意力机制全解析:从SE到自注意力,选型与实战指南

注意力机制全解析:从SE到自注意力,选型与实战指南 这两年做深度学习的不管你是搞CV还是NLP几乎都绕不开“注意力机制”这个词。我见过不少同学GitHub上找了个SE或者CBAM模块往自己的网络里一插准确率涨了就说“注意力就是牛”不涨就说“模块没用”然后就去换下一个。其实很多时候问题是出在没搞清楚这些注意力机制到底在“注意”什么、用在什么位置、计算开销能不能承受。这篇文章我把常见的注意力机制拉通梳理一遍包括SE通道注意力、CA坐标注意力、CBAM混合注意力、自注意力、多头注意力、时序注意力讲清楚它们各自的原理、适用场景、工程落地时的坑最后给出一套选型和排查的思路。内容尽量用我实际项目里的经验来讲适合刚入门想系统了解注意力的同学也适合已经在用但想把模块选型和调参做细的工程师。我会把关键原理讲透附带可以抄作业的代码片段也会说清楚哪些地方容易翻车。1. 注意力机制到底在做什么先建立整体认知1.1 一个视角注意力就是信息重标定很多讲注意力的文章一上来就搬出Transformer、QKV这些概念但其实注意力机制最初级的形态理解成“信息重标定”就够了。什么叫重标定举个例子你在一个满是杂物的房间里找钥匙你的视觉系统并不会对房间里所有东西一视同仁而是会优先把目光聚焦在桌面、口袋、门框这些可能放钥匙的地方。这个“聚焦”的过程就是对视觉信息的不同区域给予不同的权重。深度学习里的注意力机制做的本质上也是这件事让网络决定哪些通道、哪些位置、哪些时间步更重要然后给它们更高的权重。具体到实现上注意力模块通常是一个很小的子网络它输入是已有的特征图输出一个权重向量或权重矩阵然后用这个权重去乘原来的特征。权重越大对应特征对后续任务的贡献就越大。这就是为什么注意力模块往往可以“即插即用”——它不改变原有网络的主体结构只是对特征做了一次重标定。这个视角对我帮助很大因为当你把注意力理解为“重标定”时你就能明白它为什么有时候不起作用如果原特征本身的信息分布已经很均匀或者任务本身不需要强调某一部分那强行加注意力就是在给特征图做无意义的变换甚至会干扰原有信息。1.2 按作用维度给注意力机制分个类我们平时看到的注意力机制版本太多了如果不分类记忆成本很高。我习惯按它作用的维度来分这样选型的时候思路也清晰。第一类是通道注意力代表就是SESqueeze-and-Excitation和ECA。这类注意力关注的是“哪些特征通道更重要”。比如一张猫的图片有的通道可能主要响应毛发的纹理有的通道响应背景的轮廓通道注意力会去放大毛发的响应、抑制背景的响应。第二类是空间注意力关注的是“特征图哪些位置更重要”。比如检测任务中目标所在的区域应该获得更大的权重背景区域权重应该被压低。CBAM里的空间注意力部分就是在做这件事。第三类是混合注意力典型代表是CBAM它把通道注意力和空间注意力串联起来先告诉网络“看哪些特征”再告诉网络“看哪些位置”。第四类是自注意力它和前三类有本质区别。前几类是通过一个小网络学习一个固定的权重去重标定原特征而自注意力是把特征内部两两之间的关系算出来每个位置都由其他所有位置加权求和得到。这是一种动态建模复杂度更高表达能力也更强。第五类是时序注意力专门用于序列数据或时间维度的建模比如语音帧、股票序列、传感器信号、视频帧等。它处理的是“哪个时间步更重要”的问题。这样一分再看那些五花八门的注意力模块就会发现绝大多数都是在这些维度上做文章。有的在池化方式上做创新比如用最大池化代替平均池化有的在运算方式上做文章比如用1D卷积代替全连接有的则是在多个维度上做组合。理解了分类再看具体模块就会轻松很多。1.3 为什么会出现这么多变体原因其实很朴素不同任务、不同算力条件下对注意力的需求完全不一样。SE在图像分类上有效果但它的全连接层会引入参数量而且对位置信息完全无感。于是ECA提出用1D卷积替代全连接把参数量降到极低CA则专门针对位置信息缺失的问题把全局池化分解成水平方向和垂直方向两个池化。CBAM觉得不光通道重要空间位置也重要于是把两者串起来。自注意力虽然表达能力最强但计算复杂度和序列长度的平方成正比输入尺寸一大就扛不住又催生出一堆稀疏注意力、窗口注意力、线性注意力。说白了注意力机制的进化史就是一张在“表达能力、参数量、计算量、适用场景”之间找平衡的权衡表。没有哪个模块在所有场景下都无敌只有合适不合适。理解到这一层你再看那些新出的注意力变体就能自动去分析它的设计动机和代价而不是被动地追新。2. 通道注意力解析SE与ECA的实战细节2.1 SE模块的核心压缩与激发SE是通道注意力里最经典的一个思想非常简洁。对输入特征图 (X \in R^{B,C,H,W})先做全局平均池化把每个通道的 (H \times W) 空间信息压缩成一个数值得到 (z \in R^{B,C})。这个操作叫Squeeze也就是“压缩”。然后再做Excitation即“激发”用两个全连接层学习每个通道的权重。第一层全连接把通道从 (C) 压缩到 (C/r)用ReLU激活第二层再恢复回 (C)用Sigmoid输出一个0到1之间的权重向量。最后把权重向量逐通道乘回原始特征图。这里 (r) 是压缩比例通常在ResNet系列里设为16。我经常被问到为什么SE不直接用每个通道的均值来做权重非要绕一圈全连接原因有两个。第一直接算出来的权重和通道之间的交互没有任何关系而全连接的bottleneck结构允许不同通道之间共享信息能学到通道间的关系。第二bottleneck结构缩再放能限制模型复杂度起到类似正则的作用否则通道很多的时候参数量会爆炸。实际使用SE的时候我一般会把这个模块放在残差分支的最后一层卷积之后、残差相加之前或者放在每个基础block的末尾。这样修改对原网络的破坏最小也最容易做消融实验。还有一个细节是压缩比例 (r)它在ImageNet上实验出来的优选值是16但如果你做的是小模型或者通道数很小的网络16可能会把中间层压得太狠信息丢失太严重可以试着调成4或者8。2.2 ECA去掉全连接让通道注意力更轻ECAEfficient Channel Attention是继SE之后一个很讨巧的改进。它的核心观点是全连接其实没必要通道间的关系往往只和相邻局部通道有关。SE里用全连接建模了所有通道之间的交互但实际有效的信息是局部的强行全局建模反而带来了大量冗余参数。ECA的做法很简单全局平均池化之后不接全连接而是用一个一维卷积直接在通道维度上做局部交互然后接Sigmoid得到权重。一维卷积的卷积核大小 (k) 并不是拍脑袋定的而是根据通道数自适应计算出来的。公式是[ k \left| \frac{\log_2(C)}{\gamma} \frac{b}{\gamma} \right|_{odd} ]其中 (\gamma) 一般取2(b) 取1结果取最近的奇数。这样通道越多卷积核越大局部交互的范围也越大。比如通道数为64时(k) 约等于5通道数为512时(k) 约等于9。ECA在效果上和SE基本持平但参数量少得多只有SE的几十分之一。我之前在YOLO系列检测模型里替换SE为ECAmAP基本不掉但模型大小和推理耗时都有下降。如果你的项目对推理速度和模型体积很敏感比如移动端部署、边缘设备推理我会优先推荐ECA。用ECA的时候要注意一点一维卷积是在“通道”这个维度上滑动的你需要在实现时把特征从 ((B,C,1,1)) 转成适合Conv1d的 ((B,C,1)) 形状或者直接用Conv2d处理。很多新人在这里形状对不上报错报半天。2.3 通道注意力注意事项通道注意力虽然简单但在工程里还是有几个容易踩的点。第一Sigmoid输出权重初始在0.5附近这意味着刚开始训练时注意力模块几乎对所有通道都是“打五折”。如果网络是预训练模型这种缩放会在一开始打乱原有的特征分布导致loss在初始阶段出现波动。解决办法是在某些实现里给最后一层全连接或Conv1d的bias做特殊初始化让Sigmoid输出接近1让模块在训练初期尽量保持恒等映射。第二通道注意力模块插入的位置很重要。我见过有人把SE插到激活函数之前也有人插在之后效果差异很大。我的经验是如果前面接的是ReLU放在ReLU之前和之后差别不大如果前面接的是Sigmoid或者Tanh放在之前更稳。因为Sigmoid/Tanh的输出已经是0~1或-1~1的有界值再乘一个权重不会造成数值不稳定而ReLU的输出如果是大范围的先缩放再接非线性反而更平滑。第三不要在浅层滥用通道注意力。网络前几层的特征图尺寸大、通道数少通道注意力的收益往往不明显反而会拖慢训练速度。我一般建议从网络的第2或第3个stage开始再接SE/ECA浅层保持原始卷积的较高分辨率信息。3. 空间注意力与混合注意力CBAM与CA3.1 CBAM通道和空间一起做CBAMConvolutional Block Attention Module是“通道-空间协同注意力机制”的典型代表。它的结构是串联的特征图先进通道注意力模块得到和SE类似但稍有不同的通道权重加权后再进空间注意力模块得到空间权重再加权。CBAM的通道注意力和SE最大的区别在于SE只用全局平均池化CBAM则同时使用了全局平均池化和全局最大池化。最大池化捕捉的是每个通道上“最强烈的响应”平均池化捕捉的是整体分布两者互补。两个池化结果各自过同一个全连接共享层输出逐元素相加后再过Sigmoid得到通道权重。空间注意力部分更有意思。对通道注意力加权后的特征图在通道维度上分别做平均池化和最大池化得到两个 (H \times W) 的二维特征然后把它们沿着通道方向拼接起来得到2通道的特征图再用一个 (7 \times 7) 的卷积层将其压缩成1通道最后过Sigmoid得到空间权重。这个权重会乘回通道加权后的特征图最终让网络同时知道了“看什么特征”和“看哪里”。我最早用CBAM是在一个细粒度图像分类项目上。基线模型是带SE的ResNet50换到CBAM之后准确率提升了大约1.2个百分点。后来我又试过颠倒CBAM里通道和空间的顺序先空间后通道效果稍微差一些说明通道优先的设计在多数视觉任务里是对的先确定哪些特征有意义再看这些特征出现在哪里。3.2 理解Coordinate AttentionCA不只是又一个注意力热词里有“ca注意力机制”这里要明确一下通常说的CA是指Coordinate Attention坐标注意力不是Channel Attention。CA的核心卖点是在保持通道注意力低开销优势的同时把位置信息也显式地编码进去。SE和ECA都有一个先天不足全局平均池化把空间信息完全压扁了网络只知道“这个通道整体强不强”不知道“强在哪里”。而CBAM虽然加了空间注意力但空间注意力容易丢失长距离的依赖关系而且空间注意力模块大多是用卷积做的感受野有限。CA的思路很优雅。它把全局池化分解成两个方向一个水平方向的平均池化一个垂直方向的平均池化。也就是说对输入特征图 (X)分别对 (H) 维和 (W) 维做全局平均池化得到两个特征向量一个形状是 ((B,C,1,W))另一个形状是 ((B,C,H,1))。这两个向量分别编码了水平方向的空间依赖和垂直方向的空间依赖。接下来把这两个向量在空间维度上做concat然后通过一个 (1 \times 1) 卷积降维再用BatchNorm和激活函数处理。之后又split成两个独立的向量各自经过 (1 \times 1) 卷积恢复通道数最后用Sigmoid得到两个方向的注意力权重。这两个权重会分别作用到原始特征图上相当于告诉网络“每一行的重要程度”和“每一列的重要程度”。CA这种设计很聪明因为它把二维的位置信息分解成了两个一维的问题计算量不大但能捕捉到空间位置信息。对移动端小模型来说CA是一个很值得尝试的模块。我用过MobileNetV3在检测任务上替换SE为CA在相同FLOPs下mAP有小幅提升尤其对小目标的召回率更有帮助因为小目标的位置信息对检测太重要了。3.3 混合注意力常见问题关于CBAM和CA这类混合注意力有几个问题出现过很多次。第一个问题是CBAM的空间注意力部分计算开销。(7 \times 7) 卷积虽然不重但如果特征图尺寸很大比如在Encoder的第一层插入CBAM空间注意力操作的耗时其实不可忽略。我的习惯是只在大尺寸特征图上用通道注意力在分辨率降低到 (14 \times 14) 或更小之后才接完整CBAM。否则训练速度下降会很明显。第二个问题是CA的坐标池化对某些任务不友好。如果你的任务里目标的形态是高度各向异性的比如文本检测里细长的文字区域或者长条形的条形码CA的水平方向和垂直方向分解其实会损失一些对角方向的位置关系。这种情况我试过上采样特征金字塔再插CA效果好一些。第三个问题是BN和注意力的协同。CA自身有BatchNorm如果你原来的网络已经有一层BatchNorm插入CA后等于同一个block里出现了两层BN这在某些极端情况下会导致训练不稳定。遇到这种情况可以把CA里的BN换成LayerNorm或训练时适当降低学习率。4. 自注意力与多头注意力机制4.1 从特征交互理解Q、K、V前面讲的SE、CBAM、CA本质上都是通过一个小网络生成权重然后去重标定原始特征。但自注意力是完全不同的路子它让特征内部两两之间直接交互输出的每个位置都带有全图的信息。自注意力的核心概念是Query、Key、Value也就是Q、K、V。我做个生活化的比喻你可以把Q理解成“你要问的问题”K是“资料库里的索引标签”V是“标签对应的正文内容”。你在图书馆查资料时会拿问题去和所有标签做匹配匹配度高的就重点阅读对应的正文匹配度低就看一眼带过。工程实现上对于序列输入 (X \in R^{B,N,D})其中 (N) 是序列长度(D) 是特征维度分别通过三个可学习的线性变换得到 (Q XW_Q)、(K XW_K)、(V XW_V)。然后计算注意力权重矩阵[ Attention(Q,K,V) softmax(\frac{QK^T}{\sqrt{d_k}})V ]这里的 (QK^T) 计算的是任意两个位置之间的相似度除以 (\sqrt{d_k}) 是为了防止点积结果过大导致softmax的梯度消失。(d_k) 就是Key向量的维度通常等于 (D/h)(h) 是注意力头数。softmax归一化之后每一行权重之和为1表示某个位置对其他所有位置的关注度分布最后用权重去加权求和V就得到了输出。自注意力最大的优势是能捕捉长距离依赖。CNN靠堆叠卷积层扩大感受野RNN靠逐步传递隐状态都存在长距离信息衰减的问题而自注意力一步到位任意两个位置之间只隔了两次线性变换加一次点积。这也是Transformer为什么能碾压RNN和CNN成为主流架构的核心原因之一。4.2 多头为什么要“多”多头自注意力机制就是把上面的自注意力过程并行做 (h) 次每次用不同的 (W_Q、W_K、W_V)得到 (h) 个不同的输出然后把它们拼接起来再过一次线性变换。这样做的意义类比一下你看一幅画时可能同时关注构图、色彩、线条和细节不同关注点看到的是不同维度的信息。多头自注意力也一样不同的头可以在不同的子空间里学习特征之间的关系。有的头可能倾向于关注语义相关的词有的头关注句法位置有的头关注视觉上的空间相邻性。一个头捕捉不全多个头互补模型整体的表达能力就上来了。在实际使用中头数和每个头的维度是一个关键配置。一般经验是保持 (D h \times d_k)比如 (D512) 时用 (h8)每个头 (d_k64)。头数不是越多越好。头数太多每个头的维度太低能捕捉的信息就有限头数太少不同层面的关注点可能覆盖不全。我在文本分类任务上试过Bert-base的 (h12) 调成 (h6) 之后参数量降了但准确率也掉了约1个百分点效果并不划算。还有一个经常被忽略的细节多头注意力里不同头可能出现退化就是某些头的输出几乎相同没有学到差异化信息。特别是训练数据量不大的时候特别容易发生。一个缓解方案是增加attention头的dropout或者对注意力矩阵做“DropKey”操作随机掩盖部分key迫使每个头学习不同的模式。4.3 自注意力落地的尺寸与计算问题自注意力很强但它有个绕不开的坎计算复杂度随序列长度 (N) 呈平方级增长即 (O(N^2))。原因就是注意力矩阵 (QK^T) 的形状是 (N \times N)。对于文本序列几百个token的复杂度还能接受但放到视觉任务里如果输入是 (56 \times 56) 的特征图展平成序列就有3136个位置注意力矩阵就有约983万个元素显存直接告急。在CV任务里落地自注意力目前比较可行的又比较实用的做法有几类。一类是窗口注意力比如Swin Transformer把特征图切成固定大小的窗口只在窗口内做自注意力然后通过shift窗口来交互信息一类是局部注意力叠加全局稀疏注意力比如对特征图每隔几个位置采样一次做全局交互再插值回去还有一类就是线性注意力用核技巧把 (QK^T) 的计算近似成先算 (K^TV) 再算 (Q(K^TV))把复杂度从 (O(N^2)) 降到 (O(N))。我自己的习惯是如果输入图片分辨率不大比如224x224分类任务直接整图自注意力问题不大如果做的是检测、分割这种高分辨率输入的密集预测任务第一个stage尽量别上全局自注意力先在低分辨率的深层特征上用或者用窗口注意力。5. 时序注意力处理时间依赖的方向5.1 什么时候需要时序注意力热词里有一个“时序注意力机制”这类注意力在NLP和时间序列预测、语音、视频任务里用得很多。它要解决的核心问题是在一个时间序列里哪些时间步对当前预测更重要。举个生活化的例子预测明天是否会下雨时今天凌晨2点的气压读数可能和明天早上8点的降雨没有直接关系而前天同一时段的湿度趋势参考价值更大。时序注意力的任务就是自动学出这种“什么时间的信息权重更高”。需要时序注意力的典型场景包括传感器信号故障诊断比如电机振动信号里某一小段是故障特征股票价格走势预测关键拐点信息更重要语音识别不同音素在不同时间位置的重要性不同视频分类动作发生在视频的某个片段里。5.2 时序注意力的常见实现思路理解时序注意力之前要知道一个更早也经典的做法Encoder-Decoder加BahdanauAttention。在机器翻译里Encoder把源语言句子编码成一系列隐状态Decoder在生成每个目标词时不是只看最后一个隐状态而是通过注意力把所有源语言隐状态加权求和权重由当前Decoder状态和各个Encoder隐状态的匹配度算出。这本质上就是一种时序注意力只是它应用在跨序列交互上。在纯时间序列任务里更常见的是把一个时间窗口的序列特征通过LSTM或者一维CNN编码然后在时间维度上做注意力。比如先用LSTM得到每个时间步的隐状态 (h_1, h_2, ..., h_T)然后通过一个小网络计算出每个时间步的注意力权重最后做加权求和得到序列的全局表示再喂给分类器或回归头。还有一个思路是把时间维度和通道维度结合起来设计比如在通道注意力里引入时间上下文。我曾在一个电力负荷预测项目里用过类似TAMTemporal Attention Module的结构序列特征先经过一维CNN提取局部时间信息然后对时间维度做全局平均池化和最大池化拼接后通过卷积生成时间权重最后对每个时间步加权。相比直接用LSTMattention这个模块训练速度快很多效果也没有明显下降。5.3 时序注意力在工程中的坑时序注意力在工程里最大的坑是信息泄漏这是个非常严重的问题。进行时间序列预测时你的模型只能用截止到当前时刻的数据去预测未来绝不能使用未来信息。很多人在实现时序注意力时用的是全局softmax即某个时间步会关注到所有时间步包括它后面的未来步。这在离线训练时可能效果很好因为网络“作弊”提前看到了未来但线上部署时未来数据还没有出现模型性能会暴跌。解决办法是使用带mask的注意力保证计算某个时间步的输出时attention只能看到当前时刻及之前的信息。PyTorch里可以用attn_mask参数或者手动把未来位置填充为 (-\infty)。另一个坑是序列长度的选择。时序注意力的作用范围受输入窗口长度限制窗口太短注意力看不到足够的历史上下文窗口太长计算延迟和显存占用上升。我的经验是先做简单的自相关分析确定序列的周期性或依赖长度再设定窗口。比如电力负荷数据有24小时周期窗口至少得覆盖一个完整周期。还有一个容易被忽略的点是时序特征的对齐。LSTM输出的隐状态在不同时间步的尺度可能有差异如果直接把原始时间步特征做注意力训练初期可能出现权重分布不均。我一般会在注意力打分之前对特征做LayerNorm让每个时间步的特征都保持在相近的尺度上这样注意力训练更稳定。6. 注意力机制选型建议与问题排查技巧6.1 选型建议从任务和硬件出发注意力机制这么多最怕一上来就乱堆。我自己的选型路径是先回答三个问题数据是什么类型的任务对位置信息敏感吗硬件算力限制是多少。如果主要做图像分类或移动端小模型通道注意力是首选优先用ECA其次SE。ECA参数量小效果好工程成熟。如果你的任务里目标位置信息很重要比如目标检测、关键点检测、小目标分类那在通道注意力的基础上可以加CA或切换为CBAM。CA更适合轻量网络CBAM更适合重视精度、算力宽裕的模型。如果处理的是序列数据比如文本、语音、传感器信号先判断序列长度。序列在100以内可以直接用自注意力Transformer Block序列在几千或几万级别考虑窗口注意力、稀疏注意力或线性注意力。如果对可解释性有需求传统的Encoder-Decoder加BahdanauAttention反而更容易观察权重。如果任务需要同时建模空间和时间信息比如视频分类那就不要想着用一个模块全搞定。我在做视频动作识别的时候合理的做法是空间维度上用2D卷积处理单帧时间维度上用时序注意力或多头自注意力跨帧交互两个维度分开建模再融合比塞一个重量级的3D注意力模块效果更好训练也更容易收敛。6.2 典型问题排查实录我把实践中经常遇到的几个注意力相关问题和排查思路整理成了速查表方便直接对照。现象可能原因排查思路与建议插入注意力后效果不升反降模块插入位置不合理浅层特征被过度重标定只保留深层stage的注意力浅层不放训练初期loss剧烈波动注意力初始权重偏离1太多破坏预训练特征分布初始化最终权重层为1或先冻结注意力模块训练几轮再解冻模型体积变大推理变慢SE的全连接或CBAM的大卷积开销大换成ECA或把CBAM空间卷积分支的7x7改成3x3自注意力显存溢出序列长度太长注意力矩阵过大使用窗口注意力、下采样或线性注意力多头注意力各个头学习到相同模式训练数据不足或Dropout过高降低attention dropout或使用DropKey时序注意力训练表现好但线上崩注意力引入了未来信息造成泄漏检查attention mask训练时强制mask掉未来位置加了CA后训练速度明显变慢CA中两个方向池化多次卷积开销被低估检查是否在高分辨率特征图上插入移到低分辨率stage6.3 一些实战心得最后聊一点我自己的体会。深度学习项目里有一条很朴素的规律加模块容易去模块很难。尤其是注意力这种即插即用的组件一开始谁都忍不住多塞几个结果训练变慢、过拟合、效果还掉了。我现在的做法是先把Base模型跑到一个预期的水平用一个固定的验证集记录指标然后像做对照实验一样每次只加一个注意力模块同一个权重初始化种子同一套超参数跑完对比。选择注意力模块的时候不要只看最终精度还要算清楚代价。我一般在消融实验里同时记录三个指标准确率、FLOPs、推理延迟。有的模块精度涨了0.3%但推理耗时多了15%对于实时项目来说完全不划算。ECA之所以在很多工业项目里受欢迎就是因为它用极小的代价换到了接近SE甚至超过SE的效果。还有一个容易被低估的点是随机种子和初始化。注意力模块有时候对种子非常敏感同一个模块换一个种子效果可能从0.8%变成-0.2%。遇到这种情况不要急着否定模块先跑3个不同的种子看看均值和方差。如果均值是正的、方差大那说明模块有效但不稳定可以考虑加一些正则或者调整初始化方法如果均值本身就是负的那大概率是这个模块和你的任务不匹配不要在它身上继续浪费时间。时序注意力也是一样不同窗口长度对结果的影响非常大。我有一次做传感器故障诊断窗口从128换成256准确率掉了3个百分点原因就是过长的窗口引入了大量无关的历史噪声。后来用滑动窗口加时间衰减权重才把注意力重新聚焦到故障前的关键片段上。注意力机制本质上是一种先验它告诉你的网络“某些信息更重要”。用对了它是放大器用不对它就是噪声源。希望这篇文章能帮你把这些先验放到正确的位置上。
返回列表