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'] # 类别名称
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。
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')
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是非常划算的。
4.6 总结
好,这一章的内容就到这里。我们来回顾一下:
- 训练YOLOv8n模型,用Ultralytics的API,注意数据集的格式和参数调优
- 导出ONNX模型,作为中间格式
- 导出FP16模型,压缩模型大小,提升推理速度
- 验证精度损失,确保FP16模型的精度在可接受范围内
下一章,我们会把导出的FP16模型部署到嵌入式设备上,真正跑起来。到时候你会看到,模型在开发板上跑出实时检测的效果,那种成就感,嗯,很爽。