首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >YOLO26魔改:SAA+C2PSA | 选择性聚合全局上下文+空间注意力聚焦,小目标与遮挡检测跃升新高度 | CVPR2026 SAA

YOLO26魔改:SAA+C2PSA | 选择性聚合全局上下文+空间注意力聚焦,小目标与遮挡检测跃升新高度 | CVPR2026 SAA

原创
作者头像
AI小怪兽
发布2026-09-08 12:43:43
发布2026-09-08 12:43:43
470
举报
文章被收录于专栏:毕业设计毕业设计YOLO大作战

💡💡💡将 SAA 与 C2PSA 模块结合,可形成高效的全局-局部协同机制,显著提升检测性能。其优势与涨点效果逐点如下:

  1. 协同机制:SAA通过选择性聚合键值令牌,大幅降低计算成本,同时保持全局感受野,增强远距离依赖建模;C2PSA则通过跨阶段空间注意力聚焦关键局部区域,强化小目标与低对比度特征。两者互补,显著提升复杂场景下的特征表达能力。
  2. 效率收益:该组合在保持高精度的同时,显著降低FLOPs约27%,实现轻量化部署,提升推理速度。
  3. 涨点表现:在YOLO26基础上,该组合在多个数据集上稳定提升mAP 1.5%~2%,尤其对遮挡、密集和低对比度目标检测精度提升显著,展现了优异的泛化性和鲁棒性。

💡💡💡本文改进:SAA块结合YOLO26的C2PSA模块,全网首发创新。

博主简介

AI小怪兽 | 计算机视觉布道者 | 视觉检测领域创新者

深耕计算机视觉与深度学习领域,专注于目标检测前沿技术的探索与突破。长期致力于YOLO系列算法的结构性创新、性能极限优化与工业级落地实践,旨在打通从学术研究到产业应用的最后一公里。

🚀 核心专长与技术创新

  • YOLO算法结构性创新:于CSDN平台原创发布《YOLOv13魔术师》、《YOLOv12魔术师》等全系列深度专栏。系统性提出并开源了多项原创自研模块,在模型轻量化设计、多维度注意力机制融合、特征金字塔重构等关键方向完成了一系列突破性实践,为行业提供了具备高参考价值的技术路径与完整解决方案。
  • 技术生态建设与知识传播:独立运营 “计算机视觉大作战” 公众号(粉丝1.6万),成功构建高质量的技术交流社群。致力于将复杂算法转化为通俗易懂的解读与可复现的工程代码,显著降低了计算机视觉的技术入门门槛。

🏆 行业影响力与商业实践

  • 荣获腾讯云年度影响力作者创作之星奖项,内容质量与专业性获行业权威平台认证。
  • 全网累计拥有 7万+ 垂直领域技术受众,专栏文章总阅读量突破百万,在目标检测领域形成了广泛的学术与工业影响力。
  • 具备丰富的企业级项目交付经验,曾为工业视觉检测、智慧城市安防等多个关键领域提供定制化的算法模型与解决方案,驱动业务智能化升级。

💡 未来方向与使命

秉持 “让每一行代码都有温度” 的技术理念,未来将持续聚焦于实时检测、语义分割及工业缺陷检测的商业化闭环等核心方向。愿与业界同仁协同创新,共同推动技术边界,以坚实的技术能力赋能实体经济与行业变革。

1.YOLO26原理介绍

论文:https://arxiv.org/pdf/2509.25164

摘要:本研究对Ultralytics YOLO26进行了全面分析,重点阐述了其关键架构改进及其在实时边缘目标检测中的性能基准测试。YOLO26于2025年9月发布,是YOLO系列最新、最先进的成员,专为在边缘及低功耗设备上实现高效、精确且易于部署的目标而构建。本文依次详述了YOLO26的架构创新,包括:移除了分布焦点损失(DFL);采用端到端的无NMS推理;集成了渐进损失(ProgLoss)与小目标感知标签分配(STAL);以及引入了用于稳定收敛的MuSGD优化器。除架构外,本研究将YOLO26定位为多任务框架,支持目标检测、实例分割、姿态/关键点估计、定向检测及分类。我们在NVIDIA Jetson Nano与Orin等边缘设备上呈现了YOLO26的性能基准测试,并将其结果与YOLOv8、YOLOv11、YOLOv12、YOLOv13及基于Transformer的检测器进行比较。本文进一步探讨了其实时部署路径、灵活的导出选项(ONNX、TensorRT、CoreML、TFLite)以及INT8/FP16量化技术。文章重点展示了YOLO26在机器人、制造业及物联网等领域的实际应用案例,以证明其跨行业适应性。最后,讨论了关于部署效率及更广泛影响的见解,并展望了YOLO26及YOLO系列的未来发展方向。

关键词:YOLO26;边缘人工智能;多任务目标检测;无NMS推理;小目标识别;YOLO(You Only Look Once);目标检测;MuSGD优化器

结构框图如下:

1.1 YOLO11 vs YOLO26结构差异性

1.1.1 SPPF 核心差异对比

1)池化次数灵活性:YOLO11 的 3 次池化是硬编码的,要修改必须改源码;YOLO26 通过n参数可灵活调整(比如设为 2 次或 4 次),无需改核心逻辑。

2)Shortcut 设计:YOLO26 新增的残差连接能缓解深层网络的梯度消失问题,提升特征复用能力,而 YOLO11 无此设计。

3)激活函数控制:YOLO26 禁用 Conv1 的激活函数,让特征在池化前保持更 “原始” 的状态,是工程上对特征提取的优化。

源码位置:ultralytics/nn/modules/block.py

1.1.2 C3k2 核心差异对比

1)注意力机制的新增:YOLO26 的 C3k2 首次引入PSABlock(金字塔注意力模块)通过attn参数控制是否启用,这是两者最核心的功能差异 —— 启用后模块会先通过 Bottleneck 提取基础特征,再通过 PSABlock 增强关键区域的特征权重,提升小目标 / 复杂场景的检测效果。

2)分支逻辑的扩展:YOLO11 的分支仅受c3k控制,而 YOLO26 的分支逻辑优先级为attn > c3k,即只要attn=True,会优先启用注意力模块,忽略c3k的配置。

代码语言:javascript
复制
重复模块m (n次迭代):
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  如果 attn=True:                                        │
│    Sequential(                                          │
│        Bottleneck(self.c, self.c),                     │  ←─ 先特征提取
│        PSABlock(self.c, attn_ratio=0.5, num_heads=...) │  ←─ 后注意力增强
│    )                                                    │
│                                                         │
│  否则如果 c3k=True:                                     │
│    C3k(self.c, self.c, 2)                             │  ←─ 同YOLOv11
│                                                         │
│  否则:                                                  │
│    Bottleneck(self.c, self.c)                         │  ←─ 同YOLOv11
│                                                         │
└─────────────────────────────────────────────────────────┘

YOLO26 C3k2代码:

源码位置:ultralytics/nn/modules/block.py

1.2 YOLO26核心创新点

YOLO26引入了多项关键架构创新,使其区别于前几代YOLO模型。这些增强不仅提高了训练稳定性和推理效率,还从根本上重塑了实时边缘设备的部署流程。本节将详细描述YOLO26的四项主要贡献:(i)移除分布焦点损失(DFL),(ii)引入端到端无NMS推理,(iii)新颖的损失函数策略,包括渐进损失平衡(ProgLoss)和小目标感知标签分配(STAL),以及(iv)开发用于稳定高效收敛的MuSGD优化器。我们将详细讨论每一项架构增强,并通过对比分析突显其相对于YOLOv8、YOLOv11、YOLOv12和YOLOv13等早期YOLO版本的优势。

1.2.1 创新点1:移除分布焦点损失(DFL)

YOLO26最重要的架构简化之一是移除了分布焦点损失(DFL)模块(图3a),该模块曾存在于YOLOv8和YOLOv11等早期YOLO版本中。DFL最初旨在通过预测边界框坐标的概率分布来改进边界框回归,从而实现更精确的目标定位。虽然该策略在早期模型中展示了精度提升,但也带来了不小的计算开销和导出困难。在实践中,DFL在推理和模型导出期间需要专门处理,这使针对ONNX、CoreML、TensorRT或TFLite等硬件加速器的部署流程变得复杂。

源码位置:ultralytics/utils/loss.py

通过reg_max 设置为1,移除了分布焦点损失(DFL)

代码语言:javascript
复制
class BboxLoss(nn.Module):
    """Criterion class for computing training losses for bounding boxes."""

    def __init__(self, reg_max: int = 16):
        """Initialize the BboxLoss module with regularization maximum and DFL settings."""
        super().__init__()
        self.dfl_loss = DFLoss(reg_max) if reg_max > 1 else None

1.2.2 创新点2:端到端无NMS推理

YOLO26从根本上重新设计了预测头,以直接产生非冗余的边界框预测,无需NMS。这种端到端设计不仅降低了推理复杂度,还消除了对手动调优阈值的依赖,从而简化了集成到生产系统的过程。对比基准测试表明,YOLO26实现了比YOLOv11和YOLOv12更快的推理速度,其中nano模型在CPU上的推理时间减少了高达43%。这使得YOLO26对于移动设备、无人机和嵌入式机器人平台特别有利,在这些平台上,毫秒级的延迟可能产生重大的操作影响。

源码位置:ultralytics/utils/nms.py

1.2.3 创新点3:ProgLoss和STAL:增强训练稳定性和小目标检测

训练稳定性和小目标识别仍然是目标检测中持续存在的挑战。YOLO26通过整合两种新颖策略来解决这些问题:渐进损失平衡(ProgLoss)和小目标感知标签分配(STAL),如图(图3c)所示。

ProgLoss在训练期间动态调整不同损失分量的权重,确保模型不会过拟合于主导物体类别,同时防止在稀有或小类别上表现不佳。这种渐进式再平衡改善了泛化能力,并防止了训练后期的不稳定。另一方面,STAL明确优先为小目标分配标签,由于像素表示有限且易被遮挡,小目标尤其难以检测。ProgLoss和STAL共同为YOLO26在包含小目标或被遮挡目标的数据集(如COCO和无人机图像基准)上带来了显著的精度提升。

1.2.4 创新点4:用于稳定收敛的MuSGD优化器

YOLO26的最后一项创新是引入了MuSGD优化器(图3d),它结合了随机梯度下降(SGD)的优势与最近提出的Muon优化器(一种受大型语言模型训练中使用的优化策略启发而发展的技术)。MuSGD利用SGD的鲁棒性和泛化能力,同时融入了来自Muon的自适应特性,能够在不同数据集上实现更快的收敛和更稳定的优化。

源码位置:ultralytics/optim/muon.py

2.原理介绍

论文:https://arxiv.org/pdf/2604.07994

摘要:基于Transformer的方法通过建模长程依赖关系,在图像超分辨率领域带来了革命性进展。然而,原始自注意力机制的二次计算复杂度带来了显著挑战,往往导致在效率与全局上下文利用之间进行折衷。近期的基于窗口的注意力方法通过将计算局部化来缓解这一问题,但通常会产生受限的感受野。为克服这些局限,我们提出了选择性聚合Transformer。这种新颖的Transformer通过选择性地聚合键-值矩阵(借助我们的密度驱动令牌聚合算法,将令牌数量减少97%),同时保持查询矩阵的全分辨率,从而高效捕获长程依赖关系,实现模型感受野的扩大。该设计显著降低了计算成本,降低了复杂度,并能够在不牺牲重建保真度的前提下实现可扩展的全局交互。SAT利用密度和孤立性指标识别每个聚类并用单个聚合令牌表示,确保关键的高频细节得以保留。实验结果表明,SAT在最先进方法PFT的基础上最高可提升0.22dB,同时总FLOPs最高可减少27%。

3. 方法论

3.1. 动机

原始的 Self-Attention 因其二次计算复杂度而难以应用于超分辨率任务,这突显了对一种能够在低计算成本下捕获全局依赖关系的高效方法的需求。为此,我们分析了超分辨率输出与真实图像之间的逐像素绝对误差,观察到重建误差集中在高频区域,如图1所示。即使 PFT 取得了高性能,在这些区域仍然存在困难。我们的见解是,在超分辨率任务中,并非所有空间位置对重建的贡献都相等。密集特征/高频区域比均匀/低频区域携带更多信息。密集特征区域需要全局上下文来捕获长程依赖关系,而低频区域则可以安全地聚合,信息损失最小。这种不平衡促使我们提出了选择性聚合注意力,它在注意力计算期间选择性地合并用于键-值投影的低频令牌,同时保留高频令牌并在查询投影中保持关键细节以实现高质量重建。

3.2. 整体框架

SAT 的架构如图2所示。SAT 采用残差中的残差结构来构建深度特征提取。首先,输入图像 I_LR ∈ R^{H×W×3} 通过一个卷积层嵌入到 X_0 ∈ R^{H×W×C}。H、W、C 分别是图像的高度、宽度和通道数。X_0 被输入到包含 N_2 个残差 Transformer 块的残差组中以提取深层特征,然后通过一个卷积以获取细化后的特征 X_1 ∈ R^{H×W×C}。最后,X_0 和 X_1 通过残差连接融合,并传递到上采样模块以获取输出图像 I_SR ∈ R^{sH×sW×C},其中 s 是上采样因子。

每个 RTB 包含 N_1 个 Transformer 块和一个卷积。我们使用两种类型的 Transformer 块:局部 Transformer 块选择性聚合 Transformer 块。这些块以交替方式排列以建立全局-局部结构。我们的 SATB 专注于全局建模,而 LTB 则辅助提取补充深度特征提取的局部细节。每个块包括层归一化、一个注意力模块和一个多层感知机。

3.3. 选择性聚合注意力

我们形式化我们的选择性聚合注意力。给定一个输入特征 F ∈ R^{H×W×C},我们首先将其重塑为一个令牌序列 X ∈ R^{N×C},其中 N = HW 是令牌序列长度。原始的 Self-Attention 计算查询、键和值投影以及注意力输出如下: Q = X W_Q, K = X W_K, V = X W_V, (1a) Attention(Q, K, V) = softmax( (Q K^T) / √d ) V, (1b) 其中 W_Q、W_K、W_V 是可学习的投影矩阵,d 是注意力头维度。公式 1b 需要 O(N²d) 次操作来计算 N×N 矩阵 QK^T。相比之下,我们的 SAA 采用非对称压缩,在压缩键和值表示的同时保持全分辨率的查询。我们像在原始 Self-Attention 中一样计算 Q ∈ R^{N×d},但对 K 和 V 使用选择性聚合算子 Φ_SA : R^{N×d} → R^{K×d},得到 K' 和 V' ∈ R^{K×d}: K' = Φ_SA(X W_K), V' = Φ_SA(X W_V), (2) 其中 K 是压缩表示的数量。为进一步减少计算量,我们通过线性投影对 Q 和 K' 矩阵的通道维度进行缩放,比例因子为 r_c,如图3所示。然后,我们的 SAA 作为交叉注意力运行: SAA(Q, K', V') = softmax( (Q K'^T) / √(r_c d) ) V' (3) 该公式将计算复杂度从 O(N²d) 降低到 O(N K d),同时保持了输出中的全空间分辨率。通过保持全分辨率查询并压缩键和值,该设计利用了超分辨率中非对称信息需求:查询保留了精细空间结构以实现精确的高频细节恢复,而键和值则可以由原型特征紧凑地表示。

为了更好地提取全局-局部上下文信息,我们将我们的 SAA 与最近的局部注意力机制 Rwin-SA [13] 相结合,后者在各种低级视觉任务中都很有效。我们在表 5 中的消融实验证明,我们的全局-局部结构设计是网络的最优选择。

3.4. 密度驱动令牌聚合

我们提出密度驱动令牌聚合作为选择性聚合算子 Φ_SA。DTA 是密度峰值聚类原则的高效适配,专门为高维视觉令牌压缩而设计。Φ_SA 接收 N 个输入特征向量,并通过以下步骤产生 K 个语义上具有代表性的向量:密度引导的中心选择与分层子采样、令牌分配和相似度加权聚合。

密度引导的中心选择。我们的 DTA 选择具有高局部密度(表明有许多语义相似的邻居)且与其他密集区域距离较大的聚类中心,从而确保清晰的聚类间边界。对于每个令牌 x_i,我们使用基于余弦相似度的 k 近邻估计器计算其局部密度 ρ_i: s(x_i, x_j) = (x_i^T x_j) / (||x_i|| ||x_j||), (4a) ρ_i = (1/m) Σ_{j∈N_m(i)} s(x_i, x_j), (4b) 其中 N_m(i) 表示令牌 i 的 m 个最近邻。我们使用余弦相似度而非欧氏距离,因为在高维视觉特征空间中,角度关系能更好地捕获语义相似性,而基于幅度的距离则会受到集中效应的影响。

第二个量是到更高密度点的最小距离。我们首先将余弦相似度转换为距离: d(x_i, x_j) = 1 - s(x_i, x_j), (5a) δ_i = min_{j: ρ_j > ρ_i} d(x_i, x_j), (5b) 通常,δ_i 测量到具有更高密度 ρ_j > ρ_i 的最近令牌的最小距离。对于处于局部密度最大值的令牌,δ_i 被设置为到任何令牌的最大距离,确保这些密度峰值被优先选为聚类中心。

聚类中心选择标准将这两个属性组合成一个统一的分数: γ_i = ρ_i · δ_i, (6) 具有高 γ 值的令牌表现出高局部密度和大的分离度,使其成为理想的聚类代表。选择得分最高的 K 个令牌作为聚类中心 C = {c_1, ..., c_K}。

分层子采样。在所有 N 个令牌上计算密度和分离度量需要成对相似性评估,导致 O(N²C) 的复杂度,这与我们的效率目标相冲突。为在保持代表性特征覆盖的同时缓解此问题,我们引入了一种分层子采样策略。与假设令牌独立同分布的朴素随机采样不同,我们的方法考虑了自然图像的空间和语义结构,其中邻近像素共享相似特征,而远处区域则常常不同。

我们首先根据特征图中的光栅扫描顺序将 N 个令牌划分为 K 个空间上连续的区域。区域边界定义如下: R_i = { j : (i-1)⌊N/K⌋ ≤ j < i⌊N/K⌋ }, i ∈ {1, ..., K-1}, (7) 最后一个区域 R_K 包含所有剩余令牌以处理不可整除性。这种划分保持了空间的连续性,确保每个区域在特征图中形成一个连续的块。从每个 R_i 中,我们无放回地均匀采样 m_i = ⌊S/K⌋ 个令牌,其中 S = βK 是目标子采样大小,2 ≤ β < N/K 是子采样因子。具体来说,对于每个区域,我们计算 m_i = min( ⌊S/K⌋, |R_i| ) 以避免从包含少于目标样本大小的区域中过度采样。

区域子样本 S_i ⊂ R_i,|S_i| = m_i,然后合并形成最终子样本 S = ∪{i=1}^{K} S_i。如果由于区域大小不均匀或四舍五入,总样本量 |S| = Σ{i=1}^{K} m_i 小于目标值 S,我们通过从剩余未采样集合中均匀抽取额外的令牌来扩充 S。在构建子样本 S 后,我们在该子集内估计密度和分离统计数据。形成 S×S 子采样相似度矩阵 S_S = [s(x_i, x_j)]_{i,j∈S},对于每个令牌 i ∈ S,我们获得其局部密度 ρ̃_i、分离度 δ̃_i 和聚类中心分数 γ̃_i = ρ̃_i · δ̃_i。选择具有最高 γ̃_i 值的前 K 个令牌作为聚类中心,并映射回完整令牌序列中的原始索引。

令牌分配与相似度加权聚合。在中心选择之后,所有 N 个令牌根据余弦相似度分配到其最近的聚类中心: α(i) = argmax_{k∈{1,...,K}} s(x_i, c_k) (8) 我们使用相似度加权聚合来合并每个聚类中的令牌,而不是采用均匀平均(均匀平均平等对待所有聚类成员,而不考虑其与聚类中心的接近程度)。对于聚类 k,聚合表示计算如下: y_k = ( Σ_{i: α(i)=k} w_i x_i ) / ( Σ_{i: α(i)=k} w_i ), (9) 其中权重 w_i = exp( s(x_i, c_k) / τ ) 基于令牌 x_i 与中心 c_k 之间的相似度,并按温度 τ 缩放。该设计放大了高度相似令牌的贡献,同时降低了离群值的权重。温度 τ 控制加权的锐度:较小的值聚焦于接近的令牌,而较大的值则近似均匀平均。

然而,由于三角不等式,加权平均会系统地减小特征幅度: || Σ_i w_i x_i || ≤ Σ_i w_i ||x_i||, (10) 只有平行向量时等式才成立。这种范数缩减是有问题的,因为特征幅度编码了感知相关信息,并且层归一化期望一致的幅度分布。因此,我们提出特征范数恢复作为后处理步骤。给定原始令牌 {x_1, ..., x_N} 和加权平均值 {y_1, ..., y_K},我们按全局最大范数重新缩放它们如下: n_max = max_{i=1,...,N} ||x_i||, (11a) ŷ_k = ( y_k / ||y_k|| ) · n_max 如果 ||y_k|| > ε,否则 ŷ_k = y_k (11b) ε = 10^{-6} 以避免除以零。这种重新缩放保留了加权平均的方向信息,并将幅度设置为原始集合中观察到的最大值,确保了一致的特征统计。我们使用全局最大值而不是逐个聚类的最大值,以确保对所有 y_i 进行统一的幅度缩放,更好地保持整体分布。

3.如何加入YOLO26

3.1 新建ultralytics/nn/block/SAA.py

核心源码如下:

代码语言:javascript
复制
######################################## CVPR2026 SAA  by AI Little monster start ########################################

### http://cv2023.blog.csdn.net


def cluster_and_merge(x, cluster_num, subsample_factor=4):
    B, N, C = x.shape
    device = x.device
    K = cluster_num

    x_proj = x

    x_norm = F.normalize(x_proj, dim=-1)  # (B, N, D) where D = proj_dim or C

    S = min(N, max(2 * K, subsample_factor * K))  # Ensure S >= 2K, cap at N

    samples_per_region = S // K
    sub_idx = []
    for i in range(K):
        start_idx = i * (N // K)
        end_idx = (i + 1) * (N // K) if i < K - 1 else N
        region_size = end_idx - start_idx
        n_samples = min(samples_per_region, region_size)

        if region_size > 0:
            region_perm = torch.randperm(region_size, device=device)[:n_samples]
            sub_idx.append(start_idx + region_perm)

    # Add random samples to reach S if needed
    sub_idx = torch.cat(sub_idx)
    if len(sub_idx) < S:
        remaining = S - len(sub_idx)
        all_idx = torch.arange(N, device=device)
        mask = torch.ones(N, dtype=torch.bool, device=device)
        mask[sub_idx] = False
        additional = all_idx[mask][torch.randperm((~mask).sum(), device=device)[:remaining]]
        sub_idx = torch.cat([sub_idx, additional])

    x_norm_sub = x_norm[:, sub_idx]  # (B, S, D)

    # Cosine similarity (normalized dot product)
    sim_sub = x_norm_sub @ x_norm_sub.transpose(1, 2)  # (B, S, S)
    torch.diagonal(sim_sub, dim1=1, dim2=2).fill_(-1)

    # Mean of top-k similarities
    k = min(K, S - 1)
    sim_topk_sub, _ = torch.topk(sim_sub, k=k, dim=-1)  # (B, S, k)
    density_sub = sim_topk_sub.mean(dim=-1)  # (B, S)
    density_sub = density_sub + torch.rand_like(density_sub) * 1e-6

    # Mask for points with higher density
    mask_higher_density = (density_sub[:, None, :] > density_sub[:, :, None]).float()  # (B, S, S)

    # For points with higher density, keep similarity; otherwise set to very negative
    masked_sim_sub = sim_sub * mask_higher_density - 1e9 * (1.0 - mask_higher_density)

    # Maximum similarity to higher-density points
    max_sim_to_higher, _ = masked_sim_sub.max(dim=-1)  # (B, S)

    # Convert to distance: δ = 1 - similarity
    delta_sub = 1.0 - max_sim_to_higher  # (B, S)

    # Handle points with maximum density (no higher-density neighbors)
    max_density_mask_sub = (mask_higher_density.sum(dim=-1) == 0)  # (B, S)

    # For max density points, use maximum distance in subsample
    min_sim_global = sim_sub.min(dim=-1)[0]  # (B, S)
    max_dist_global = (1.0 - min_sim_global).to(delta_sub.dtype)
    delta_sub[max_density_mask_sub] = max_dist_global[max_density_mask_sub]

    # Ensure delta is non-negative
    delta_sub = torch.clamp(delta_sub, min=0.0)

    # Score: γ = ρ × δ
    score_sub = density_sub * delta_sub  # (B, S)

    # Select top-K scoring points as cluster centers
    _, center_idx_in_sub = torch.topk(score_sub, k=K, dim=-1)  # (B, K)

    # Map back to original indices
    center_idx = sub_idx[center_idx_in_sub]  # (B, K)

    # Get center representations (normalized)
    centers_norm = torch.gather(
        x_norm,
        1,
        center_idx[..., None].expand(B, K, x_norm.shape[-1])
    )  # (B, K, D)

    # Use cosine similarity (consistent with center selection)
    sim_token_center = x_norm @ centers_norm.transpose(1, 2)  # (B, N, K)

    # Assign to cluster with highest similarity
    assign_idx = sim_token_center.argmax(dim=-1)  # (B, N)

    # Weighted merging
    # Merge using original (unprojected) tokens for output quality
    out = x.new_zeros(B, K, C)

    # One-hot encoding of assignments
    one_hot = F.one_hot(assign_idx, num_classes=K).type_as(x)  # (B, N, K)

    # Count tokens per cluster
    cluster_counts = one_hot.sum(dim=1, keepdim=True).clamp(min=1e-6)  # (B, 1, K)

    # Weighted average: sum tokens per cluster, then normalize
    out = torch.einsum("bnc,bnk->bkc", x, one_hot) / cluster_counts.transpose(1, 2)

    return out


class SAA(nn.Module):
    def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0., c_ratio=0.5, M=0.03):
        super(SAA, self).__init__()
        assert dim % num_heads == 0, f"dim {dim} should be divided by num_heads {num_heads}."
        self.dim = dim
        self.num_heads = num_heads
        self.cr = int(dim * c_ratio)
        self.scale = qk_scale or (self.cr // num_heads) ** -0.5
        self.M = M  # Ratio for NF (foreground size)

        # QKV projections
        self.q = nn.Linear(dim, self.cr, bias=qkv_bias)
        self.k = nn.Linear(dim, self.cr, bias=qkv_bias)
        self.v = nn.Linear(dim, dim, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Conv2d(dim, dim, 1)
        self.proj_drop = nn.Dropout(proj_drop)

    def forward(self, x):
        B, C, H, W = x.shape
        N = H * W

        x = x.flatten(2).transpose(2, 1)

        T_unimp = x
        NF = int(self.M * N)

        # Average and cluster-merge background tokens
        T_avg = cluster_and_merge(T_unimp, NF)
        # Norm preservation
        norms = torch.norm(T_unimp, dim=-1)  # B x num_unimp
        max_norm = norms.max(dim=-1, keepdim=True)[0].unsqueeze(-1)  # B x 1 x 1
        avg_norm = torch.norm(T_avg, dim=-1, keepdim=True)  # B x 1 x 1
        epsilon = 1e-6
        mask = avg_norm > epsilon  # B x 1 x 1
        scaled = (T_avg / (avg_norm + epsilon)) * max_norm
        T_avg = torch.where(mask, scaled, T_avg)

        # Concat for KV_comp
        KV_comp = T_avg
        K_size = KV_comp.shape[1]

        # Cross-Attention
        q = self.q(x).reshape(B, N, self.num_heads, self.cr // self.num_heads).permute(0, 2, 1, 3)
        k = self.k(KV_comp).reshape(B, K_size, self.num_heads, self.cr // self.num_heads).permute(0, 2, 1, 3)
        v = self.v(KV_comp).reshape(B, K_size, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)
        out = (attn @ v).transpose(2, 3).reshape(B, C, H, W)
        out = self.proj(out)
        out = self.proj_drop(out)

        return out


######################################## CVPR2026 SAA  by AI Little monster end  ########################################

3.2 修改tasks.py

1)第一处修改 C2PSA_SAA进行注册

代码语言:javascript
复制
from ultralytics.nn.block.SAA import C2PSA_SAA

2)第二处修改

修改def parse_model(d, ch, verbose=True): # model_dict, input_channels(3)

只需要在你源码基础上加入C2PSA_SAA,其他模块为博主其他文章的优化点

代码语言:javascript
复制
    base_modules = frozenset(
        {
            Classify,
            Conv,
            ConvTranspose,
            GhostConv,
            Bottleneck,
            GhostBottleneck,
            SPP,
            SPPF,
            C2fPSA,
            C2PSA,
            DWConv,
            Focus,
            BottleneckCSP,
            C1,
            C2,
            C2f,
            C3k2,
            RepNCSPELAN4,
            ELAN1,
            ADown,
            AConv,
            SPPELAN,
            C2fAttn,
            C3,
            C3TR,
            C3Ghost,
            torch.nn.ConvTranspose2d,
            DWConvTranspose2d,
            C3x,
            RepC3,
            PSA,
            SCDown,
            C2fCIB,
            A2C2f,
            C2PSA_SAA,
        }
    )
    repeat_modules = frozenset(  # modules with 'repeat' arguments
        {
            BottleneckCSP,
            C1,
            C2,
            C2f,
            C3k2,
            C2fAttn,
            C3,
            C3TR,
            C3Ghost,
            C3x,
            RepC3,
            C2fPSA,
            C2fCIB,
            C2PSA,
            A2C2f,
            C2PSA_SAA
        }
    )

3.3 yolo26-C2PSA_SAA.yaml

代码语言:javascript
复制
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license

# Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs
# Model docs: https://docs.ultralytics.com/models/yolo26
# Task docs: https://docs.ultralytics.com/tasks/detect

# Parameters
nc: 80 # number of classes
end2end: True # whether to use end-to-end mode
reg_max: 1 # DFL bins
scales: # model compound scaling constants, i.e. 'model=yolo26n.yaml' will call yolo26.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs
  s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs
  m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs
  l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs
  x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs

# YOLO26n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]
  - [-1, 1, SPPF, [1024, 5, 3, True]] # 9
  - [-1, 2, C2PSA_SAA, [1024]] # 10

# YOLO26n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 2, C3k2, [512, True]] # 13

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 2, C3k2, [256, True]] # 16 (P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 13], 1, Concat, [1]] # cat head P4
  - [-1, 2, C3k2, [512, True]] # 19 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 10], 1, Concat, [1]] # cat head P5
  - [-1, 1, C3k2, [1024, True, 0.5, True]] # 22 (P5/32-large)

  - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 1.YOLO26原理介绍
    • 1.1 YOLO11 vs YOLO26结构差异性
      • 1.1.1 SPPF 核心差异对比
      • 1.1.2 C3k2 核心差异对比
    • 1.2 YOLO26核心创新点
      • 1.2.1 创新点1:移除分布焦点损失(DFL)
      • 1.2.2 创新点2:端到端无NMS推理
      • 1.2.3 创新点3:ProgLoss和STAL:增强训练稳定性和小目标检测
      • 1.2.4 创新点4:用于稳定收敛的MuSGD优化器
  • 2.原理介绍
  • 3.如何加入YOLO26
    • 3.1 新建ultralytics/nn/block/SAA.py
    • 3.2 修改tasks.py
    • 3.3 yolo26-C2PSA_SAA.yaml
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档