ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

https://blog.csdn.net/yolochangeworld/article/details/155139636

https://blog.csdn.net/yolochangeworld/article/details/155139636

前言

本文介绍了渐进特征金字塔网络(AFPN)及其在YOLO26中的结合应用。AFPN用于目标检测任务中的多尺度特征提取,解决了传统特征金字塔网络信息丢失和融合效果不佳的问题。其创新点在于跨非相邻层的直接特征融合、自适应空间融合操作,具备高效计算性能与广泛适用性。AFPN通过渐进式融合策略和自适应操作,避免语义差距和信息冲突。我们将AFPN集成进YOLO26,替换部分模块。本文完成 AFPN 在 YOLO26 中的工程接入,具体检测精度与速度需通过目标数据集上的完整训练和消融实验评估。

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

文章目录

  • 前言
  • 介绍
    • 摘要
    • 创新点
  • 文章链接
  • 基本原理
  • 核心代码
  • YOLO26引入代码
  • tasks注册
    • 步骤1:导包
    • 步骤2:解析多路输入与通道
  • 配置yolo26-AFPN.yaml
  • 实验
    • 脚本
    • 结果

介绍

摘要

在目标检测任务当中,多尺度特征对于对具有尺度变化的对象进行编码起着至关重要的作用。采用经典的自顶向下与自底向上特征金字塔网络是提取多尺度特征的常用策略。然而,这些方法存在特征信息丢失或降级的问题,这对非相邻层次之间的融合效果产生了不利影响。本文提出了一种渐进特征金字塔网络(AFPN),旨在支持非相邻层次之间的直接交互。AFPN从融合两个相邻的低级特征开始启动,并逐步地将更高级别的特征纳入到融合过程之中。通过这种方式,能够避免非相邻层次之间存在的较大语义差距。考虑到在每个空间位置的特征融合过程中,存在多对象信息冲突的可能性,进一步运用自适应空间融合操作来缓解这些不一致情况。我们将所提出的AFPN融入两阶段和单阶段目标检测框架里,并利用MS - COCO 2017验证和测试数据集进行评估。实验评估结果显示,相较于其他最先进的特征金字塔网络,我们的方法取得了更具竞争力的成果。

创新点

AFPN(渐进特征金字塔网络)的创新点主要体现在以下几个方面:

  1. 跨非相邻层的直接特征融合:AFPN通过渐进式的特征融合策略,允许非相邻层次之间的直接交互,这有助于防止在特征传递和交互过程中的信息丢失或退化。这种方法与传统的特征金字塔网络(FPN)不同,后者通常仅在相邻层次之间进行特征融合。

  2. 自适应空间融合操作:为了处理不同层次特征之间可能出现的信息矛盾,AFPN引入了自适应空间融合操作。这种方法能够根据不同空间位置的特征动态调整融合权重,从而有效地解决了多目标信息冲突问题。

  3. 高效的计算性能与竞争力的检测结果:在MS COCO 2017验证和测试数据集上的大量实验表明,AFPN不仅在计算效率上优于其他先进的特征金字塔网络,而且还获得了更具竞争力的检测结果。这表明AFPN能够在保持高效计算的同时,提高目标检测任务的准确性。

  4. 广泛的应用适用性:AFPN被成功集成到两阶段和单阶段目标检测框架中,并在不同的检测模型(如Faster R-CNN和YOLOv5)上验证了其有效性和提升性能,证明了其广泛的适用性和灵活性。

  5. 启发自HRNet的设计:AFPN的设计受到了高分辨率网络(HRNet)的启发,它在整个特征提取过程中保持了低级别特征的丰富性,并通过重复融合低级别和高级别特征来生成更丰富的特征表示。这种设计思想在人体姿态估计领域已显示出其优势,AFPN将其成功应用于目标检测任务。

通过这些创新点,AFPN解决了传统特征金字塔网络在处理尺度变化大的对象时的限制,提高了目标检测的性能和效率。

文章链接

论文地址:论文地址

代码地址:代码地址

核心代码:代码地址

基本原理

AFPN(渐进特征金字塔网络)的核心设计理念是在Backbone网络的自下而上特征提取过程中,通过渐进的方式集成不同层级的特征。这个过程从低级别特征的融合开始,逐步融合更高级别的特征,最终融合顶级(最高级别)的特征。这种方法旨在缓解直接融合非相邻层次特征时由于语义差距较大而导致的融合效果不佳的问题。通过逐渐融合特征,AFPN能够使不同层级的特征在语义上更加接近,有效减少了不同层级特征间的语义差距,特别是在最低级别和最高级别的特征之间。这种渐进的融合策略不仅优化了特征的融合效率,而且提高了特征表示的丰富性和有效性。在AFPN的设计中,自适应空间融合操作进一步增强了不同层次特征融合的灵活性和效果,通过调整融合策略来应对特征间的潜在矛盾,从而为目标检测任务提供了更加精确和鲁棒的特征表示。

核心代码

class ASFF2(nn.Module): """ASFF2 module for YOLO AFPN head https://arxiv.org/abs/2306.15988""" def __init__(self, c1, c2, level=0): super().__init__() if len(c1) != 2 or level not in (0, 1): raise ValueError('ASFF2 requires two input channel counts and level 0 or 1') c1_l, c1_h = c1 self.level = level self.dim = c1_l, c1_h self.inter_dim = self.dim[self.level] compress_c = 8 if level == 0: self.stride_level_1 = Upsample(c1_h, self.inter_dim) if level == 1: self.stride_level_0 = Conv(c1_l, self.inter_dim, 2, 2, 0) # downsample 2x self.weight_level_0 = Conv(self.inter_dim, compress_c, 1, 1) self.weight_level_1 = Conv(self.inter_dim, compress_c, 1, 1) self.weights_levels = nn.Conv2d(compress_c * 2, 2, kernel_size=1, stride=1, padding=0) self.conv = Conv(self.inter_dim, c2, 3, 1) def forward(self, x): x_level_0, x_level_1 = x[0], x[1] if self.level == 0: level_0_resized = x_level_0 level_1_resized = self.stride_level_1(x_level_1) elif self.level == 1: level_0_resized = self.stride_level_0(x_level_0) level_1_resized = x_level_1 level_0_weight_v = self.weight_level_0(level_0_resized) level_1_weight_v = self.weight_level_1(level_1_resized) levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v), 1) levels_weight = self.weights_levels(levels_weight_v) levels_weight = F.softmax(levels_weight, dim=1) fused_out_reduced = level_0_resized * levels_weight[:, 0:1] + level_1_resized * levels_weight[:, 1:2] return self.conv(fused_out_reduced) class ASFF3(nn.Module): def __init__(self, c1, c2, level=0): super().__init__() if len(c1) != 3 or level not in (0, 1, 2): raise ValueError('ASFF3 requires three input channel counts and level 0, 1 or 2') c1_l, c1_m, c1_h = c1 self.level = level self.dim = c1_l, c1_m, c1_h self.inter_dim = self.dim[self.level] compress_c = 8 if level == 0: self.stride_level_1 = Upsample(c1_m, self.inter_dim) self.stride_level_2 = Upsample(c1_h, self.inter_dim, scale_factor=4) if level == 1: self.stride_level_0 = Conv(c1_l, self.inter_dim, 2, 2, 0) self.stride_level_2 = Upsample(c1_h, self.inter_dim) if level == 2: self.stride_level_0 = Conv(c1_l, self.inter_dim, 4, 4, 0) self.stride_level_1 = Conv(c1_m, self.inter_dim, 2, 2, 0) self.weight_level_0 = Conv(self.inter_dim, compress_c, 1, 1) self.weight_level_1 = Conv(self.inter_dim, compress_c, 1, 1) self.weight_level_2 = Conv(self.inter_dim, compress_c, 1, 1) self.weights_levels = nn.Conv2d(compress_c * 3, 3, kernel_size=1, stride=1, padding=0) self.conv = Conv(self.inter_dim, c2, 3, 1) def forward(self, x): x_level_0, x_level_1, x_level_2 = x[0], x[1], x[2] if self.level == 0: level_0_resized = x_level_0 level_1_resized = self.stride_level_1(x_level_1) level_2_resized = self.stride_level_2(x_level_2) elif self.level == 1: level_0_resized = self.stride_level_0(x_level_0) level_1_resized = x_level_1 level_2_resized = self.stride_level_2(x_level_2) elif self.level == 2: level_0_resized = self.stride_level_0(x_level_0) level_1_resized = self.stride_level_1(x_level_1) level_2_resized = x_level_2 level_0_weight_v = self.weight_level_0(level_0_resized) level_1_weight_v = self.weight_level_1(level_1_resized) level_2_weight_v = self.weight_level_2(level_2_resized) levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v), 1) w = self.weights_levels(levels_weight_v) w = F.softmax(w, dim=1) fused_out_reduced = level_0_resized * w[:, :1] + level_1_resized * w[:, 1:2] + level_2_resized * w[:, 2:] return self.conv(fused_out_reduced)

YOLO26引入代码

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

importtorchimporttorch.nnasnnfromtorch.nnimportfunctionalasFfromultralytics.nn.modules.convimportConvclassUpsample(nn.Module):"""Applies convolution followed by upsampling."""def__init__(self,c1,c2,scale_factor=2):super().__init__()ifscale_factor==2:self.cv1=nn.ConvTranspose2d(c1,c2,2,2,0,bias=True)# nn.Upsample(scale_factor=2, mode='nearest')elifscale_factor==4:self.cv1=nn.ConvTranspose2d(c1,c2,4,4,0,bias=True)else:raiseValueError('AFPN Upsample supports scale_factor 2 or 4')defforward(self,x):returnself.cv1(x)classASFF2(nn.Module):"""ASFF2 module for YOLO AFPN head https://arxiv.org/abs/2306.15988"""def__init__(self,c1,c2,level=0):super().__init__()iflen(c1)!=2orlevelnotin(0,1):raiseValueError('ASFF2 requires two input channel counts and level 0 or 1')c1_l,c1_h=c1 self.level=level self.dim=c1_l,c1_h self.inter_dim=self.dim[self.level]compress_c=8iflevel==0:self.stride_level_1=Upsample(c1_h,self.inter_dim)iflevel==1:self.stride_level_0=Conv(c1_l,self.inter_dim,2,2,0)# downsample 2xself.weight_level_0=Conv(self.inter_dim,compress_c,1,1)self.weight_level_1=Conv(self.inter_dim,compress_c,1,1)self.weights_levels=nn.Conv2d(compress_c*2,2,kernel_size=1,stride=1,padding=0)self.conv=Conv(self.inter_dim,c2,3,1)defforward(self,x):x_level_0,x_level_1=x[0],x[1]ifself.level==0:level_0_resized=x_level_0 level_1_resized=self.stride_level_1(x_level_1)elifself.level==1:level_0_resized=self.stride_level_0(x_level_0)level_1_resized=x_level_1 level_0_weight_v=self.weight_level_0(level_0_resized)level_1_weight_v=self.weight_level_1(level_1_resized)levels_weight_v=torch.cat((level_0_weight_v,level_1_weight_v),1)levels_weight=self.weights_levels(levels_weight_v)levels_weight=F.softmax(levels_weight,dim=1)fused_out_reduced=level_0_resized*levels_weight[:,0:1]+level_1_resized*levels_weight[:,1:2]returnself.conv(fused_out_reduced)classASFF3(nn.Module):def__init__(self,c1,c2,level=0):super().__init__()iflen(c1)!=3orlevelnotin(0,1,2):raiseValueError('ASFF3 requires three input channel counts and level 0, 1 or 2')c1_l,c1_m,c1_h=c1 self.level=level self.dim=c1_l,c1_m,c1_h self.inter_dim=self.dim[self.level]compress_c=8iflevel==0:self.stride_level_1=Upsample(c1_m,self.inter_dim)self.stride_level_2=Upsample(c1_h,self.inter_dim,scale_factor=4)iflevel==1:self.stride_level_0=Conv(c1_l,self.inter_dim,2,2,0)self.stride_level_2=Upsample(c1_h,self.inter_dim)iflevel==2:self.stride_level_0=Conv(c1_l,self.inter_dim,4,4,0)self.stride_level_1=Conv(c1_m,self.inter_dim,2,2,0)self.weight_level_0=Conv(self.inter_dim,compress_c,1,1)self.weight_level_1=Conv(self.inter_dim,compress_c,1,1)self.weight_level_2=Conv(self.inter_dim,compress_c,1,1)self.weights_levels=nn.Conv2d(compress_c*3,3,kernel_size=1,stride=1,padding=0)self.conv=Conv(self.inter_dim,c2,3,1)defforward(self,x):x_level_0,x_level_1,x_level_2=x[0],x[1],x[2]ifself.level==0:level_0_resized=x_level_0 level_1_resized=self.stride_level_1(x_level_1)level_2_resized=self.stride_level_2(x_level_2)elifself.level==1:level_0_resized=self.stride_level_0(x_level_0)level_1_resized=x_level_1 level_2_resized=self.stride_level_2(x_level_2)elifself.level==2:level_0_resized=self.stride_level_0(x_level_0)level_1_resized=self.stride_level_1(x_level_1)level_2_resized=x_level_2 level_0_weight_v=self.weight_level_0(level_0_resized)level_1_weight_v=self.weight_level_1(level_1_resized)level_2_weight_v=self.weight_level_2(level_2_resized)levels_weight_v=torch.cat((level_0_weight_v,level_1_weight_v,level_2_weight_v),1)w=self.weights_levels(levels_weight_v)w=F.softmax(w,dim=1)fused_out_reduced=level_0_resized*w[:,:1]+level_1_resized*w[:,1:2]+level_2_resized*w[:,2:]returnself.conv(fused_out_reduced)

tasks注册

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

步骤1:导包

fromultralytics.nn.featureFusion.AFPNimportASFF2,ASFF3

步骤2:解析多路输入与通道

在parse_model(d, ch, verbose=True)的模块判断链中增加以下专用分支。

elifmin{ASFF2,ASFF3}:expected_inputs=2ifmisASFF2else3ifnotisinstance(f,list)orlen(f)!=expected_inputs:raiseValueError(f"{m.__name__}requires{expected_inputs}input layers")c2=make_divisible(min(args[0],max_channels)*width,8)args=[[ch[x]forxinf],c2,*args[1:]]

配置yolo26-AFPN.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: progressive two-scale and three-scale adaptive fusionhead:-[4,1,Conv,[128,1,1]]# 11 project backbone P3/8-[6,1,Conv,[256,1,1]]# 12 project backbone P4/16-[[11,12],1,ASFF2,[128,0]]# 13 fuse at P3-[[11,12],1,ASFF2,[256,1]]# 14 fuse at P4-[13,2,C3k2,[128,True]]# 15 refine P3-[14,2,C3k2,[256,True]]# 16 refine P4-[10,1,Conv,[512,1,1]]# 17 project backbone P5/32-[[15,16,17],1,ASFF3,[128,0]]# 18 fuse at P3-[[15,16,17],1,ASFF3,[256,1]]# 19 fuse at P4-[[15,16,17],1,ASFF3,[512,2]]# 20 fuse at P5-[18,2,C3k2,[128,True]]# 21 P3/8-small-[19,2,C3k2,[256,True]]# 22 P4/16-medium-[20,1,C3k2,[512,True,0.5,True]]# 23 P5/32-large-[[21,22,23],1,Detect,[nc]]# Detect(P3, P4, P5)

实验

脚本

importwarnings warnings.filterwarnings('ignore')fromultralyticsimportYOLOif__name__=='__main__':# 修改为自己的配置文件地址model=YOLO('./ultralytics/cfg/models/26/yolo26-AFPN.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='AFPN',)

结果

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进