news 2026/10/15 1:10:59

YOLO26改进 - 特征融合 | GIRAFFEDET之GFPN :广义特征金字塔网络,高效地融合多尺度特征

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO26改进 - 特征融合 | GIRAFFEDET之GFPN :广义特征金字塔网络,高效地融合多尺度特征

前言

本文介绍了创新的对象检测框架GiraffeDet在YOLO26中的结合应用。GiraffeDet旨在通过轻量级骨干网络和深度、庞大的颈部模块实现高效的多尺度信息交换,其核心包括轻量级的空间到深度链(S2D-chain)和广义特征金字塔网络(GFPN)。其中,S2D-chain减少计算成本和域迁移问题,GFPN提供跨层次和跨尺度的特征融合。我们将GFPN集成进YOLO26,替代原有的特征融合模块,实现更高效的信息交换。本次接入保留 YOLO26 主干,在 Neck 中引入 GFPN 特征融合阶段;检测精度和速度的具体变化需要在目标数据集上进行完整训练及基线对比。

文章目录: YOLO26改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总
专栏链接: 最新YOLO26改进专栏

文章目录

  • 前言
  • 介绍
    • 摘要
  • 文章链接
  • 基本原理
    • GiraffeDet 基本原理和组件
        • 1. 基本原理
        • 2. 组件
        • 3. GiraffeDet 家族
    • GFPN (广义特征金字塔网络) 详解
        • GFPN 设计要点
        • GFPN 详细设计
        • 实验结果与性能
  • 核心代码
  • YOLO26引入代码
  • tasks注册
    • 步骤1:导包
    • 步骤2:注册基础模块和重复模块
  • 配置yolo26-GFPN.yaml
  • 实验
    • 脚本
    • 结果

介绍

摘要

在传统的目标检测框架中,通常采用从图像识别模型继承的主干网络来提取深层潜在特征,然后通过颈部模块融合这些潜在特征,以捕捉不同尺度的信息。由于目标检测中的分辨率远高于图像识别,主干网络的计算成本往往占据总推理成本的主要部分。这种重型主干设计范式主要是由于将图像识别模型转移到目标检测中时的历史遗留,而不是针对目标检测进行的端到端优化设计。在本研究中,我们表明这种范式确实导致了次优的目标检测模型。为此,我们提出了一种新颖的重型颈部范式,GiraffeDet,这是一种类长颈鹿的高效目标检测网络。GiraffeDet使用极其轻量级的主干网络和非常深且大的颈部模块,鼓励在不同空间尺度和不同潜在语义层次之间进行密集的信息交换。这种设计范式使检测器即使在网络的早期阶段也能以同等优先级处理高级语义信息和低级空间信息,从而在检测任务中更加有效。对多个流行的目标检测基准的数值评估表明,GiraffeDet在各种资源约束下始终优于先前的SOTA模型。源码可在 https://github.com/jyqi/GiraffeDet 获取。

文章链接

论文地址:论文地址

代码地址:代码地址

基本原理

GiraffeDet 基本原理和组件

GiraffeDet 是一个创新的对象检测框架,其设计宗旨是通过轻量级骨干网络和深度、庞大的颈部模块实现高效的多尺度信息交换,从而提高检测性能。其核心思想包括轻量级的空间到深度链(Space-to-Depth Chain, S2D-chain)和广义特征金字塔网络(Generalized Feature Pyramid Network, GFPN),共同组成了一个“长颈鹿”形网络。

1. 基本原理
  • 轻量级骨干(Lightweight Backbone):

    • GiraffeDet 使用轻量级的空间到深度链(S2D-chain)作为骨干网络,取代传统的CNN骨干,减少了计算成本和域迁移问题。
    • S2D-chain 包括两个 3x3 卷积层和多个 S2D 块,每个 S2D 块由一个 S2D 层和一个 1x1 卷积组成,通过将空间维度的信息转移到深度维度来实现特征的下采样。
  • 广义特征金字塔网络(Generalized FPN, GFPN):

    • GFPN 提供了跨层次和跨尺度的特征融合,通过“Queen-Fusion”实现像国际象棋中的王后路径般的高效信息交换。
    • GFPN 设计中包含跳跃层连接(log2n-link),能够有效传递早期节点到后期节点的信息,并减少冗余。
2. 组件
  • S2D链:

    • 包含初始下采样的 3x3 卷积和多个 S2D 块。S2D 块通过固定间隔的均匀采样和重组特征实现空间维度到深度维度的转换。
  • GFPN:

    • 由多个深度和宽度可调的层组成。每层包括多种尺度和层次的特征融合,使用跳跃层连接和跨尺度连接。
    • Queen-Fusion 融合了当前层和相邻层的特征,提供了高效的高低层信息交换。
  • 预测网络(Prediction Network):

    • 负责生成对象的边界框和分类标签。通过 GFPN 提供的丰富特征进行准确的对象检测。
3. GiraffeDet 家族
  • 多样化模型:
    • 根据 GFPN 的深度和宽度,GiraffeDet 开发了多个适应不同计算资源限制的模型,包括 Giraffe-D7、D11、D14、D16、D25 和 D29。
    • 实验结果表明,GiraffeDet 在各个 FLOPs 级别上都表现出了较高的准确性和效率。

GFPN (广义特征金字塔网络) 详解

GFPN 是 GiraffeDet 中的一个关键组件,其设计旨在高效地融合多尺度特征,以提升目标检测性能。GFPN 结合了跳跃层连接(skip-layer connections)和跨尺度连接(cross-scale connections)等创新技术,解决了传统特征金字塔网络(FPN)设计中的局限性,增强了不同特征层次之间的信息交换。

GFPN 设计要点
  1. 多尺度特征融合:

    • GFPN 的目的是汇聚从骨干网络提取的不同分辨率的特征。
    • 它基于传统 FPN 的概念,但引入了更复杂的连接,以增强信息流动。
  2. 特征金字塔网络的演进:

    • 传统 FPN:引入自上而下的路径来融合多尺度特征。
    • PANet:在 FPN 结构的基础上增加了自下而上的路径,增强了双向信息流。
    • BiFPN:移除只有一个输入边的节点,并增加同一级别原始输入的额外边,进一步提高了连接性。
    • GFPN:结合跳跃层连接和跨尺度连接,优化了水平和垂直方向上的信息流。
  3. 跳跃层连接(Skip-layer Connection):

    • 设计用于减少深层网络中的梯度消失问题。
    • 具体实现方式有两种:密集连接(dense-link)和 log2n 连接(log2n-link)。
  4. 跨尺度连接(Cross-scale Connection):

    • 设计用于克服大尺度变异问题。
    • 以前的工作仅考虑相邻层之间的特征连接,而 GFPN 提出了新的融合方法——皇后融合(Queen-fusion),考虑了相同层次和邻近层次的特征。
GFPN 详细设计
  1. 跳跃层连接:

    • 密集连接(dense-link):每一层都接收所有前一层的特征图,并进行卷积操作。
    • log2n 连接(log2n-link):每一层接收至多 log2(l) + 1 层的前一层特征图,减少了计算复杂度,并有效传递信息。
  2. 皇后融合(Queen-fusion):

    • 类似于国际象棋中的皇后路径,融合了当前层和相邻层的特征。
    • 例如,在 P5 层,融合了上一层 P4 的下采样、上一层 P6 的上采样、上一层 P5 和当前层 P4 的特征。
实验结果与性能
  • 实验结果表明,GFPN 在处理大尺度变异方面表现优异,在各个 FLOPs 级别上均实现了更高的准确性和效率。
  • 连接分析显示,log2n 连接在信息传输方面比密集连接更有效,而皇后融合则能实现充分的高低层信息交换。

GFPN 的创新设计使得 GiraffeDet 能够在目标检测任务中提供卓越的性能,并在处理不同尺度的对象时表现出色。通过跳跃层连接和跨尺度连接,GFPN 实现了高效的信息融合,提高了检测的准确性和效率。

核心代码

import torch import torch.nn as nn from ultralytics.nn.modules.conv import Conv, RepConv def get_activation(name="silu", inplace=True): """Resolve the activation names used by the article.""" if name is None: return nn.Identity() if isinstance(name, nn.Module): return name activations = { "silu": lambda: nn.SiLU(inplace=inplace), "swish": lambda: nn.SiLU(inplace=inplace), "relu": lambda: nn.ReLU(inplace=inplace), "lrelu": lambda: nn.LeakyReLU(0.1, inplace=inplace), "hardsigmoid": lambda: nn.Hardsigmoid(inplace=inplace), "identity": nn.Identity, } if name not in activations: raise ValueError(f"Unsupported GFPN activation: {name}") return activations[name]() class ConvBNAct(Conv): """Reuse the YOLO26 convolution while preserving the article's activation options.""" def __init__(self, in_channels, out_channels, ksize, stride=1, groups=1, act="silu"): super().__init__(in_channels, out_channels, k=ksize, s=stride, g=groups, act=get_activation(act)) class BasicBlock_3x3_Reverse(nn.Module): """RepConv channel transform followed by a spatial convolution and optional residual.""" def __init__(self, ch_in, ch_hidden_ratio, ch_out, act="relu", shortcut=True): super().__init__() if ch_in != ch_out: raise ValueError("GFPN reverse block requires equal input and output channels") ch_hidden = max(1, int(ch_in * ch_hidden_ratio)) self.conv1 = ConvBNAct(ch_hidden, ch_out, 3, act=act) # The article's RepConv has no BN identity branch; bn=False preserves this structure. self.conv2 = RepConv(ch_in, ch_hidden, k=3, s=1, p=1, act=get_activation(act), bn=False) self.shortcut = shortcut def forward(self, x): y = self.conv1(self.conv2(x)) return x + y if self.shortcut else y class SPP(nn.Module): """Parallel spatial pooling used by the optional GFPN stage branch.""" def __init__(self, ch_in, ch_out, k=1, pool_size=(5, 9, 13), act="swish"): super().__init__() self.pool = nn.ModuleList(nn.MaxPool2d(size, 1, size // 2) for size in pool_size) self.conv = ConvBNAct(ch_in, ch_out, k, act=act) def forward(self, x): return self.conv(torch.cat([x, *(pool(x) for pool in self.pool)], dim=1)) class CSPStage(nn.Module): """Concatenate the bypass and every reverse block output before the final projection.""" def __init__( self, ch_in, ch_out, n=1, block_fn="BasicBlock_3x3_Reverse", ch_hidden_ratio=1.0, act="silu", spp=False, ): super().__init__() if ch_out < 2 or n < 1: raise ValueError("GFPN CSPStage requires ch_out >= 2 and n >= 1") if block_fn != "BasicBlock_3x3_Reverse": raise ValueError(f"Unsupported GFPN block: {block_fn}") ch_first = ch_out // 2 ch_mid = ch_out - ch_first self.conv1 = ConvBNAct(ch_in, ch_first, 1, act=act) self.conv2 = ConvBNAct(ch_in, ch_mid, 1, act=act) self.convs = nn.ModuleList( BasicBlock_3x3_Reverse(ch_mid, ch_hidden_ratio, ch_mid, act=act, shortcut=True) for _ in range(n) ) self.spp = SPP(ch_mid * 4, ch_mid, act=act) if spp else nn.Identity() self.spp_index = (n - 1) // 2 self.conv3 = ConvBNAct(ch_first + ch_mid * n, ch_out, 1, act=act) def forward(self, x): y1 = self.conv1(x) y2 = self.conv2(x) mid_out = [y1] for i, conv in enumerate(self.convs): y2 = conv(y2) if i == self.spp_index: y2 = self.spp(y2) # SPP transforms the block output rather than adding another concatenation branch. mid_out.append(y2) return self.conv3(torch.cat(mid_out, dim=1))

YOLO26引入代码

在根目录下的ultralytics/nn/目录,新建一个featureFusion目录,然后新建一个以GFPN为文件名的py文件, 把代码拷贝进去。

importtorchimporttorch.nnasnnfromultralytics.nn.modules.convimportConv,RepConvdefget_activation(name="silu",inplace=True):"""Resolve the activation names used by the article."""ifnameisNone:returnnn.Identity()ifisinstance(name,nn.Module):returnname activations={"silu":lambda:nn.SiLU(inplace=inplace),"swish":lambda:nn.SiLU(inplace=inplace),"relu":lambda:nn.ReLU(inplace=inplace),"lrelu":lambda:nn.LeakyReLU(0.1,inplace=inplace),"hardsigmoid":lambda:nn.Hardsigmoid(inplace=inplace),"identity":nn.Identity,}ifnamenotinactivations:raiseValueError(f"Unsupported GFPN activation:{name}")returnactivations[name]()classConvBNAct(Conv):"""Reuse the YOLO26 convolution while preserving the article's activation options."""def__init__(self,in_channels,out_channels,ksize,stride=1,groups=1,act="silu"):super().__init__(in_channels,out_channels,k=ksize,s=stride,g=groups,act=get_activation(act))classBasicBlock_3x3_Reverse(nn.Module):"""RepConv channel transform followed by a spatial convolution and optional residual."""def__init__(self,ch_in,ch_hidden_ratio,ch_out,act="relu",shortcut=True):super().__init__()ifch_in!=ch_out:raiseValueError("GFPN reverse block requires equal input and output channels")ch_hidden=max(1,int(ch_in*ch_hidden_ratio))self.conv1=ConvBNAct(ch_hidden,ch_out,3,act=act)# The article's RepConv has no BN identity branch; bn=False preserves this structure.self.conv2=RepConv(ch_in,ch_hidden,k=3,s=1,p=1,act=get_activation(act),bn=False)self.shortcut=shortcutdefforward(self,x):y=self.conv1(self.conv2(x))returnx+yifself.shortcutelseyclassSPP(nn.Module):"""Parallel spatial pooling used by the optional GFPN stage branch."""def__init__(self,ch_in,ch_out,k=1,pool_size=(5,9,13),act="swish"):super().__init__()self.pool=nn.ModuleList(nn.MaxPool2d(size,1,size//2)forsizeinpool_size)self.conv=ConvBNAct(ch_in,ch_out,k,act=act)defforward(self,x):returnself.conv(torch.cat([x,*(pool(x)forpoolinself.pool)],dim=1))classCSPStage(nn.Module):"""Concatenate the bypass and every reverse block output before the final projection."""def__init__(self,ch_in,ch_out,n=1,block_fn="BasicBlock_3x3_Reverse",ch_hidden_ratio=1.0,act="silu",spp=False,):super().__init__()ifch_out<2orn<1:raiseValueError("GFPN CSPStage requires ch_out >= 2 and n >= 1")ifblock_fn!="BasicBlock_3x3_Reverse":raiseValueError(f"Unsupported GFPN block:{block_fn}")ch_first=ch_out//2ch_mid=ch_out-ch_first self.conv1=ConvBNAct(ch_in,ch_first,1,act=act)self.conv2=ConvBNAct(ch_in,ch_mid,1,act=act)self.convs=nn.ModuleList(BasicBlock_3x3_Reverse(ch_mid,ch_hidden_ratio,ch_mid,act=act,shortcut=True)for_inrange(n))self.spp=SPP(ch_mid*4,ch_mid,act=act)ifsppelsenn.Identity()self.spp_index=(n-1)//2self.conv3=ConvBNAct(ch_first+ch_mid*n,ch_out,1,act=act)defforward(self,x):y1=self.conv1(x)y2=self.conv2(x)mid_out=[y1]fori,convinenumerate(self.convs):y2=conv(y2)ifi==self.spp_index:y2=self.spp(y2)# SPP transforms the block output rather than adding another concatenation branch.mid_out.append(y2)returnself.conv3(torch.cat(mid_out,dim=1))

tasks注册

在ultralytics/nn/tasks.py中进行如下操作:

步骤1:导包

fromultralytics.nn.featureFusion.GFPNimportCSPStageasGFPN_CSPStage

步骤2:注册基础模块和重复模块

在parse_model(d, ch, verbose=True)中的两个现有集合中各加入 GFPN_CSPStage。下方只展示新增位置,保留原集合中的其他模块:

base_modules=frozenset({GFPN_CSPStage,})repeat_modules=frozenset(# modules with 'repeat' arguments{GFPN_CSPStage,})

配置yolo26-GFPN.yaml

# 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# Parametersnc:80# number of classesend2end:True# whether to use end-to-end modereg_max:1# DFL binsscales:# 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]s:[0.50,0.50,1024]m:[0.50,1.00,512]l:[1.00,1.00,512]x:[1.00,1.50,512]# YOLO26n backbonebackbone:# [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,[1024]]# 10# YOLO26n head: GFPN CSP stages in a PAN comparison configurationhead:-[10,1,nn.Upsample,[None,2,"nearest"]]# 11-[[-1,6],1,Concat,[1]]# 12 backbone P4 fusion-[-1,3,GFPN_CSPStage,[512]]# 13-[-1,1,nn.Upsample,[None,2,"nearest"]]# 14-[[-1,4],1,Concat,[1]]# 15 backbone P3 fusion-[-1,3,GFPN_CSPStage,[256]]# 16 P3/8-small-[-1,1,Conv,[256,3,2]]# 17-[[-1,13],1,Concat,[1]]# 18 head P4 fusion-[-1,3,GFPN_CSPStage,[512]]# 19 P4/16-medium-[-1,1,Conv,[512,3,2]]# 20-[[-1,10],1,Concat,[1]]# 21 backbone P5 fusion-[-1,3,GFPN_CSPStage,[1024]]# 22 P5/32-large-[[16,19,22],1,Detect,[nc]]# Detect(P3, P4, P5)

实验

脚本

importwarnings warnings.filterwarnings('ignore')fromultralyticsimportYOLOif__name__=='__main__':# 修改为自己的配置文件地址model=YOLO('./ultralytics/cfg/models/26/yolo26-GFPN.yaml')# 修改为自己的数据集地址model.train(data='./ultralytics/cfg/datasets/coco8.yaml',cache=False,imgsz=640,epochs=10,single_cls=False,# 是否是单类别检测batch=8,close_mosaic=10,workers=0,optimizer='MuSGD',# optimizer='SGD',amp=False,project='runs/train',name='GFPN',)

结果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:10:05

雅虎35条军规:前端性能优化的清单式工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:09:19

20200726调度实验:用真实数据验证四大进程调度算法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:09:19

GoogLeNet论文精读:Inception模块与多尺度特征提取实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:08:59

Go语言基础数据类型详解与最佳实践

1. Go语言基础数据类型概述作为一门现代化的系统编程语言&#xff0c;Go在数据类型设计上体现了简洁高效的理念。我在实际项目开发中发现&#xff0c;深入理解这些基础类型对编写健壮代码至关重要。Go的基础数据类型主要分为布尔型、数值型和字符串型三大类&#xff0c;每种类型…

作者头像 李华
网站建设 2026/10/15 1:08:04

LCS转LIS优化:最长公共子序列到最长递增子序列的转化与Swap变体

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华