013、GAM全局注意力与MHSA多头自注意力:全局上下文建模在检测头中的改进
013、GAM全局注意力与MHSA多头自注意力全局上下文建模在检测头中的改进一个让我熬夜到凌晨三点的bug上个月做工业缺陷检测项目客户要求检测PCB板上的微小划痕。YOLOv8s跑出来的结果让我血压飙升——大块缺陷召回率95%但那些只有几个像素的划痕召回率不到40%。我盯着检测头输出的特征图看了半天发现深层特征里小目标的响应几乎被背景噪声淹没了。这就是典型的全局上下文缺失问题。YOLOv8的检测头虽然用了C2f结构但本质上还是局部感受野的堆叠小目标在深层特征中信息量太少注意力机制根本分配不到足够的权重。我试了试在检测头里加SE模块效果有提升但有限SE只是通道维度的全局压缩空间信息丢失严重。GAM全局注意力别被名字骗了GAMGlobal Attention Mechanism是我在某个工业检测竞赛的冠军方案里看到的。它的设计思路很直接——同时保留通道和空间维度的全局信息而不是像SE那样先压缩再激活。GAM的结构分三部分通道注意力子模块、空间注意力子模块、以及一个残差连接。通道部分用了一个全局平均池化两个全连接层这跟SE差不多。但空间部分有意思——它用了两个卷积层来生成空间注意力图而且这两个卷积的核大小是7x7感受野足够大。我在YOLOv8的检测头里加GAM时踩了个坑。一开始我把GAM放在每个检测分支的最后结果训练loss降不下去。后来发现GAM的输入输出维度必须一致而YOLOv8的检测头里不同层的通道数不一样我忘了做维度匹配。正确的做法是在每个检测分支的卷积层之间插入GAM而不是在分支末尾。# 别这样写——维度不匹配会报错classDetect(nn.Module):defforward(self,x):foriinrange(self.nl):x[i]self.cv2[i](x[i])x[i]self.cv3[i](x[i])x[i]GAM(x[i])# 这里维度可能对不上# 正确做法——在cv2和cv3之间插入classDetect(nn.Module):defforward(self,x):foriinrange(self.nl):x[i]self.cv2[i](x[i])x[i]GAM(x[i])# cv2输出维度固定可以接GAMx[i]self.cv3[i](x[i])加了GAM之后小目标的召回率从40%提到了55%。但还不够因为GAM虽然保留了空间信息但它的空间注意力还是基于局部卷积生成的对于长距离依赖关系建模能力有限。MHSA多头自注意力杀鸡用牛刀MHSAMulti-Head Self-Attention是Transformer的核心组件把它塞进检测头听起来有点疯狂——计算量太大了。但我发现一个trick只在检测头的最后一层用MHSA而且只用在最大的特征图上P3层80x80分辨率这样计算量可控。MHSA在检测头里的实现有个关键点位置编码。YOLOv8的特征图是有空间结构的直接套用ViT那种全局自注意力会丢失位置信息。我试了两种方案绝对位置编码和相对位置编码。绝对位置编码简单直接加一个可学习的position embedding但泛化性差换分辨率就得重新训练。相对位置编码效果好但实现复杂。这里分享一个折中方案——用卷积实现局部位置编码。在MHSA之前加一个3x3的depthwise卷积相当于给每个位置注入邻域信息这样自注意力就能感知到局部结构。这个trick来自Swin Transformer的启发但实现起来简单得多。# 这里踩过坑——MHSA的qkv计算要小心维度classMHSA(nn.Module):def__init__(self,dim,num_heads8):super().__init__()self.num_headsnum_heads self.qkvnn.Linear(dim,dim*3)# 一次性生成qkv# 注意dim必须能被num_heads整除否则报错assertdim%num_heads0,dim must be divisible by num_heads把MHSA加到检测头后小目标召回率提到了62%。但代价是推理速度慢了15%而且显存占用增加了。对于实时检测场景这个trade-off需要权衡。GAM MHSA双剑合璧的坑与甜既然GAM和MHSA各有优势我尝试把它们组合起来。方案是在检测头的每个分支先过GAM增强局部-全局特征再过MHSA建模长距离依赖。但直接串联会导致梯度消失——GAM的输出经过MHSA后特征分布被严重打乱。解决方案是在GAM和MHSA之间加一个LayerNorm并且用残差连接把GAM的输入直接加到MHSA的输出上。这个结构有点像Transformer的encoder block但去掉了FFN层因为检测头不需要那么深的非线性变换。组合后的效果小目标召回率从40%提到了70%mAP提升了3.2个点。但有个副作用——大目标的检测精度下降了0.5个点。分析原因是全局注意力让模型过于关注小目标的细节反而忽略了大目标的整体结构。解决办法是在loss里对不同大小的目标加权小目标权重提高大目标权重降低。工程落地的血泪教训计算量控制GAM和MHSA都吃显存。我的经验是只在P3层80x80用MHSAP4和P5层只用GAM。这样计算量增加控制在20%以内。训练技巧加了注意力机制后学习率要调小。我原来用0.01改成0.005才稳定。另外warmup步数要增加从3个epoch改成5个epoch让注意力模块慢慢适应。部署注意ONNX导出时MHSA的reshape操作可能会报错。解决方案是用torch.nn.functional.scaled_dot_product_attention替代手动实现这个函数在ONNX导出时更友好。数据增强全局注意力对遮挡敏感。如果训练数据里遮挡样本少测试时遇到遮挡会崩。建议在训练时增加RandomErasing或Cutout增强。个人经验总结全局注意力不是银弹。如果你的任务是大目标检测比如行人、车辆加GAM就够了MHSA带来的提升有限。但如果你做小目标检测比如缺陷、遥感目标GAMMHSA的组合值得一试。最后说一句不要迷信注意力机制。我见过有人把检测头改成纯Transformer结构结果训练都训不收敛。YOLOv8的C2f结构本身就很优秀注意力机制只是锦上添花不是雪中送炭。先保证baseline足够强再考虑加注意力。下一章我会分享如何在Neck部分改进多尺度特征融合这比检测头改进更实用效果也更明显。