YOLOv13改进策略【Neck篇】| AFPN 渐进式特征金字塔,整体替换 Head 参数反降 82 万

本文基于 YOLOv13 官方仓库(iMoonLab/yolov13,ultralytics 8.3.63 fork) 实测整理,Windows/CPU 全程可跑。Neck 篇收官之作是最大刀阔斧的一篇:AFPN(Asymptotic Feature Pyramid Network,Yang et al., 2023,官方源码 github.com/gyyang23/AFPN)主张放弃"自顶向下+自底向上"的固定路径,让相邻尺度先融合、逐级渐进 。本文用官方 AFPN 整体替换 v13 的 HyperACE/FullPAD Head------实测参数减少 825,746、GFLOPs 7.1。

前言

Neck 篇收官。前四篇都是"节点级"改动(换一个汇合点、一处上采样、一处卷积);这篇是结构级 改动:把 v13 的整个 head(HyperACE 超图 + 7 个 FullPAD 门控 + PANet 路径,共 24 层)替换为一个 AFPN 渐进式融合模块。AFPN 出自论文《AFPN: Asymptotic Feature Pyramid Network for Object Detection》(2023),核心思想是渐近(asymptotic) :直接融合语义差距大的相邻尺度会有信息冲突,先把最相邻的层级融合出"中间结果",再逐级向更远尺度扩散。替换后整网只有 14 层。

专栏目录 :YOLOv13改进目录一览

专栏地址 :YOLOv13改进专栏------持续更新各方向即插即用改进

一、替换点分析:整个 Head

yaml 复制代码
backbone:
  - [-1, 1, Conv,  [64, 3, 2]]          # 0-P1/2
  - [-1, 1, Conv,  [128, 3, 2, 1, 2]]   # 1-P2/4
  - [-1, 2, DSC3k2,  [256, False, 0.25]]# 2 ← AFPN 的第 4 路输入(P2 尺度,64 通道)
  - [-1, 1, Conv,  [256, 3, 2, 1, 4]]   # 3-P3/8
  - [-1, 2, DSC3k2,  [512, False, 0.25]]# 4 ← 第 1 路(P3,128 通道)
  - [-1, 1, DSConv,  [512, 3, 2]]       # 5-P4/16
  - [-1, 4, A2C2f, [512, True, 4]]      # 6 ← 第 2 路(P4,128 通道)
  - [-1, 1, DSConv,  [1024, 3, 2]]      # 7-P5/32
  - [-1, 4, A2C2f, [1024, True, 1]]     # 8 ← 第 3 路(P5,256 通道)

head:
  - [[2, 4, 6, 8], 1, AFPN, [128]]      # 9 ★AFPN(整体替换,4 尺度渐进融合)
  - [9, 1, GoI, [1]]                    # 10 取 P3
  - [9, 1, GoI, [2]]                    # 11 取 P4
  - [9, 1, GoI, [3]]                    # 12 取 P5
  - [[10, 11, 12], 1, Detect, [nc]]     # 13 Detect(P3, P4, P5)

两个关键点:

  1. AFPN 官方是 4 输入结构,而 v13 backbone 恰好有 4 个天然尺度------层 2(P2/4,64 通道)、层 4(P3/8,128 通道)、层 6(P4/16,128 通道)、层 8(P5/32,256 通道),与官方 C2, C3, C4, C5 的尺度约定完全对应;
  2. 多输出的桥梁 :AFPN 输出 4 路特征,而 Detect 需要按层取用------本文引入一个 7 参数的辅助模块 GoI(Go-Index,取第 idx 路)搭桥。yaml 从 33 层瘦身到 14 层。

二、AFPN 原理

  • 传统 FPN/PAN 的路径是写死的:先自顶向下、再自底向上,所有相邻尺度无差别融合。AFPN 问:语义差距悬殊的尺度(P5 与 P3)直接融合,冲突谁来解决?
  • 渐近融合 :只让相邻 尺度先融合(P2+P3 → P3';P3'+P4 → P4'......),语义差距逐级消化。每一级融合用 ASFF 节点(自适应空间融合,逐像素 softmax 权重)完成;
  • 渐进的两轮:第一轮完成基础融合,第二轮在融合结果上再做一次带残差的渐进融合,最后各尺度过 BasicBlock 精炼输出;
  • 尺度对齐:跨尺度融合前用 1×1 卷积(Downsample_x2/x4/x8 与 Upsample)对齐空间尺寸与通道。

一句话:把"金字塔的形状"也交给网络按内容组织,而不是设计者画死。

⚠️ 实现说明 :官方 AFPN 为 mmdetection 的 4 级实现,本文逐类内嵌(BasicConv/BasicBlock/Upsample/Downsample/ASFF_2I/ASFF_3/ASFF_4/BlockBody),仅将官方类名 ASFF_2 改为 ASFF_2I(避免与 ultralytics parse_model 的属性名冲突,见下方新坑)。内部节点与官方逐位一致。

三、实现代码

模块较长(BasicConv / BasicBlock / Upsample / Downsample_x2/x4/x8 / ASFF_2I / ASFF_3 / ASFF_4 / BlockBody / AFPN / GoI 共约 230 行),完整代码可在专栏配套代码包中获取,核心骨架如下:

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F


class BasicConv(nn.Module):
    """官方 AFPN 的 BasicConv:Conv + BN + SiLU(k>1 时自动补 padding)。"""

    def __init__(self, in_planes, out_planes, kernel_size, stride=1, pad=None,
                 relu=True, bn=True, bias=False):
        super().__init__()
        if not pad:
            pad = (kernel_size - 1) // 2 if kernel_size else 0
        self.conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride=stride,
                              padding=pad, groups=groups if False else 1, bias=bias)
        self.bn = nn.BatchNorm2d(out_planes, eps=1e-5, momentum=0.01, affine=True) if bn else None
        self.relu = nn.ReLU() if relu else None

    def forward(self, x):
        x = self.conv(x)
        if self.bn is not None:
            x = self.bn(x)
        if self.relu is not None:
            x = self.relu(x)
        return x


class ASFF_2(nn.Module):
    """两输入 ASFF 融合节点:softmax 逐像素加权。"""

    def __init__(self, inter_dim):
        super().__init__()
        compress_c = 8
        self.weight_level_1 = BasicConv(inter_dim, compress_c, 1, 1)
        self.weight_level_2 = BasicConv(inter_dim, compress_c, 1, 1)
        self.weight_levels = nn.Conv2d(compress_c * 2, 2, 1, 1, 0)
        self.conv = BasicConv(inter_dim, inter_dim, 3, 1)

    def forward(self, input1, input2):
        w1 = self.weight_level_1(input1)
        w2 = self.weight_level_2(input2)
        wei = self.weight_levels(torch.cat((w1, w2), 1))
        wei = F.softmax(wei, dim=1)
        return input1 * wei[:, 0:1] + input2 * wei[:, 1:2]


# Downsample_x2/x4/x8、Upsample、ASFF_3(三输入)、ASFF_4(四输入)、
# BlockBody(两轮渐进融合)与官方实现一致,篇幅所限从略;
# 完整代码与专栏配套包一致。


class AFPN(nn.Module):
    """AFPN 渐进式特征金字塔:输入 4 个尺度,输出 4 路融合特征。"""

    def __init__(self, in_channels, out_channels=128):
        super().__init__()
        self.conv0 = BasicConv(in_channels[0], in_channels[0] // 8, 1)
        self.conv1 = BasicConv(in_channels[1], in_channels[1] // 8, 1)
        self.conv2 = BasicConv(in_channels[2], in_channels[2] // 8, 1)
        self.conv3 = BasicConv(in_channels[3], in_channels[3] // 8, 1)
        self.body = BlockBody([in_channels[0] // 8, in_channels[1] // 8,
                               in_channels[2] // 8, in_channels[3] // 8])
        self.conv00 = BasicConv(in_channels[0] // 8, out_channels, 1)
        self.conv11 = BasicConv(in_channels[1] // 8, out_channels, 1)
        self.conv22 = BasicConv(in_channels[2] // 8, out_channels, 1)
        self.conv33 = BasicConv(in_channels[3] // 8, out_channels, 1)

    def forward(self, x):
        x0, x1, x2, x3 = x
        x0 = self.conv0(x0)
        x1 = self.conv1(x1)
        x2 = self.conv2(x2)
        x3 = self.conv3(x3)
        out0, out1, out2, out3 = self.body([x0, x1, x2, x3])
        return self.conv00(out0), self.conv11(out1), self.conv22(out2), self.conv33(out3)


class GoI(nn.Module):
    """GoIndex:从多输出模块取第 idx 路特征。"""

    def __init__(self, c1, idx=0):
        super().__init__()
        self.idx = idx

    def forward(self, x):
        t = x[0] if isinstance(x, (list, tuple)) and len(x) == 1 else x
        return t[self.idx]

⚠️ v13 新坑(属性名冲突) :ultralytics 的 parse_model 会给每个层实例写入 m_.f(from 索引)等属性,官方类里若有同名子模块(如 self.f)会直接 TypeError。AFPN 的融合节点 ASFF_2/ASFF_4 无此问题,但为区分"对外注册的列表输入版"与"BlockBody 内部的双参数版",本文将内部节点命名为 ASFF_2I。注册到 ultralytics 的官方模块,成员命名务必避开 f/i/type/np/params。

四、添加步骤(v13 版)

1. 修改 ultralytics/nn/modules/conv.py

BasicConv(若已有则跳过)、BasicBlock、Upsample、Downsample_x2/x4/x8、ASFF_2I、ASFF_3、ASFF_4、BlockBody、AFPN、GoI 依次粘贴到 conv.py 末尾。

2. 修改 ultralytics/nn/modules/__init__.py

from .conv import (...) 里追加 AFPN, 与 GoI,;__all__ 里给最后一项 "DSConv" 补逗号后追加 "AFPN"、"GoI"。

3. 修改 ultralytics/nn/tasks.py

顶部导入块加 AFPN、GoI;parse_model() 里新增两个分支(AFPN 多输入取通道列表;GoI 取路):

python 复制代码
        elif m is AFPN:  # AFPN:多输入整体 Neck,输出通道取声明的统一值
            args.insert(0, [ch[x] for x in f])
            c2 = args[-1]
        elif m is GoI:  # GoI:从多输出模块取第 idx 路
            c2 = ch[f]
            args = [c2, *args]

插入位置:elif m is FullPAD_Tunnel: 之后、else: 之前。

五、yaml 模型文件

整体替换 Head,全网仅 14 层:

yaml 复制代码
# Ultralytics YOLOv13n + AFPN 渐进式特征金字塔(整体替换 Head)🚀
nc: 80
scales:
  n: [0.50, 0.25, 1024]
  s: [0.50, 0.50, 1024]
  l: [1.00, 1.00, 512]
  x: [1.00, 1.50, 512]

backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv,  [64, 3, 2]]          # 0-P1/2
  - [-1, 1, Conv,  [128, 3, 2, 1, 2]]   # 1-P2/4
  - [-1, 2, DSC3k2,  [256, False, 0.25]]# 2(P2 尺度,AFPN 第 4 路输入)
  - [-1, 1, Conv,  [256, 3, 2, 1, 4]]   # 3-P3/8
  - [-1, 2, DSC3k2,  [512, False, 0.25]]# 4(P3,AFPN 第 1 路输入)
  - [-1, 1, DSConv,  [512, 3, 2]]       # 5-P4/16
  - [-1, 4, A2C2f, [512, True, 4]]      # 6(P4,第 2 路输入)
  - [-1, 1, DSConv,  [1024, 3, 2]]      # 7-P5/32
  - [-1, 4, A2C2f, [1024, True, 1]]     # 8(P5,第 3 路输入)

head:
  - [[2, 4, 6, 8], 1, AFPN, [128]]      # 9 ★AFPN(整体替换 Head)
  - [9, 1, GoI, [1]]                    # 10 取 P3
  - [9, 1, GoI, [2]]                    # 11 取 P4
  - [9, 1, GoI, [3]]                    # 12 取 P5
  - [[10, 11, 12], 1, Detect, [nc]]     # 13 Detect(P3, P4, P5)

六、成功运行结果

bash 复制代码
yolo detect train model=yolov13n-AFPN.yaml data=mydata.yaml epochs=100 imgsz=640 batch=16

Python 方式:

python 复制代码
from ultralytics import YOLO

model = YOLO("yolov13n-AFPN.yaml").load("yolov13n.pt")
model.train(data="mydata.yaml", epochs=100, imgsz=640, batch=16)

本文实测(YOLOv13 官方仓库,Windows CPU,v13n 结构,整体替换 Head):

复制代码
基线 yolov13n:  2,494,151 parameters, 6.5 GFLOPs
+AFPN:          1,668,405 parameters, 7.1 GFLOPs   Head→AFPN(全网14层)
3 epochs completed.  Results saved to runs\detect\v13_afpn_smoke

✅ 断言全部通过:① 内嵌的融合节点与官方 AFPN 实现同权重输出逐位一致(ASFF_2 max diff = 0);② 第 9 层为 AFPN,总参数量比基线减少 825,746(HyperACE/FullPAD 整套结构退场);③ 14 层 yaml 的 4 路输入与 GoI 取路全部正确,整网 640 前向正常,3 轮冒烟训练正常收敛。

模型 Params GFLOPs 说明
YOLOv13n 基线 2,494,151 6.5 HyperACE + 7×FullPAD + PANet
+AFPN 1,668,405 7.1 渐进式融合,全网 14 层

提示:AFPN 是结构级替换,与节点级改进不是同一量级的实验------它改变的是"融合的路径组织"本身。GFLOPs 略升(P2 高分辨率路参与融合)与参数大降并存,属于"用算力换结构简化"的典型。若你的任务对平移稳定性和多尺度一致性敏感,这套渐进式结构值得完整消融。

七、总结

AFPN 的四点记忆锚:渐近融合 (只融合相邻尺度,语义冲突逐级消化)、ASFF 节点是融合的原子 (逐像素 softmax 权重)、4 尺度输入恰好对上 v13 的层 2/4/6/8 (P2 高分辨率路变废为宝)、GoI 桥梁(多输出模块与 ultralytics 单张量书写的通用解法)。它也是 Neck 篇的方法论收官:节点级(BiFPN/GSConv/CARAFE/ASFF_2)解决"某个点怎么做",结构级(AFPN)解决"整条路怎么组织"------两层思路配合使用,才能把 Neck 改透。

Neck 篇五篇到此完整。下一篇进入【损失函数篇】------WIoU v3 动态非单调聚焦损失,专治低质量标注。

觉得有帮助的话,点赞收藏关注三连支持一下,评论区欢迎交流你的实验结果~

专栏目录:YOLOv13改进目录一览 专栏地址:YOLOv13改进专栏------持续更新各方向即插即用改进

相关推荐
YOLO_DATA1 天前
YOLO27防震锤缺陷检测数据集 防震锤数据集 1000张 防震锤 带标注 voc yolo 2 类 目标检测
人工智能·深度学习·yolo·目标检测·计算机视觉·数据集·无人机
YOLO数据集集合1 天前
红外多类型无人机检测数据集 | 红外检测 无人机识别 多类型分类 低空安防 反无人机 9139期
yolo·目标检测·分类·数据挖掘·无人机·无人机检测·红外无人机
YOLO数据集集合2 天前
电力设备目标检测数据集 | 电力设备 变电站巡检 部件识别 目标检测 YOLO格式 9127期
人工智能·yolo·目标检测·计算机视觉·目标跟踪·电力设备
FPGA小徐2 天前
FPGA 部署 YOLO 完整指南:从模型量化到 Zynq PS+PL 硬件加速
yolo·fpga开发
FPGA小徐2 天前
Ubuntu 22.04 安装 Miniconda、PyTorch 与 YOLOv8 并完成 CPU 推理
pytorch·yolo·ubuntu
YOLO数据集集合2 天前
桥梁病害检测数据集 | 桥梁病害 结构健康监测 裂缝检测 钢筋外露9134期
yolo·目标检测·外墙裂缝·建筑病害·桥梁病害·桥梁缺陷
zy_destiny3 天前
深度学习实战-基于YOLOv8的玉米雄穗目标检测:从数据集下载到训练部署全流程实录
yolo·目标检测·机器学习
YOLO数据集集合3 天前
外墙裂缝目标检测YOLO数据集:6,296张图像助力建筑病害智能识别| 外墙裂缝检测 YOLO数据集 建筑健康监测 无人机巡检 目标检测8030期
yolo·目标检测·无人机·建筑·外墙裂缝·建筑病害
richard_yuu3 天前
Hopfield 网络:联想记忆的「鼻祖」,为什么它能「回忆」?
深度学习·神经网络·yolo·机器学习