3. YOLOv8基础:模型结构解析与自定义检测模型训练

好,咱们进入正题。YOLOv8是Ultralytics团队在2023年初推出的版本,说实话,它比之前的v5和v7都要“整饬”得多——代码结构清晰,训练流程标准化,导出ONNX几乎零门槛。我个人习惯把v8看作一个“工业化”的起点,而不是一个纯粹的学术创新。

这一章我们做两件事:第一,把v8的骨架拆开看看,搞清楚它到底长什么样;第二,用你自己的数据集训一个检测模型。这两步走完,你才算真正“拥有”了一个能用的模型,后面导出ONNX和TensorRT加速才有意义。

3.1 YOLOv8模型结构:从输入到输出的完整链路

YOLOv8的结构可以分成三块:Backbone(骨干网络)、Neck(颈部)和Head(检测头)。说白了,就是“看图片→提特征→做预测”这么个流程。

核心变化:v8相比v5最大的改动是Head部分——从Anchor-Based(锚框机制)换成了Anchor-Free(无锚框)。这意味着你不再需要手动调锚框尺寸了,模型自己学。我在项目里遇到过因为锚框设置不合理导致小目标漏检的情况,换成v8之后这个问题自然消失了。

下面这张图是我画的v8结构简图,你看一眼就能明白整体脉络:

YOLOv8 模型结构简图 Backbone(骨干网络) Neck(颈部) Head(检测头) Conv + BN + SiLU C2f 模块 × 3 C2f 模块 × 6 C2f 模块 × 6 SPPF 上采样 + 拼接 C2f 模块 下采样 + 拼接 C2f 模块 Detect 小尺度 Detect 中尺度 Detect 大尺度 说明:C2f 是 v8 的核心模块,基于 CSPNet 思想,将梯度分流后再融合,参数量更少、梯度更丰富。 Detect 头输出三个尺度的特征图,分别负责小、中、大目标的检测。

3.2 关键模块详解:C2f 与 Detect Head

咱们挑两个最重要的模块细说一下。

3.2.1 C2f 模块

C2f 是 v8 对 v5 中 C3 模块的升级版。它把输入特征图分成两路:一路直接走 shortcut,另一路经过多个 Bottleneck 后再拼接。这样做的好处是——梯度在反向传播时有了“高速公路”,不会因为网络深就消失。

我的经验:如果你在训练时发现 loss 降不下去,或者模型收敛特别慢,可以检查一下 C2f 中的 Bottleneck 数量。v8n(nano版本)默认只有1个,v8l(large版本)有3个。我曾经在轻量级部署场景下把 v8n 的 Bottleneck 从1改成2,mAP 涨了1.2个点,推理速度只慢了3ms,很划算。

3.2.2 Detect Head(解耦头)

v8 的检测头是 Decoupled Head,也就是把分类和回归分开做。具体来说:

  • 分类分支:输出每个 anchor point 对应每个类别的概率,形状为 [batch, num_classes, h, w]。
  • 回归分支:输出每个 anchor point 到目标框四条边的距离,形状为 [batch, 4, h, w]。

为什么要分开?因为分类和回归关注的特征不一样——分类更关注“这是什么”,回归更关注“边界在哪”。共用参数反而会互相干扰。v5 用的是耦合头,v8 改成解耦头之后,收敛速度和精度都有提升。

注意:v8 的回归分支输出的是 dist(距离),而不是直接输出坐标。它用 Distribution Focal Loss(DFL)把坐标建模成离散分布,说白了就是把一个连续值拆成多个 bin 去预测。这样做对小目标的定位更精细,但也会增加一点计算量。导出 ONNX 时要注意 DFL 层的算子兼容性,我后面会专门讲。

3.3 训练一个自定义检测模型:完整流程

好,结构看完了,咱们动手训练一个。我假设你已经准备好了数据集——图片和对应的标注文件(YOLO格式的 .txt)。

3.3.1 数据集准备

YOLO格式的标注文件长这样:

class_id x_center y_center width height

注意,x_center、y_center、width、height 都是相对于图片宽高的归一化值(0~1)。举个例子,一张 640×640 的图片,目标框左上角在 (160, 160),右下角在 (480, 480),那么标注就是:

0 0.5 0.5 0.5 0.5

数据集目录结构建议这样组织:

datasets/
  my_dataset/
    images/
      train/
        img001.jpg
        img002.jpg
        ...
      val/
        img100.jpg
        ...
    labels/
      train/
        img001.txt
        img002.txt
        ...
      val/
        img100.txt
        ...

3.3.2 编写配置文件

在 Ultralytics 框架下,你需要一个 YAML 文件来描述数据集和模型。我一般叫它 my_dataset.yaml:

# 数据集路径
path: ./datasets/my_dataset
train: images/train
val: images/val

# 类别信息
nc: 3  # 类别数量
names: ['person', 'car', 'bicycle']  # 类别名称

关键点:nc 和 names 的顺序必须和标注文件中的 class_id 一一对应。我见过有人把 names 写错了顺序,结果训练出来的模型把“人”识别成“车”,排查了半天才发现是配置问题。

3.3.3 开始训练

训练代码非常简洁,Ultralytics 把一切都封装好了:

from ultralytics import YOLO

# 加载预训练模型(推荐)
model = YOLO('yolov8n.pt')

# 开始训练
results = model.train(
    data='my_dataset.yaml',
    epochs=100,
    imgsz=640,
    batch=16,
    device='cuda:0',
    workers=4,
    lr0=0.01,
    augment=True,
    project='runs/train',
    name='my_exp'
)

几个参数我解释一下:

  • epochs:我一般从100开始,观察 loss 曲线,如果50轮就收敛了就提前停止。
  • imgsz:训练尺寸。640是默认值,如果你的目标很小(比如人脸),可以试试800或1024。
  • batch:根据显存来调。RTX 3090 可以跑 batch=32,笔记本 3060 建议 8~16。
  • lr0:初始学习率。0.01 是官方推荐值,但如果你用很小的 batch(比如4),建议降到 0.001,否则 loss 容易震荡。

3.3.4 训练过程中的关键指标

训练时终端会打印一堆指标,我挑几个重点说:

指标 含义 正常范围
box_loss 边界框回归损失 训练结束时 < 1.5
cls_loss 分类损失 训练结束时 < 1.0
dfl_loss 分布聚焦损失 训练结束时 < 1.2
mAP50 IoU阈值0.5时的平均精度 简单数据集 > 0.9,复杂数据集 > 0.7
mAP50-95 IoU从0.5到0.95的平均精度 通常比mAP50低0.1~0.2

避坑指南:我曾经在训练一个工业缺陷检测模型时,发现 box_loss 一直在降,但 mAP 纹丝不动。后来排查发现是标注文件里有些框的 width 或 height 写成了0(目标太小被误标)。YOLO 对 width=0 的框会直接忽略,但 loss 计算时又把它算进去了,导致 loss 虚低。所以训练前一定要做数据校验——检查标注文件里有没有0值。

3.3.5 训练完成后得到什么

训练结束后,在 runs/train/my_exp/ 目录下你会看到:

  • weights/best.pt:验证集上 mAP 最高的权重,导出 ONNX 时用这个。
  • weights/last.pt:最后一轮的权重,如果训练中断了可以接着训。
  • results.csv:所有 epoch 的指标记录,可以用 Excel 或 pandas 画 loss 曲线。
  • confusion_matrix.png:混淆矩阵,帮你分析哪些类别容易混淆。

3.4 验证模型效果

训练完了,跑个验证看看效果:

model = YOLO('runs/train/my_exp/weights/best.pt')
results = model.val(data='my_dataset.yaml')
print(results.box.map)  # 打印 mAP50-95

如果想看单张图片的预测结果:

results = model('test.jpg', save=True, conf=0.5)
# 结果会保存在 runs/detect/predict/ 下

我的习惯:验证时我会把 conf 设成 0.25 而不是 0.5,这样能看到更多低置信度的预测框,方便判断模型是“漏检”还是“误检”。如果低置信度框里有很多正确的目标,说明模型其实学到了,只是置信度偏低,后面可以通过调整 NMS 阈值或增加训练轮数来改善。

3.5 本章小结

这一章我们做了三件事:

  1. 拆解了 YOLOv8 的 Backbone、Neck、Head 结构,重点讲了 C2f 和 Decoupled Head。
  2. 用 Ultralytics 框架训练了一个自定义检测模型,从数据准备到训练参数都过了一遍。
  3. 学会了验证模型效果,以及如何从训练日志中发现问题。