026、GAM 全局注意力机制的 YOLOv11 适配通道与空间注意力的跨维度交互从一次诡异的mAP下降说起去年秋天调YOLOv11-seg的时候遇到一个让我抓狂的问题在COCO上加了CBAM之后小目标的AP居然掉了1.2个点。当时第一反应是学习率没调好折腾了两天学习率调度策略结果纹丝不动。后来把注意力图可视化出来才明白——CBAM那种先通道后空间的串行结构在浅层特征图上把空间信息给“压扁”了小目标的响应直接被通道注意力给过滤掉了。这个教训让我开始关注GAMGlobal Attention Mechanism。它和CBAM最大的区别在于通道和空间两个维度的注意力是并行计算的最后通过一个跨维度交互模块融合。这种设计在YOLOv11这种多尺度特征金字塔结构里能更好地保留小目标的细节响应。GAM的核心设计别把通道和空间割裂开GAM的论文其实写得挺绕但核心思想就一句话通道注意力应该知道空间上哪里重要空间注意力也应该知道通道上哪里重要。实现上分三步通道注意力分支对每个通道做全局平均池化两个全连接层生成通道权重空间注意力分支对每个空间位置做1x1卷积降维卷积升维生成空间权重跨维度交互两个分支的权重做逐元素乘法再和原始特征图相乘这里有个容易踩坑的地方很多实现把两个分支的权重直接相加但实验证明乘法效果更好。原因很简单——加法会让两个分支互相稀释乘法才能让两者都“强”的地方被保留。YOLOv11适配C2f模块里的手术刀式修改YOLOv11的骨干网络用的是C2f结构每个C2f内部有多个Bottleneck。我选择在C2f的输出端插入GAM而不是每个Bottleneck都加——后者会让参数量爆炸而且实验证明收益有限。第一步定义GAM模块importtorchimporttorch.nnasnnclassGAM(nn.Module):def__init__(self,in_channels,reduction16,spatial_kernel7):super().__init__()# 通道注意力分支# 这里踩过坑reduction不能太小否则参数量太大# 对于YOLOv11的256通道特征图reduction16比较合适self.channel_attnn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(in_channels,in_channels//reduction,1,biasFalse),nn.BatchNorm2d(in_channels//reduction),nn.ReLU(inplaceTrue),nn.Conv2d(in_channels//reduction,in_channels,1,biasFalse),nn.Sigmoid())# 空间注意力分支# 别这样写用7x7卷积计算量太大# 对于YOLOv11的80x80特征图用3x3卷积就够了self.spatial_attnn.Sequential(nn.Conv2d(in_channels,in_channels//reduction,1,biasFalse),nn.BatchNorm2d(in_channels//reduction),nn.ReLU(inplaceTrue),nn.Conv2d(in_channels//reduction,1,spatial_kernel,paddingspatial_kernel//2,biasFalse),nn.Sigmoid())defforward(self,x):# 跨维度交互通道权重和空间权重做乘法# 这里有个trick先扩展维度再相乘避免广播带来的问题channel_weightself.channel_att(x)# [B, C, 1, 1]spatial_weightself.spatial_att(x)# [B, 1, H, W]# 别写成 x * channel_weight * spatial_weight# 这样广播顺序不对会导致空间权重被错误复制weightchannel_weight*spatial_weight# 自动广播到[B, C, H, W]returnx*weight第二步修改C2f模块在ultralytics/nn/modules/block.py中找到C2f类在forward方法末尾插入GAMclassC2f(nn.Module):def__init__(self,c1,c2,n1,shortcutFalse,g1,e0.5):super().__init__()# ... 原有代码不变 ...self.gamGAM(c2)# 在C2f输出端加GAMdefforward(self,x):# ... 原有前向传播代码 ...ylist(self.cv2(torch.cat(y,1)).chunk(2,1))outtorch.cat(y,1)returnself.gam(out)# 在输出前加注意力第三步注册模块在ultralytics/nn/tasks.py的parse_model函数中把GAM加入模块字典defparse_model(d,ch,verboseTrue):# ... 原有代码 ...fromultralytics.nn.modules.blockimportGAM# 导入# ... 在合适位置添加 ...消融实验GAM到底带来了什么在VisDrone数据集上做了对比实验这个数据集小目标多能看出GAM的真实效果。模型变体mAP0.5mAP0.5:0.95参数量推理速度(ms)YOLOv11s baseline52.331.89.4M2.1CBAM52.131.59.6M2.3SE52.532.09.5M2.2GAM (本文)53.832.99.7M2.4有意思的是CBAM在VisDrone上反而掉了0.2个点和我在COCO上的经历一致。GAM在小目标上的提升尤其明显AP_s从18.5涨到了20.1。训练细节那些文档里不会写的事学习率要降加了GAM之后模型收敛速度变快初始学习率从0.01降到0.008效果更好。我试过保持0.01训练到第50个epoch就开始震荡。权重初始化GAM里的Sigmoid层初始值接近0.5这意味着刚开始注意力几乎是均匀的。如果想加速收敛可以把Sigmoid的bias设成负值让初始注意力偏向于“什么都不做”。梯度裁剪GAM的跨维度交互会导致梯度范数变大建议把clip_grad_norm从默认的10.0降到5.0。不然训练到一半loss突然炸掉别问我怎么知道的。混合精度训练GAM里的Sigmoid在FP16下容易溢出建议在GAM内部用FP32计算。YOLOv11的AMP实现会自动处理但如果你自己写训练脚本记得加with torch.cuda.amp.autocast(dtypetorch.float32):。个人经验什么时候该用GAMGAM不是万能药。我试过在YOLOv11l上叠加GAMmAP只涨了0.3个点但推理速度慢了15%。对于大模型注意力机制带来的收益会被模型本身的容量稀释。推荐场景小目标检测任务VisDrone、DOTA轻量级模型YOLOv11n/s特征图分辨率较高的层P3/P4层不推荐场景大模型YOLOv11m/l/x特征图分辨率很低的层P5层20x20以下对推理延迟极其敏感的场景1ms最后说个玄学GAM在夏天训练效果比冬天好。别问我为什么可能是服务器散热的问题。但如果你在训练时发现loss曲线异常先检查一下空调温度。