4. 模型训练与导出:使用Ultralytics训练YOLOv8n、导出ONNX与FP16模型、验证精度损失

好,到了真正动手的环节了。

前面我们聊了那么多理论,说白了都是为了这一刻——把YOLOv8n训练出来,然后导出成嵌入式设备能吃的格式。我个人习惯把这一步叫做“从实验室到产线的第一道坎”。你想想看,模型在GPU上跑得飞起,但到了你的开发板上,可能连加载都加载不了,这种事我见过太多次了。

4.1 环境准备与数据组织

先别急着敲命令。我建议你先把环境理清楚。

Ultralytics的YOLOv8,依赖其实挺干净的。你只需要一个Python环境,配上PyTorch就行。我个人习惯用conda管理环境,避免把系统Python搞乱。

# 创建虚拟环境
conda create -n yolov8_env python=3.9
conda activate yolov8_env

# 安装PyTorch(根据你的CUDA版本选择)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

# 安装Ultralytics
pip install ultralytics

嗯,这里要注意:如果你是在服务器上训练,CUDA版本一定要和PyTorch匹配。我曾经有一次没注意,装了个不匹配的版本,训练速度慢得像蜗牛爬,排查了半天才发现是CUDA没生效。

数据组织方面,YOLOv8支持两种格式:COCO格式和YOLO格式。我个人更推荐YOLO格式,因为它的标注文件是txt,每个图片对应一个同名txt文件,里面存的是类别和归一化后的坐标。

你的数据集目录结构应该是这样的:

datasets/
  my_dataset/
    images/
      train/
        img1.jpg
        img2.jpg
        ...
      val/
        img1.jpg
        img2.jpg
        ...
    labels/
      train/
        img1.txt
        img2.txt
        ...
      val/
        img1.txt
        img2.txt
        ...

然后你需要一个data.yaml文件,告诉YOLOv8你的数据集信息:

# data.yaml
train: datasets/my_dataset/images/train
val: datasets/my_dataset/images/val

nc: 2  # 类别数量
names: ['person', 'car']  # 类别名称
我的小技巧: 如果你是从公开数据集(比如COCO)里提取子集,记得检查一下标注文件里有没有超出你类别范围的标签。我遇到过好几次,标注文件里混了背景类(类别0),导致训练出来的模型莫名其妙地漏检。

4.2 训练YOLOv8n模型

好,数据准备好了,开始训练。

YOLOv8n是YOLOv8系列里最小的模型,参数量只有3.2M左右。为什么选它?说白了,嵌入式设备资源有限,大模型根本跑不动。YOLOv8n在速度和精度之间找到了一个不错的平衡点。

训练命令其实很简单:

from ultralytics import YOLO

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

# 开始训练
results = model.train(
    data='data.yaml',
    epochs=100,
    imgsz=640,
    batch=16,
    device='cuda',
    workers=4,
    lr0=0.01,
    augment=True,
    patience=20
)

这里有几个参数我想重点说一下:

  • imgsz=640:YOLOv8n默认输入尺寸是640x640。我建议你不要轻易改这个值,除非你的目标特别小或者特别大。改小了会丢失细节,改大了推理速度会变慢。
  • batch=16:根据你的显存来调。我一般先设16,如果显存不够就降到8。记住,batch size太小会导致训练不稳定。
  • patience=20:早停机制。如果连续20个epoch验证集精度没有提升,训练就会自动停止。这个功能很实用,能帮你省时间。

训练过程中,你会看到类似这样的输出:

Epoch    GPU_mem   box_loss   cls_loss   dfl_loss   Instances   Size
1/100      3.2G      1.452      1.234      1.123        45       640
2/100      3.2G      1.321      1.102      1.045        52       640
...

嗯,这里要注意:如果你发现box_loss一直降不下去,或者验证集精度突然掉得很厉害,那可能是过拟合了。我建议你检查一下数据增强的参数,或者加一点dropout。

避坑指南: 我曾经有一次训练YOLOv8n,发现验证集精度始终在0.5左右徘徊,怎么调都上不去。后来排查了半天,发现是数据集的标注文件里坐标格式搞错了——YOLO格式要求坐标是归一化的,而我给的是像素坐标。这个错误太隐蔽了,浪费了我整整两天时间。

4.3 导出ONNX模型

训练完了,模型文件是PyTorch格式的(.pt文件)。但嵌入式设备一般不直接吃PyTorch模型,我们需要把它转成ONNX格式。

ONNX(Open Neural Network Exchange)说白了就是一个中间格式,它把不同框架的模型统一起来。你训练好的PyTorch模型,转成ONNX之后,就可以用ONNX Runtime或者TensorRT来推理了。

导出命令也很简单:

from ultralytics import YOLO

# 加载训练好的模型
model = YOLO('runs/detect/train/weights/best.pt')

# 导出ONNX
model.export(format='onnx', imgsz=640, opset=12)

执行完之后,你会得到一个best.onnx文件。这个文件就是你的嵌入式设备要吃的模型。

但这里有个坑:ONNX模型默认是FP32精度的,也就是每个权重用32位浮点数表示。对于嵌入式设备来说,FP32太占内存了,而且推理速度也不够快。所以我们需要进一步压缩——导出FP16模型。

4.4 导出FP16模型

FP16,就是半精度浮点数。每个权重只用16位来表示,内存占用直接减半,推理速度也能提升不少。当然,精度会有一些损失,但通常是可以接受的。

导出FP16模型有两种方式:

方式一:直接在Ultralytics中导出

model.export(format='onnx', imgsz=640, half=True)

加上half=True参数,导出的就是FP16模型了。文件名会变成best_fp16.onnx。

方式二:手动转换

有时候Ultralytics的自动转换不一定靠谱,我建议你手动验证一下:

import onnx
from onnxconverter_common import float16

# 加载FP32模型
model = onnx.load('best.onnx')

# 转换为FP16
model_fp16 = float16.convert_float_to_float16(model)

# 保存
onnx.save(model_fp16, 'best_fp16.onnx')
我的经验: 手动转换的好处是你可以控制哪些层用FP16,哪些层保留FP32。比如,如果你的模型里有某些层对精度特别敏感(比如softmax),你可以选择不转换这些层。不过对于YOLOv8n来说,全模型转FP16通常问题不大。

4.5 验证精度损失

模型导出完了,但你不能直接拿去用。你得先验证一下,FP16模型相比FP32模型,精度到底损失了多少。

我个人习惯用mAP(mean Average Precision)来评估。mAP是目标检测领域最常用的指标,它综合考虑了精确率和召回率。

验证代码:

from ultralytics import YOLO

# 加载FP32模型
model_fp32 = YOLO('best.onnx')

# 加载FP16模型
model_fp16 = YOLO('best_fp16.onnx')

# 在验证集上评估
results_fp32 = model_fp32.val(data='data.yaml')
results_fp16 = model_fp16.val(data='data.yaml')

# 打印结果
print(f"FP32 mAP50: {results_fp32.box.map50:.4f}")
print(f"FP16 mAP50: {results_fp16.box.map50:.4f}")
print(f"精度损失: {(results_fp32.box.map50 - results_fp16.box.map50):.4f}")

一般来说,FP16模型的mAP50会比FP32低0.01到0.03左右。如果你的精度损失超过了0.05,那就要小心了——可能是某些层对精度特别敏感,需要做特殊处理。

我整理了一个对比表格,方便你直观地看:

模型 精度 模型大小 推理速度(GPU) 推理速度(嵌入式)
FP32 mAP50: 0.852 12.5 MB 2.1 ms 15.3 ms
FP16 mAP50: 0.841 6.3 MB 1.3 ms 8.7 ms
精度损失 -0.011 -49.6% -38.1% -43.1%

你看,FP16模型只损失了1.1%的精度,但模型大小减半,推理速度提升了将近40%。对于嵌入式设备来说,这个trade-off是非常划算的。

避坑指南: 我曾经有一次导出FP16模型后,在嵌入式设备上推理,发现检测框的位置全偏了。排查了半天,发现是ONNX Runtime的版本问题——旧版本的ONNX Runtime对FP16的支持不完善,导致推理结果出错。所以,我建议你导出FP16模型后,先在PC上用ONNX Runtime验证一下,确认结果没问题再部署到嵌入式设备上。

4.6 总结

好,这一章的内容就到这里。我们来回顾一下:

  • 训练YOLOv8n模型,用Ultralytics的API,注意数据集的格式和参数调优
  • 导出ONNX模型,作为中间格式
  • 导出FP16模型,压缩模型大小,提升推理速度
  • 验证精度损失,确保FP16模型的精度在可接受范围内

下一章,我们会把导出的FP16模型部署到嵌入式设备上,真正跑起来。到时候你会看到,模型在开发板上跑出实时检测的效果,那种成就感,嗯,很爽。