目录
一、引言
二、注意力优势、结构图、代码
2.1 CA注意力
2.2 ECA注意力
2.3 GAM注意力
2.4 CAA注意力
1. CAA(Context Anchor Attention)的优势特点
2. 在本文中的突出贡献
三、逐步手把手添加CA/ECA/GAM/CAA注意力
3.1 第一步
3.2 第二步
3.3 第三步
3.4 第四步
四 完整yaml
4.1 YOLO11-MM前期(early)加入CA、ECA、GAM、CAA
4.2 YOLO11-MM中期(middle)加入CA、ECA、GAM、CAA
4.3 YOLO11-MM后期(late)加入CA、ECA、GAM、CAA
五 训练代码和结果
5.1 模型训练代码
5.2 模型训练结果
六 总结
一、引言
本文主要围绕YOLO11-MM 多模态目标检测展开,重点探讨在网络中引入 CA/ECA/GAM/CAA等注意力机制的核心思路与具体实践。我们将 CA/ECA/GAM/CAA分别置于模态融合路径的前期、中期和后期,对不同插入位置进行改进与性能对比,系统分析在不同阶段引入注意力,对特征表达能力和最终检测效果带来的影响。
需要强调的是,YOLO11-MM 多模态目标检测与 RTDETR-MM 多模态目标检测在设计取向上存在明显差异:
YOLO11-MM 更偏向轻量化与工程友好,结构更易于改进和融合各类增强模块,在推理速度、部署体验,以及训练稳定性与收敛性方面具有更明显的优势。
在实验过程中,我们一方面通过合理选择注意力模块的插入位置,尽可能增强模型的特征表示能力;另一方面也尽量控制额外参数量,在较低计算与存储开销的前提下,获得尽可能高的检测精度。近期我们已完成大量实验并对结果进行了系统整理,希望能在多模态目标检测方向的研究与工程实践中,为大家节省一定的调参与反复验证成本。
需要特别说明的是:本文所使用的数据集为 FLIR 数据集的一个子集,而非完整 FLIR 数据集,后续在复现或扩展实验时请务必留意这一点。也希望这篇文章能为正在攻关多模态目标检测的同学提供一些有价值的参考。
二、注意力优势、结构图、代码
2.1 CA注意力
优势特点:
将通道注意力和坐标信息显式结合,沿 H、W 两个方向做聚合,既建模长程依赖,又保留精确位置信息;
对细粒度空间位置信息更敏感,更利于小目标、远距离目标的定位;
计算开销适中,相比复杂自注意力更轻量,易于插入现有网络。
在本文中的突出贡献:
将 CA 布置在模态融合前期,可以在刚开始对齐可见光与红外特征时,就引导网络关注“在哪儿更重要”;
有助于在复杂场景下压制无关区域、突出多模态一致的目标区域,为后续特征融合打下更好的空间对齐基础。
重要意义:
说明在多模态检测中,显式编码坐标信息的注意力对跨模态对齐和目标定位尤为关键;
为后续设计多模态骨干和融合模块提供了一个清晰的思路:在早期特征阶段应优先考虑带有空间先验的注意力形式。
CA核心代码:
import torch import torch.nn as nn import math import torch.nn.functional as F class h_sigmoid(nn.Module): def __init__(self, inplace=True): super(h_sigmoid, self).__init__() self.relu = nn.ReLU6(inplace=inplace) def forward(self, x): return self.relu(x + 3) / 6 class h_swish(nn.Module): def __init__(self, inplace=True): super(h_swish, self).__init__() self.sigmoid = h_sigmoid(inplace=inplace) def forward(self, x): return x * self.sigmoid(x) class CoordAtt(nn.Module): def __init__(self, inp, reduction=32): super(CoordAtt, self).__init__() oup = inp self.pool_h = nn.AdaptiveAvgPool2d((None, 1)) self.pool_w = nn.AdaptiveAvgPool2d((1, None)) mip = max(8, inp // reduction) self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0) self.bn1 = nn.BatchNorm2d(mip) self.act = h_swish() self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0) self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0) def forward(self, x): identity = x n, c, h, w = x.size() x_h = self.pool_h(x) x_w = self.pool_w(x).permute(0, 1, 3, 2) y = torch.cat([x_h, x_w], dim=2) y = self.conv1(y) y = self.bn1(y) y = self.act(y) x_h, x_w = torch.split(y, [h, w], dim=2) x_w = x_w.permute(0, 1, 3, 2) a_h = self.conv_h(x_h).sigmoid() a_w = self.conv_w(x_w).sigmoid() out = identity * a_w * a_h return out2.2 ECA注意力
优势特点:
不做通道压缩,通过一维卷积实现局部跨通道交互,结构极其简洁;
额外参数和计算量极小,几乎不影响模型推理速度,非常适合部署场景;
在保持轻量的同时,能够有效突出关键通道、抑制冗余通道。
在本文中的突出贡献:
将 ECA 布置在模态融合中期,用于在已经对齐的多模态特征上进一步筛选“哪几类通道更有用”;
在几乎不增加开销的前提下,提升了特征表达的判别性,实现了精度–复杂度的更优折中。
重要意义:
证明了在多模态检测中,轻量级通道注意力足以带来可观收益,非常适合实际工程部署;
为后续在边缘设备、实时系统中引入注意力机制提供了可行范例:优先考虑类似 ECA 这类低成本模块。
ECA核心代码
import torch from torch import nn from torch.nn.parameter import Parameter class ECA(nn.Module): """Constructs a ECA module. Args: channel: Number of channels of the input feature map k_size: Adaptive selection of kernel size """ def __init__(self, channel, k_size=3): super(ECA, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.conv = nn.Conv1d(1, 1, kernel_size=k_size, padding=(k_size - 1) // 2, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): # feature descriptor on the global spatial information y = self.avg_pool(x) # Two different branches of ECA module y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1) # Multi-scale information fusion y = self.sigmoid(y) return x * y.expand_as(x)2.3 GAM注意力
优势特点:
同时建模全局通道依赖与空间分布,实现更充分的通道–空间交互;
感受野更大,对复杂背景、长距离依赖建模能力强;
相比单纯的 SE/CBAM 等模块,对全局上下文的利用更加充分。=
在本文中的突出贡献:
将 GAM 布置在模态融合后期/检测头前,在高层语义特征上做一次“全局重整”;
有助于在最终检测阶段进一步压制伪目标和背景干扰,提升对难例、拥挤场景的鲁棒性。
重要意义:
展示了在多模态检测框架中,在后期引入更强的全局注意力,可以显著提升上限性能;
为后续工作提供启示:在靠近检测头的高语义阶段,适当使用稍“重”的全局注意力模块,是提升检测质量的有效途径。
核心代码:
import torch import torch.nn as nn ''' https://arxiv.org/abs/2112.05561 ''' class GAM(nn.Module): def __init__(self, in_channels, rate=4): super().__init__() out_channels = in_channels in_channels = int(in_channels) out_channels = int(out_channels) inchannel_rate = int(in_channels / rate) self.linear1 = nn.Linear(in_channels, inchannel_rate) self.relu = nn.ReLU(inplace=True) self.linear2 = nn.Linear(inchannel_rate, in_channels) self.conv1 = nn.Conv2d(in_channels, inchannel_rate, kernel_size=7, padding=3, padding_mode='replicate') self.conv2 = nn.Conv2d(inchannel_rate, out_channels, kernel_size=7, padding=3, padding_mode='replicate') self.norm1 = nn.BatchNorm2d(inchannel_rate)