2. YOLOv3/v4/v5模型结构精讲:Backbone、Neck、Head设计思想、CSPNet与Focus结构、损失函数与后处理

好,咱们直接进入正题。YOLO系列从v3到v5,结构上其实有一条很清晰的进化脉络。我个人习惯把这三代放在一起讲,因为它们的核心思想是一脉相承的,只是每个版本在细节上做了「手术刀式」的优化。你想想看,搞FPGA部署最怕什么?怕模型结构花里胡哨,算子太偏。所以理解清楚这些结构,你才能知道哪些层可以砍,哪些层必须留。

2.1 Backbone:特征提取的「地基」

Backbone说白了就是用来「看」图片的。它把一张图不断下采样,提取出不同尺度的特征图。YOLOv3用的是Darknet-53,v4换成了CSPDarknet-53,v5则是在v4基础上又加了Focus层。

Darknet-53(YOLOv3):53层卷积堆叠,全是3x3和1x1。没有花哨的Inception或残差密集连接,就是简单堆叠。我在项目中遇到过一个问题:这种结构在FPGA上其实很友好,因为卷积核尺寸固定,容易做流水线优化。但缺点是参数量偏大,对DSP资源消耗高。

CSPDarknet-53(YOLOv4):这里引入了CSPNet思想。嗯,CSPNet是什么?它把特征图分成两路:一路正常卷积,另一路直接拼接到后面。这样做的好处是梯度流更通畅,计算量还降了20%左右。我个人觉得,这是YOLOv4最值得借鉴的设计。

核心要点:CSPNet的本质是「部分连接」,不是全连接。它减少了重复梯度计算,让网络更容易训练。

YOLOv5的Backbone:在CSPDarknet基础上,v5在输入层加了Focus结构。这个结构很有意思——它把输入图片每隔一个像素取一个点,拼成4张缩小图。说白了就是空间换通道,把宽高信息压缩到通道里。我刚开始看这个设计时觉得多此一举,后来在FPGA上实测发现:Focus层可以用切片操作实现,完全不需要乘法器,对资源紧张的场景简直是福音。

2.2 Neck:特征融合的「桥梁」

Neck的作用是把Backbone提取的不同尺度特征融合起来。YOLOv3用的是FPN(特征金字塔),v4和v5则升级成了PANet结构。

FPN(YOLOv3):自顶向下传递语义信息。大特征图有位置信息,小特征图有语义信息,FPN把它们串起来。但有个问题:它只传递了语义信息,位置信息传递不够。我在做小目标检测时吃过这个亏——FPN对大目标还行,小目标经常漏检。

PANet(YOLOv4/v5):在FPN基础上加了一条自底向上的路径。说白了就是「双向融合」——既把语义信息往下传,又把位置信息往上送。这样做小目标检测精度能提升3-5个点。你想想看,这对FPGA部署意味着什么?意味着你需要多处理一条特征融合路径,但换来的是精度提升,值不值?我个人觉得值。

结构 融合方式 小目标检测 FPGA资源消耗
FPN(v3) 单向 一般
PANet(v4/v5) 双向

2.3 Head:检测输出的「最后一公里」

Head负责输出最终的检测结果。YOLOv3/v4/v5的Head结构基本一致,都是1x1卷积输出。但有个关键区别:v3和v4用的是耦合头(分类和回归共享卷积),v5改成了解耦头(分类和回归分开)。

耦合头:一个卷积同时输出类别和坐标。优点是参数少,速度快。缺点是分类和回归任务互相干扰。我在项目中遇到过:耦合头训练时loss下降很快,但最终mAP上不去,就是因为两个任务在「打架」。

解耦头(YOLOv5):分类和回归各用一组卷积。虽然参数多了点,但每个任务可以专注学习自己的特征。嗯,这里要注意:解耦头在FPGA上实现时,需要多分配一些DSP资源给分类分支,因为分类的通道数通常比回归多。

避坑指南:我曾经在部署YOLOv5时,为了省资源把解耦头强行合并成耦合头,结果精度掉了2个点。后来老老实实保留了原结构。所以我的建议是:Head部分尽量别动,这是精度敏感区。

2.4 CSPNet与Focus结构深度解析

这两个结构是YOLOv4/v5的核心创新,值得单独拿出来讲。

CSPNet:全称Cross Stage Partial Network。它的设计思想是:把特征图按通道分成两部分,一部分正常处理,另一部分直接跳过。这样做的好处有三个:

  • 减少计算量(约20%)
  • 防止梯度消失(梯度可以直通)
  • 提升特征复用(跳过的那部分保留了原始信息)

在FPGA上实现CSPNet时,我建议把拆分和拼接操作用DMA+行缓存实现,不要用BRAM去存整张特征图,否则资源会爆。

Focus结构:这个结构在YOLOv5中用于下采样。它把输入图片每隔一个像素取一个点,生成4张子图,然后拼在一起。举个例子:输入是640x640x3,经过Focus后变成320x320x12。为什么这么做?因为这样可以避免使用步长为2的卷积,减少信息丢失。

# Focus结构的伪代码实现
def focus(x):
    # x: [B, C, H, W]
    # 每隔一个像素取点
    x1 = x[:, :, 0::2, 0::2]  # 左上
    x2 = x[:, :, 0::2, 1::2]  # 右上
    x3 = x[:, :, 1::2, 0::2]  # 左下
    x4 = x[:, :, 1::2, 1::2]  # 右下
    # 在通道维度拼接
    return torch.cat([x1, x2, x3, x4], dim=1)

注意:Focus结构在FPGA上实现时,切片操作可以用简单的地址映射完成,不需要额外计算。但要注意数据对齐——如果输入宽度不是偶数,切片会出问题。我建议在预处理阶段就保证输入尺寸是2的幂次。

2.5 损失函数:训练的核心驱动力

YOLOv3/v4/v5的损失函数都包含三部分:分类损失、定位损失、置信度损失。但具体实现有差异。

YOLOv3:定位损失用MSE(均方误差)。简单粗暴,但有个问题——MSE对大小目标的误差一视同仁,导致小目标定位不准。

YOLOv4:改用CIoU损失。CIoU考虑了三个因素:重叠面积、中心点距离、宽高比。说白了就是「不仅要框得准,还要框得正」。我在项目中对比过,CIoU比MSE在mAP上能提升2-3个点。

YOLOv5:在CIoU基础上加了Focal Loss思想。Focal Loss是什么?它让模型更关注难分类的样本,减少易分类样本的权重。嗯,这里要注意:Focal Loss在FPGA上推理时不需要计算,它只影响训练过程。所以部署时你只需要实现CIoU的后处理即可。

版本 定位损失 分类损失 置信度损失
v3 MSE BCE BCE
v4 CIoU BCE BCE
v5 CIoU + Focal BCE + Focal BCE + Focal

2.6 后处理:从网络输出到检测框

后处理是FPGA部署中最容易出问题的环节。YOLO的后处理主要分两步:NMS(非极大值抑制)和坐标解码。

坐标解码:网络输出的是相对于网格的偏移量,需要解码成真实坐标。公式很简单:

bx = sigmoid(tx) + cx   # 中心点x
by = sigmoid(ty) + cy   # 中心点y
bw = pw * exp(tw)       # 宽度
bh = ph * exp(th)       # 高度

其中cx、cy是网格坐标,pw、ph是先验框尺寸。我在FPGA上实现时,把sigmoid和exp用查找表实现,省掉了浮点运算单元。

NMS:这是后处理的性能瓶颈。传统NMS是O(n²)复杂度,当检测框数量多时,FPGA上跑不动。我建议用以下优化:

  • 先按置信度排序,只保留Top-K个框(K=100左右)
  • 用并行比较器同时计算多个框的IoU
  • 用流水线结构处理,避免串行等待

个人经验:我曾经在FPGA上实现NMS时,用了纯硬件状态机,结果时序跑不到200MHz。后来改成微处理器+硬件加速器的混合架构,把排序和IoU计算交给硬件,控制逻辑交给软件,才解决了问题。所以我的建议是:后处理别全用硬件实现,软硬协同才是王道。

好了,这一章的内容就到这里。YOLOv3/v4/v5的结构虽然各有不同,但核心思想是相通的——用更好的Backbone提取特征,用更聪明的Neck融合特征,用更精细的Head输出结果。理解了这些,你就能在FPGA上做出有针对性的优化。下一章我们会讲如何把这些结构映射到FPGA的硬件资源上,到时候见。