没想到 YOLOv1 到 YOLOv4 就已经写了快 20000 字……

编辑页面卡的不行,我不得不分几篇写完。YOLOv1 到 YOLO 26一共 12 篇,正好分上中下三篇。

YOLOv5

重量级来了,Ultralytics 闪亮登场了。第一个没有论文的 YOLO 正式版本,但它成为了工业界的事实标准。

YOLOv5 的一大变化就是提供了多种模型尺寸:n/s/m/l/x,通过统一的的 depth_multiple(深度倍数)和 width_multiple(宽度倍数)参数缩放。基准模型也就是没有缩放的版本是 l 。其中 depth_multiple 参数实际上控制的是可重复模块(如 C3、BottleneckCSP)的堆叠次数width_multiple 控制的则是卷积层的通道数

YOLOv5 还是提供了 P5(640×640)和 P6(1280×1280) 两套不同输入分辨率的模型。P6 在 Backbone 多了一层特征提取。咱常用的是 P5。

Pytorch

YOLOv4 仍然基于 DarkNet 框架,没有使用 PyTorch,底层用 C 语言实现,网络结构通过 cfg 文件定义,而非 yaml 文件或者 python 代码,训练和二次开发门槛高,部署也很麻烦。

YOLOv5 完全使用 PyTorch 框架实现,原生支持 Onnx/TensorRT 等部署导出,十分方便。

俺寻思,后来 Pytorch 一统天下, YOLOv5 功不可没。

网络设计上,延续 YOLO 经典结构: Input --> Backbone(特征提取) --> Neck(特征融合) --> Head(检测头)。

Input

YOLOv4 引入了 Mosaic,v5 则是直接集成 Mosaic 到默认训练流程。

YOLOv5 加入了 AutoAnchor,K - means 之后用遗传算法优化得到最佳 Anchor,每次训练开始之前,会根据数据集自适应计算 Anchor。在此之前 Anchor 需要手动调整。

Focus

Focus 模块位于网络最前端,紧接输入,作用是将空间维度上的信息聚焦到通道维度,实现2倍下采样,但是不丢失任何信息

假设输入一个 640 × 640 × 3 的图像,空间维度上拆分成 2 × 2 的Grid,就是 4 块区域,注意这里不是从整体上切分,看下图:

YOLOv5-Focus.webp

然后就得到了 4 张 320 × 320 × 3的特征图,然后在通道维度上做拼接得到 12 通道的 320 × 320 特征图,最后用 3 × 3 卷积映射到目标通道数。

后来这个模块被一个 6×6 Conv(k=6, s=2, p=2)给替代了。主要是考虑到内存连续访问和推理引擎原生优化的问题。

CSP + C3

也就是从这时候开始,YOLO 开始出现很多网络结构(初看会感觉意义不明)的缩写,罪大恶极。你叫 CBL 我能理解为 Conv + BN + LeakyReLU,可这 C3 是个什么玩意儿?

先说一下 BottleNeck,这个在前面没有提到,是一种先压缩、再提取、再恢复信息的残差模块。举个简单的例子:

输入:H × W × 256

通道投影(1×1 Conv):
→ H × W × 64

空间特征提取(3×3 Conv,stride=1,padding=1):
→ H × W × 64

通道映射恢复(1×1 Conv):
→ H × W × 256

类似瓶颈,先宽后窄再宽,降维之后再用 3 × 3 卷积可以有效降低计算量。这里的降维类似一次通道投影,可以朝PCA那个方向去理解,降维之后得到的是比较有价值的特征,而不是白白损失掉了。

还可以接残差 shortcut,把网络堆的更深。在 Backbone 一般用带残差的,缓解深层网络梯度消失的问题。 Neck 一般用不带残差的,因为 Neck 用于特征融合,本来就浅。

C3 就是多个 BottleNeck 融合 CSP 思想的产物。Cross Stage Partial Bottleneck with 3 convolutions

那我们复习一下 CSP 是啥呢……感觉距离看完上篇文章已经过了5分钟了捏……

CSP 就是把特征从通道维度拆分,一部分通道继续向深处走,另一部分直接走捷径,最后再融合;和ResNet的区别是,ResNet需要全部通道参与运算。

YOLOv5 的 C3 结构可以理解成 n 个 BottleNeck串联又 加了3个卷积层。具体结构可以参考下图的 CSPLayer,其中的 ConvModule 是 CBS。

YOLOv5l-Architecture.webp

C3 替代的是 YOLOv4 中的 BottleneckCSP;而 BottleneckCSP 替代的是原始 Darknet53 中的 Residual Block。

SPPF

Faster 的 SPP,YOLOv5 的重要工程优化,速度更快,但是精度上……YOLOv5没论文,没消融实验,俺不知道……

为啥串行反而 Fast 了呢?不是并行更快么?

SPPF 是用串行 5 × 5 的 MaxPooling 来取代并行的 5/9/13 的 SSP,上图对 SPPF 的绘制有误,实际上是跳连后才 Concat 的。

SPP 的计算量:

5×5 池化:O(25 \times H \times W \times C)
9×9 池化:O(81 \times H \times W \times C)
13×13 池化:O(169 \times H \times W \times C)
总计算量:O((25 + 81 + 169) \times H \times W \times C) = O(275 \times H \times W \times C)

SPPF 的计算量:

3 次 5×5 池化:O(3 \times 25 \times H \times W \times C) = O(75 \times H \times W \times C)

计算量小了不少。在 YOLOv5 的实现里,SPPF 与原先 SPP 的多尺度池化输出效果是等价的,但实现方式更高效。

YOLOv6

美团视觉智能部提出的。其实在 YOLOv5 之后,我对 Ultralytics 以外的团队突出的 YOLO 就不是很感兴趣了……

YOLOv6 也是针对工程部署做的优化,更关注:高FPS + 高精度 + 易于部署。也是第一次训练时支持 INT8 量化,其他模型都是 PTQ 训练后用 TensorRT 之类的做 INT8 量化。

Backbone

引入(chong)重参数化 Reparameterization 的思想,重参数化就是:把模型的参数重新组织一遍,让训练时的结构和推理时的结构不一样,但数学上等价。

Backbone 采用 RepVGG 风格设计。RepVGG 和 ResNet 一样都是图像分类网络。

训练阶段:典型的重参数化基本模块会包含多条并行分支,例如 3×3 卷积、1×1 卷积,以及在满足维度和步长条件时可用的恒等映射(Identity),并分别接入 BatchNorm。这种多分支结构能够提供更丰富的梯度流和特征表示能力。

推理阶段:通过结构重参数化技术,把这些分支的卷积和 BN 参数代数融合为一个等价的 3×3 卷积层。也就是说,训练时用多分支提升优化性,推理时再折叠成更适合部署的单分支结构。[4]

Neck

使用 Rep-PAN + RepBlock/CSPStackRep。小规模模型用 RepBlock,大规模模型用 CSPStackRep。所以看得出来和 YOLOv5 的区别,并不是按深度/宽度缩放网络,而是针对不同规模的网络做不同设计。

Rep-PAN 就是 PAN + 重参数化模块替换。

CSPStackRep 就是 CSP 分流结构 + RepBlock 重参数化。

BiC (Bi-directional Concatenation) 双向级联,是 Rep-PAN 中的一个关键结构。BiC是在 Top-Down 的过程中引入一个更层的特征,以保留更多定位信息,对小目标检测更友好。注意看下图,Backbone 的 C3(更高分辨率)和 C4(更低分辨率) 进入 BiC 模块 Concate 的同时,还有一个来自Neck P5(融合后的特征)的特征经过卷积再 Concate。

YOLOv6s-Architecture.webp

Decoupled Head

Anchor-Free 解耦头,把分类回归在一个输出分支里面拆开,让它们做各自的事。

Anchor 这玩意儿是犯什么罪了吗一会用一会不用的……我感觉一开始 v1 没用 Anchor 是因为没想到,v2 之后借鉴了 R-CNN 的 Anchor,效果有提升,就沿用了下去,但是后面随着 DL 的发展,发现 Anchor 缺点也多,毕竟是个超参数,调不好会对精度有影响,从 YOLOX 之后就引入了 Anchor-Free + 解耦头,证明无需 Anchor 也能达到 Anchor-Based 的效果。

YOLOv5之前 输出 [ x, y, w, h, obj, cls... ] 格式的结果,位置(回归)和语义(分类)信息是掺和在一起的。

Loss Function

使用解耦头和 Anchor-Free 带来的变化就是:不再单独设置传统的 objectness 损失,总体上由分类损失和边界框回归损失构成;其中回归部分又可以细分为 IoU 类损失,以及在更大规模模型中引入的 DFL。YOLOv6 在实验后发现,在 Anchor-Free 架构下,单独设计目标置信度损失并不会带来明显收益。

YOLOv6 的损失函数一般写作,其中 DLF 全称 Distribution Focal Loss:

{L_{total} = \lambda_{cls} L_{VFL} + \lambda_{iou} L_{IoU} + \lambda_{dfl} L_{DFL}}

分类损失采用 VFL (Varifocal Loss),原因是检测任务中正负样本极不均衡。如果使用 BCE,梯度容易被海量 Easy 负样本主导。Focal Loss 方法的核心是降低易分样本的权重,让模型更关注难分样本

为了同时监督定位质量 IoU,让正样本的监督信号不再是硬指示函数1,而是作为一个带定位质量信息的软标签,VFL 让分类分数同时反应类别正确性和定位质量好坏,目标是 classification score = classification conf × localization score。

VFL 对正样本:

L_{VFL}^+ = -q \left( q \log(p) + (1-q) \log(1-p) \right)

对负样本:

L_{VFL}^- = -\alpha p^\gamma \log(1-p)

其中

  • q∈[0,1] :软标签,质量分数
  • \alpha:负样本权重系数
  • \gamma:聚焦参数,类似 Focal Loss)
  • 负样本使用不对称加权,抑制低质量预测

边界框回归损失本质就是 IoU + DFL, IoU 部分在这里描述框的重叠质量,不再赘述。DFL 描述四条边界的精确位置差异,计算公式为:

L_{DFL} = -\left( (y_{i+1} - y) \log(P(y_i)) + (y - y_i) \log(P(y_{i+1})) \right)

YOLOv7

跟 YOLOv6 的同期作品,仍然是 Anchor-Based。并非又把 Anchor 又加了回来,YOLOv5 之后的版本更迭,也就那样吧……相邻版本间的关联并没有你想象得那么大。

BoF

我还注意到很有趣的一点,很多网站说 YOLOv7 使用了免费赠品包……这是机翻的锅,英文原文是 Bag-of-Freebies(BoF),这个概念来自对应名称的论文 [7]。BoF 并不只是狭义的数据增强 Pipeline,而是泛指一类只作用于训练阶段、能够提升效果但不增加推理开销的策略集合,因此才起了这么个名字。不过机翻成这个雷霆名字,实在是……还是机翻作者的锅。

除了负责输出的Lead Head,YOLOv7 在 BoF 基础上,为模型设计了 Auxiliary Head(辅助头),可以为 Backbone 提供更多监督信号。

训练结束会把它给删掉,推理速度不变。

但是有个问题,辅助头标签怎么分?因为 Lead Head 学得快,Auxiliary Head 学得慢。因此 YOLOv7 为辅助头分配了粗粒度的标签,让它学个大概就行。

Backbone

提出了 E-ELAN(Extended Efficient Layer Aggregation Network)扩展高效层聚合网络

那啥是不扩展的 ELAN 呢……可以把它理解成 E-ELAN 的前身思路:核心关注点都是控制梯度路径、提升特征聚合效率。后来的《Designing Network Design Strategies Through Gradient Path Analysis》更像是对这套设计思想的进一步整理和论文化表述。

E-ELAN 的核心思想是在保持原有梯度传播路径长度稳定的前提下,通过扩展并行计算分支(expand cardinality)、重组不同分支间的信息流(shuffle cardinality)以及最终特征融合(merge cardinality),增强网络的特征表达能力。其实现中通常结合组卷积以控制计算复杂度,从而在不显著增加参数量的情况下获得更丰富的特征组合能力。

写完 YOLOv12 之后决定再回来仔细研究一下 ELAN,因为理解 ELAN 的本质很重要。起初我认为 ELAN 类似 CBL、C3 一类组装出来的 Block,但 ELAN 本质上是一种拓扑结构或者说设计范式,然后在训练框架中被实例化成一个模块。

ELAN.webp

上图展示了从 VoVNet 到 CSPVoVNet,再到 ELAN 和 E-ELAN 的拓扑结构演进过程,这些都是 YOLOv4 的作者搞出来的。

这张图我觉得不需要关注卷积,只需要关注箭头走向。

VoVNet 采用连续计算后统一聚合的方式,将多层特征在阶段末端一次性融合,但随着网络加深,梯度传播路径容易变长。

CSPVoVNet 在此基础上引入 CSP 结构,将输入特征拆分为计算分支与直连分支,通过跨阶段连接减少重复梯度信息并降低计算开销。

ELAN 进一步调整特征聚合方式,通过保留不同深度层的输出并统一聚合,在增加最短梯度路径长度的前提下增强特征复用能力,从而提升网络训练稳定性与表达能力。

E-ELAN 则进一步扩展并行计算分支,并通过 shuffle 与 merge 重新组织分支间的信息流,在保持原有梯度传播路径稳定的同时提升特征组合能力和表示能力。

Model Scaling

之前模型深度和宽度都是分别缩放,YOLOv7 提出了联合缩放。

试验表明,参数更可控,精度提升明显。

YOLOv8

也是 Ultralytics 发布的,延续了 YOLOv5 提供多尺度模型的传统。

网络实际上延续 Backbone -> Neck -> Head 的三段式结构,但是官方代码中的 Neck 被融合到了 Head 中,因此代码上只有 Backbone 和Head 的部分。

YOLOv8-Architecture.webp

C2f

Backbone 仍然使用 CSP,Neck 也是 PAN,但是将两者的 C3 全部替换成了新的 C2f。Cross Stage Partial Bottleneck with 2 convolutions and Fast。

C2f 设计基于对 CSP 的改进和对之前 YOLOv7 E-ELAN 的借鉴,都是并行更多梯度流分支,获取更多层次的梯度信息。

C3 的 BottleNeck 是串联的,C2f 让特征经过每个 BottleNeck 之后,分出一路走 shortcut,最后都 Concat 到一起。此外 Conv 数量变成了2个。

C2f.webp
C3.webp

Anchor-Free

YOLOv8 采用 Anchor-Free 方式回归边界框,不过它并不是直接输出 x, y, w, h,而是预测 left, top, right, bottom,也就是当前特征点(更准确地说,是网格上的 anchor point)到 GT 四条边的距离;再由这四个距离解码出最终的框坐标。

这么多版本 YOLO 的发展,让人们意识到,Anchor 存在几个问题:

  • Anchor 的尺寸、长宽、数量都是需要调的超参数,哪怕经过聚类之后用 GA 来优化,在模型迁移和部署之后都会导致之前的 Anchor 不再适用
  • 正负样本不均衡的问题,一个 GT 框需要与大量 Anchor 框进行 IoU 计算,这个过程中存在大量无效负样本
  • Anchor 长宽比有限,对数据集中极端长宽比的物体,可能找不到合适的 Anchor 进行匹配

注意 Anchor-Free 仍然需要 NMS,因为 YOLOv8 仍然是 Dense Prediction。

每个特征点都会输出一个候选框,假设一个目标覆盖了 3 × 3 的特征区域,那可能回归出 9 个框,同一个目标得到多个框,推理时必须 NMS 去重。

YOLOv8 被归类为无锚点模型,是因为它不依赖预定义的 anchor box 尺寸和长宽比[10]。

但在训练解码过程中,它仍然会在特征图上生成一组规则的参考点(通常称为 anchor points),用这些点来表示预测位置、分配正负样本,并把预测的 ltrb 距离还原成边界框。

所以更准确地说,YOLOv8 是无锚框(anchor-box-free),但不是完全不使用参考点。

Label Assignment

YOLOv8 在训练阶段采用动态标签分配(Task-Aligned Assigner,TAL)。

相比早期 YOLO 的固定匹配方式:

  • YOLOv3:固定 Anchor 匹配
  • YOLOv5:Anchor + IoU 匹配
  • YOLOv8:动态选择正样本

核心思想是:

分类得分高且定位准确的预测点,更应该成为正样本。

训练时,对于每个 GT(真实框):

  1. 在多个尺度特征图上找到候选预测点;
  2. 计算候选点的分类得分与定位质量(IoU);
  3. 构造任务对齐分数:
score=s^\alpha\times IoU^\beta

其中:

  • s:分类预测分数
  • IoU:预测框与 GT 的重叠程度
  • \alpha,\beta:超参数

最终选取 Top-K 个得分最高的候选作为正样本参与训练。

相比固定 Anchor 匹配方式:

  • 不需要人为设计 Anchor;
  • 自动调整正样本数量;
  • 分类与定位目标更加一致;
  • 对密集目标、小目标更加友好。

Decoupled Head

依然是被分成了回归头和分类头,两个任务目标不同,共享特征容易造成梯度冲突。

Head 会输出 3 个不同尺度的特征图:

输出层 特征图尺寸 目标类型
P3 80×80 小目标
P4 40×40 中目标
P5 20×20 大目标

在使用 COCO 数据集的情况下,YOLOv8 的检测头实际上输出:B × 144 × 8400,其中:

  • B:Batch Size
  • 144:每个候选点输出维度
  • 8400:全部预测位置 = 80 × 80 + 40 × 40 + 20 × 20

这里的 144 由两部分组成,分类输出的 80 类别置信度和回归输出的 4 × 16 = 64,其中 4 是 tblr,16 是 RegMax。

tlrb 在实际展开的时候,结果是:

L : [p0,p1,...,p15]
T : [p0,p1,...,p15]
R : [p0,p1,...,p15]
B : [p0,p1,...,p15]

每组是 16 个概率,每组分别计算期望,这个期望的结果就是解码狂的实际位置。在 3 个不同尺度的特征图上乘当前层的 Stride,得到真实的像素尺寸。

解码后,最终输出维度是 B × 84 × 8400。

Loss Function

YOLOv8 的损失函数由三部分组成:

Loss=L_{box}+L_{cls}+L_{dfl}

**BBox Loss ** 使用 CIoU Loss:

L_{box}=1-CIoU

Classification Loss 使用 BCE,对每个类别独立计算分数。检测任务里这样做的好处是实现简单,也便于和动态标签分配配合;但在常规目标检测设定下,每个正样本通常仍只对应一个 GT 类别,而不是把检测任务本身改成多标签分类。

Distribution Focal Loss(DFL)预测距离的离散概率分布,假设 RegMax=16,左侧 l 预测结果为:[0.01,0.03,0.10,0.25, 0.40,0.15,0.04,0.02, ...],那距离最可能在 p3 和 p4 之间,不过不是取最大值而是算数学期望:

distance= \sum p_i\times i

假设最终结果是 3.33。那么 GT 咋监督呢?假设 GT 为 3.7,DFL不会强制标签变成 [0,0,0,1,0...],而是拆成 3 和 4 两个相邻整数并分配权重,并按距离分配权重。

  • 距离 3:w_l=4-3.7=0.3,也就是30%可能在3
  • 距离 4:w_r=3.7-3=0.7,也就是70%可能在7

得到监督目标:[0,0,0,0.3,0.7,0,...]

参考文章

[1] ultralytics/yolov5: Ultralytics YOLOv5 in PyTorch > ONNX > CoreML > TFLite

[2] A Comprehensive Review of YOLO Architectures in Computer Vision: From YOLOv1 to YOLOv8 and YOLO-NAS

[3] YOLOv11 改进 - 基础知识 为什么SPPF比SPP更快?深入解析YOLO中多尺度特征提取的效率优化与代码实现 - 魔改工程师 - 博客园

[4] 2101.03697v1

[5] [2209.02976] YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications

[6] [2207.02696] YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors

[7] [1902.04103] Bag of Freebies for Training Object Detection Neural Networks

[8] C3与C2f模块介绍与代码_c3模块-CSDN博客

[9] YOLOv8 原理和实现全解析 — MMYOLO 0.6.0 文档

[10] YOLOv8 目标检测中的 Anchor-Free 机制:图示 + 代码