PPYOLOE详解第二弹:你真的知道什么是Neck嘛?


本文先介绍双阶段检测模型Neck,以FPN论文为例讲4种结构。再讲单阶段检测模型,回顾YOLOv1到v3中Neck的发展,最后重点介绍PANet及PPYOLOE的Neck结构,还给出了相关代码,展现了目标检测网络Neck的演变与特点。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

上次介绍完了PPYOLOE的Backbone 那么这次就到目标检测网络的第二部分--Neck了

相比较Backbone被称为主干(骨干)网络比较明确,但是neck的提出就不太明确了,我第一次认识到这个词还是在看YOLOv4网络的时候,YOLOv3中我甚至都没注意到这里,因为YOLOv3中Neck就只是使用来的一个上采样的方式来将主干网络的不同输出进行融合,但是到了YOLOv4中 Neck的占比变得更丰富了,它的作用甚至逐渐的和Head与Backbone相提并论了起来,由于PPYOLOE中的neck创新点也不是很多,单独说又显得有点水,那么这里我们就向大家介绍几种不同的Neck以及YOLO系列中Neck的发展历史,最后再讲YOLO Neck!

So just do it

相信如果对目标检测有一些了解的人应该会知道目标检测网络分为点阶段与双阶段检测模型,那么我们先简单讲一下与本文关系不太大的双阶段检测模型都有哪些Neck吧!

零:双阶段检测模型Neck

这里我们以《Feature Pyramid Networks for Object Detection》这篇论文为例子来介绍几个Neck,FPN这篇论文中一共提出了4个结构

       
  1. 使用图像金字塔构建特征金字塔。在每个图像比例上独立计算特征,速度较慢。
  2. 最近的检测系统选择只使用单尺度特征,以实现更快的检测。
  3. 另一种方法是重用Conventas计算的金字塔特征层次(如果它是特征化的图像金字塔)。
  4. 特征金字塔网络(FPN)与(b)和(c)一样快速,但更精确。

0.1:第一种

       

第一种是对每一种尺度的图像都进行特征提取,因此能够产生多尺度的特征表示,由于所有登记的特征图都具有不同的语义信息因此检测精度较高,但是他的推理时间相较于其他网络会大幅增加,同时占用大量的资源,不好实现。

0.2: 第二种

       

第二种就是只利用最高层的特征图进行预测,像是经典的FasterRCNN网络就是使用的这种简单的方式,也就是几乎没有Neck什么事情,这种方法速度虽然比较快,但是他的检测精度相比较其他几种方法来说还是有待提高

0.3: 第三种

       

第三种网络与第一种网络类似,但是他放弃了使用底层特征图,而是在较高层次搭建金字塔结构,这就导致一些比较低级的语义信息被错过,而这对于小目标检测来说是极其重要的

0.4: 第四种

       

插曲

特征金字塔结构,我有一个项目是在详细的介绍,这里不过多赘述,如想详细了解可以看我的之前的项目ResNet+FPN详解

一:单阶段检测模型

1.1: YOLOv1

原论文 You Can Only look once 

YOLOv1是与SSD和Faster RCNN同时代的产物,但是YOLOv1在刚刚出来的时候真的不是很惊艳,因为在检测精度上它与Faster RCNN相差太远了,在“能耗比”上也比不过同时期的SSD,而且这时的YOLOv1采用的是上文中的b模式,也就是只用最高层的特征图进行预测,所以其实YOLOv1中并没有什么Neck,这里也就不过多赘述了,上面有原论文有兴趣的可以去读一下,这里就放一张YOLOv1的原图供大家参考

       

这里的图片源自B站up主霹雳吧啦Wz

1.2:YOLOv2

原论文 YOLO9000: Better, Faster, Stronger

       

*** YOLOv2的论文原名叫YOLO9000,这是因为作者通过引入anchor等操作让YOLO能够检测超过9000种,正如YOLO9000的名字一样 Better,Faster,Stronger,作者在网络中也开始使用Backbone中间层次的特征图,从而利用到了中间层的语义信息,通过上图可以这里YOLOv2是将中间的分支先通过一个基础卷积块把通道数从512变为64,后通过PassThrough Layer将通道数改为256,长宽各减一半,然后与最高层的特征图在深度维度进行concat拼接,这里YOLOv2已经开始使用下采样的的方式来进行语义融合,这里我认为可以算是YOLO的Neck初具雏形,但在YOLOv2中它仍然只有一个检测头,所以它应该是类似于FPN***

这里的图片源自B站up主霹雳吧啦Wz

1.3: YOLOv3

YOLOv3: An Incremental Improvement

       

YOLOv3的论文名称很有意思,渐进式改进,所以可能作者只是想把YOLO当做一个日常升级?自己还藏了一手?所以也有的同学会戏称原YOLO系列的作者为藏一手司机。在YOLOv3中Backbone已经被替换成了DarkNet53,这次的Backbone输出分支也从2变成了3个,同时预测头也增加到了3个。这里我们就将Backbone的三个分支从低到高以此称为C0 [B,256,64,64],C1[B,512,32,32],C2[B,1024,16,16]。首先网络将C2的输出经过一系列卷积之后,传出两个分支C2-1,C2-2,C2-1分支用于预测头,C2-2经过上采样技术用于与C1的输出进行Concat拼接用于语义融合,并在经过一系列卷积之后再次传出两个分支C1-1、C1-2,C1-1分支作用在第二个预测头上,C1-2分支再次经过一个上采样技术然后与C0的释出进行Concat拼接用于语义融合,并在经过一系列卷积之后,诶~这回到头了,不分支了,直接接一个检测头,哈哈哈哈

Neck:我滴任务完成了

       

YOLOv3相比较YOLOv2他增加了Backbone输出,增加了预测头分支,但是他的语义融合方式从下采样改成了上采样,所以这里有没有可能就是,他真的留了一手,真的准备在自己的YOLOv4中让Neck称为一个真正的Neck?但是很可惜由于种种原因YOLO的原作者放弃了更新YOLO,留一手司机究竟留没留一手,谁也不知道了。

这里的图片源自B站up主霹雳吧啦Wz

二:正式介绍PANet

比较新的几篇YOLO的Neck都是采用了PAN结构,像是PPYOLOv2,YOLO-X,YOLOv5(待定)以及本文要介绍PPYOLOE都采用了这个结构

       

首先从Backbone获取C5输出需要经过CSPStage,

       
# 1. C5会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从1024变成384.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从1024变为384,然后经过3个BasicBlock结构其中在第二个BasicBlock结构后加入一个SPP结构# 2. 将分支一与分支二进行concat拼接# 3. 再经过一个ConvBNLayer结构,说句实话我也不知道这是在干吗因为这个ConvBN是1x1卷积,而且他的输出通道数与输入通道数都是768,所以我也有点疑惑。# 4. 分出两支一个为C5-1,另外一个为C5-2
               
 5. 然后将C5-1再经过一个ConvBNLayer层将通道数从768转换为384,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 6. 然后与C4进行Concat拼接,称为C4-0,此时C4-0通道数为896,宽高都为32.
               
# 7. C4-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从896变成192.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从896变为192,然后经过3个BasicBlock结构# 8. 将分支一与分支二进行concat拼接# 9. 再经过一个ConvBNLayer结构。# 10. 分出两支一个为C4-1,另外一个为C4-2
               
 11. 然后将C5-1再经过一个ConvBNLayer层将通道数从384转换为192,然后通过interpolate函数进行上采样将宽高x2,通道数不变。 12. 然后与C3进行Concat拼接,称为C3-0,此时C3-0通道数为448,宽高都为64.
               
# 13. C3-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从448变成96.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从448变为96,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 16. 分出两支一个为C3-1,另外一个为C3-2# 17. C3-2直接作用于预测头上
               
 11. 然后将C3-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C4-2进行Concat拼接,称为P4-0,此时P4-0通道数为576,宽高都为32.
               
# 13. P4-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从576变成192.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从576变为192,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 16. 分出两支一个为P4-1,P4-2# 17. P4-2直接作用于预测头上
               
 11. 然后将P4-1再经过一个ConvBNLayer层将宽高除以二 12. 然后与C5-2进行Concat拼接,称为P5-0,此时P4-0通道数为1152,宽高都为16.
               
# 13. P5-0会先分出两个分支
	# 1.1. 分支一     直接经过一个1x1卷积把通道数从1152变成384.
	# 1.2. 分支二     首先经过一个1x1的卷积将通道数从1152变成384,然后经过3个BasicBlock结构# 14. 将分支一与分支二进行concat拼接# 15. 再经过一个ConvBNLayer结构。# 17. 然后直接作用于预测头上
   

PPYOLOE Neck 所有代码

代码地址 PaddleDetection/ppdet/modeling/necks/custom_pan.py

In [9]
!git clone -b develop https://gitee.com/paddlepaddle/PaddleDetection.git

%cd PaddleDetection/
!python setup.py install
!pip install -r requirements.txt
    In [ ]
!python ./ppdet/modeling/necks/custom_pan.py
   
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at##     http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.import paddleimport paddle.nn as nnimport paddle.nn.functional as Ffrom ppdet.core.workspace import register, serializablefrom ppdet.modeling.layers import DropBlockfrom ppdet.modeling.ops import get_act_fnfrom ppdet.modeling.backbones.cspresnet import ConvBNLayer, BasicBlockfrom ppdet.modeling.shape_spec import ShapeSpecimport numpy as np

__all__ = ['CustomCSPPAN']class SPP(nn.Layer):
    def __init__(self,
                 ch_in,
                 ch_out,
                 k,
                 pool_size,
                 act='swish',
                 data_format='NCHW'):
        super(SPP, self).__init__()
        self.pool = []
        self.data_format = data_format        for i, size in enumerate(pool_size):
            pool = self.add_sublayer(                'pool{}'.format(i),
                nn.MaxPool2D(
                    kernel_size=size,
                    stride=1,
                    padding=size // 2,
                    data_format=data_format,
                    ceil_mode=False))
            self.pool.append(pool)
        self.conv = ConvBNLayer(ch_in, ch_out, k, padding=k // 2, act=act)    def forward(self, x):
        outs = [x]        for pool in self.pool:
            outs.append(pool(x))        if self.data_format == 'NCHW':
            y = paddle.concat(outs, axis=1)        else:
            y = paddle.concat(outs, axis=-1)

        y = self.conv(y)        return yclass CSPStage(nn.Layer):
    def __init__(self, block_fn, ch_in, ch_out, n, act='swish', spp=False):
        super(CSPStage, self).__init__()

        ch_mid = int(ch_out // 2)
        self.conv1 = ConvBNLayer(ch_in, ch_mid, 1, act=act)
        self.conv2 = ConvBNLayer(ch_in, ch_mid, 1, act=act)
        self.convs = nn.Sequential()
        next_ch_in = ch_mid        for i in range(n):
            self.convs.add_sublayer(                str(i),                eval(block_fn)(next_ch_in, ch_mid, act=act, shortcut=False))            if i == (n - 1) // 2 and spp:
                self.convs.add_sublayer(                    'spp', SPP(ch_mid * 4, ch_mid, 1, [5, 9, 13], act=act))
            next_ch_in = ch_mid
        self.conv3 = ConvBNLayer(ch_mid * 2, ch_out, 1, act=act)    def forward(self, x):
        y1 = self.conv1(x)
        y2 = self.conv2(x)
        y2 = self.convs(y2)
        y = paddle.concat([y1, y2], axis=1)
        y = self.conv3(y)        return yclass CustomCSPPAN(nn.Layer):
    __shared__ = ['norm_type', 'data_format', 'width_mult', 'depth_mult', 'trt']    def __init__(self,
                 in_channels=[256, 512, 1024],
                 out_channels=[768,768/2,768/4],
                 norm_type='bn',
                 act='relu',
                 stage_fn='CSPStage',
                 block_fn='BasicBlock',
                 stage_num=1,
                 block_num=3,
                 drop_block=False,
                 block_size=3,
                 keep_prob=0.9,
                 spp=True,
                 data_format='NCHW',
                 width_mult=1.0,
                 depth_mult=1.0,
                 trt=False):

        super(CustomCSPPAN, self).__init__()
        out_channels = [max(round(c * width_mult), 1) for c in out_channels]
        block_num = max(round(block_num * depth_mult), 1)
        act = get_act_fn(
            act, trt=trt) if act is None or isinstance(act,
                                                       (str, dict)) else act
        self.num_blocks = len(in_channels)
        self.data_format = data_format
        self._out_channels = out_channels
        in_channels = in_channels[::-1]
        fpn_stages = []
        fpn_routes = []        for i, (ch_in, ch_out) in enumerate(zip(in_channels, out_channels)):            if i > 0:
                ch_in += ch_pre // 2

            stage = nn.Sequential()            for j in range(stage_num):
                stage.add_sublayer(                    str(j),                    eval(stage_fn)(block_fn,
                                   ch_in if j == 0 else ch_out,
                                   ch_out,
                                   block_num,
                                   act=act,
                                   spp=(spp and i == 0)))            if drop_block:
                stage.add_sublayer('drop', DropBlock(block_size, keep_prob))

            fpn_stages.append(stage)            if i < self.num_blocks - 1:
                fpn_routes.append(
                    ConvBNLayer(
                        ch_in=ch_out,
                        ch_out=ch_out // 2,
                        filter_size=1,
                        stride=1,
                        padding=0,
                        act=act))

            ch_pre = ch_out

        self.fpn_stages = nn.LayerList(fpn_stages)
        self.fpn_routes = nn.LayerList(fpn_routes)

        pan_stages = []
        pan_routes = []        for i in reversed(range(self.num_blocks - 1)):
            pan_routes.append(
                ConvBNLayer(
                    ch_in=out_channels[i + 1],
                    ch_out=out_channels[i + 1],
                    filter_size=3,
                    stride=2,
                    padding=1,
                    act=act))

            ch_in = out_channels[i] + out_channels[i + 1]
            ch_out = out_channels[i]
            stage = nn.Sequential()            for j in range(stage_num):
                stage.add_sublayer(                    str(j),                    eval(stage_fn)(block_fn,
                                   ch_in if j == 0 else ch_out,
                                   ch_out,
                                   block_num,
                                   act=act,
                                   spp=False))            if drop_block:
                stage.add_sublayer('drop', DropBlock(block_size, keep_prob))

            pan_stages.append(stage)

        self.pan_stages = nn.LayerList(pan_stages[::-1])
        self.pan_routes = nn.LayerList(pan_routes[::-1])    def forward(self, blocks, for_mot=False):
        blocks = blocks[::-1]
        fpn_feats = []        for i, block in enumerate(blocks):            if i > 0:
                block = paddle.concat([route, block], axis=1)            print(self.fpn_stages[i])
            route = self.fpn_stages[i](block)
            fpn_feats.append(route)            if i < self.num_blocks - 1:
                route = self.fpn_routes[i](route)
                route = F.interpolate(
                    route, scale_factor=2., data_format=self.data_format)

        pan_feats = [fpn_feats[-1], ]
        route = fpn_feats[-1]        for i in reversed(range(self.num_blocks - 1)):
            block = fpn_feats[i]
            route = self.pan_routes[i](route)
            block = paddle.concat([route, block], axis=1)
            route = self.pan_stages[i](block)
            pan_feats.append(route)        return pan_feats[::-1]    @classmethod
    def from_config(cls, cfg, input_shape):
        return {'in_channels': [i.channels for i in input_shape], }    @property
    def out_shape(self):
        return [ShapeSpec(channels=c) for c in self._out_channels]if __name__=='__main__':
    model = CustomCSPPAN()
    path = './infer/neck/CustomCSPPAN_SPP_True'
    # weight_path = '123.pdparams'
    # static = paddle.load(weight_path)
    x2 = paddle.to_tensor(np.random.rand(1,1024,16,16),dtype='float32')
    x1 = paddle.to_tensor(np.random.rand(1,512,32,32),dtype='float32')
    x0 = paddle.to_tensor(np.random.rand(1,256,64,64),dtype='float32')
    out = [x0,x1,x2]
    out = model(out)    # model.eval()
    # paddle.jit.save(model,path,(out,False))


# 头上  # 相比较  # 第一种  # 另外一个  # 都是  # 数为  # 两支  # 都为  # 是在  # 会先  # python  # YOLO  # for  # Object  # asic  # red  # ai  # b站  # apache  # git 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化91478 】 【 技术知识72672 】 【 云计算0 】 【 GEO优化84317 】 【 优选文章0 】 【 营销推广36048 】 【 网络运营41350 】 【 案例网站102563 】 【 AI智能45237


相关推荐: 斑马AI怎样注册账号_斑马AI注册流程与儿童信息绑定【教程】  使用文心一言进行中文客服话术库的逻辑优化  AI辅助儿童圣经课程创作:轻松制作教育视频  怎么用ai生成配色方案 AI设计色彩搭配与灵感获取【技巧】  批改网ai检测工具能否检测引用格式_批改网ai检测工具引用格式检查与修正提示【攻略】  AI网站构建指南:Duda平台免费创建教程  Apollo.io vs Instantly AI:深度测评与功能对比  解锁生成式AI工程师之路:技能、职业发展与未来趋势  秀米AI排版如何自动生成模板_秀米AI排版模板生成入口与风格选择【攻略】  lovemo网页版直接进入 lovemo官网在线登录  医疗专家如何利用课程和内容赋能女性对抗癌症  如何用AI一键生成求职简历?AI简历优化与制作工具推荐【干货】  讯飞星火怎样一键生成教案_讯飞星火教案生成与学科选择【教程】  TRX40主板终极对决:3990X散热性能深度评测  Gemini怎样写实用型提示词_Gemini实用提示词编写【攻略】  AI驱动的Web应用测试:突破QA挑战,提升用户体验  AI 3D建模革命:免费生成高质量模型和纹理  服务合同模板:起草、签署和管理指南,提升业务效率  经济型游戏PC构建指南:30000卢比畅玩3A游戏  面试成功秘诀:如何巧妙回答常见面试问题  探索心灵的音乐之旅:Kanwar Garewal的《Ishq Bulleh Nu》  AI交易机器人:TradingView上无需代码即可构建AI交易机器人指南  CareerCraft AI:提升大学生实习就业的智能平台  Beats to Rap On AI Stem Splitter:终极音乐创作工具  AI绘图工具测评:告别复杂流程,高效创作流程图  如何利用文心一言优化知乎高赞回答的逻辑结构  怎么用AI帮你进行头脑风暴并分类?5分钟输出结构化创意清单  AI写作鱼怎么一键生成朋友圈文案_AI写作鱼文案风格切换与字数设置【指南】  探索占星术:揭秘 कुंडली 中的 शुक्र,财富与运势的钥匙  淋巴按摩终极指南:在家打造紧致透亮肌肤  百度搜索ai助手怎么关闭 百度搜索ai对话屏蔽方法  Feelin聊天网页版地址 Feelin AI官方网站首页  快速生成PPT工具怎么用_快速生成PPT工具使用方法详细指南【教程】  Midjourney怎样用参数调分辨率_Midjourney分辨率调整技巧【教程】  AI写作鱼怎么一键生成论文大纲_AI写作鱼大纲生成与逻辑梳理【技巧】  AI 时代高效开发:版本控制与 AI 协同工作流  Midjourney怎么用一键生成海报_Midjourney海报生成教程【方法】  五大AI视频编辑工具:提升视频创作效率和质量  如何通过 DeepSeek 优化分布式存储系统架构  斑马AI能否关联学校教材_斑马AI教材同步与版本匹配【技巧】  AI海报设计终极指南:免费智能工具,手机轻松搞定!  Pearson AI学习工具:高效提升你的数学学习效率  Ignite & Sell Assistant:AI 邮件营销终极指南  AI一键生成儿童绘本故事  lovemo官网入口直达 lovemo网页版在线使用  飞猪旅行AI如何预约抢票_飞猪AI抢票预约与加速包使用【攻略】  Mootion AI视频生成器:一键创作动画故事!  腾讯混元图像3.0上线LiblibAI,80B参数助力创作者高效出图  Claude 4.5 深度解析: Coding, VS Code & AI Agent 新纪元  Claude怎样写指令型提示词_Claude指令提示词写法【方法】 

 2025-07-31

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

南京市珐之弘网络技术有限公司


南京市珐之弘网络技术有限公司

南京市珐之弘网络技术有限公司专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 87067657

 13565296790

 87067657@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.