1. 为什么我最终选择了ultralytics这套方案
第一次接触YOLOv8是在一个工业质检的小项目上,当时的需求很明确:在产线边缘设备上跑一个目标检测模型,识别零件表面的缺陷。团队之前用的是YOLOv5,代码维护得比较乱,训练脚本、推理脚本、导出脚本散落在不同人手里,每次换个人接手都要重新捋一遍。后来听说ultralytics把YOLOv8的整套流程都统一到了一个Python包里,抱着试试看的心态切过去,结果整个项目的开发效率提升了一大截。
ultralytics这个包最核心的价值在于:它把数据准备、模型训练、验证测试、导出部署这条链路全部收进了一套统一的API里。你不需要再去clone各种fork版本的仓库,也不需要手动改配置文件里的路径和类别数。一条yolo命令或者几行Python代码就能完成从训练到推理的全过程。对于我这种经常需要在不同项目之间切换的人来说,这种一致性带来的效率提升是实打实的。
这篇文章主要面向两类人:一是刚接触YOLOv8,想快速把整套流程跑通的新手;二是已经用过YOLOv5或其他版本,想迁移到ultralytics这套体系的老手。我会从环境搭建开始,一步步讲到训练自己的数据集、测试模型效果、以及最终部署到不同平台上的注意事项。中间会穿插一些我实际踩过的坑和总结出来的经验,希望能帮你少走弯路。
注意:本文基于ultralytics 8.x版本撰写,不同版本之间API可能有细微差异,建议以官方文档为准。
2. 环境搭建:CPU版本和GPU版本的选择与配置
2.1 先搞清楚你的硬件条件再动手
很多人一上来就照着教程装CUDA、装cuDNN,结果发现自己电脑根本没有NVIDIA显卡,白折腾半天。所以在动手之前,先确认一下你的硬件情况:
- 有NVIDIA显卡:可以装GPU版本,训练速度比CPU快几十倍甚至上百倍
- 只有CPU:装CPU版本也能跑,但训练大模型会非常慢,适合学习和推理测试
- AMD显卡:ultralytics官方对AMD显卡的支持有限,通常需要走ROCm或者DirectML的路线,配置起来比较折腾
我自己的主力机器是一台带RTX 3060的台式机,但有时候在外面只能用笔记本的CPU跑推理。所以两种环境我都配过,下面分别说。
2.2 CPU版本的安装:比你想的要简单
CPU版本的安装其实非常简单,因为不需要处理CUDA驱动那些乱七八糟的东西。以Ubuntu 20.04为例,基本步骤如下:
# 创建虚拟环境(强烈建议,不要装在系统Python里) python3 -m venv yolov8_env source yolov8_env/bin/activate # 安装ultralytics pip install ultralytics # 验证安装 yolo version如果你用的是Windows,步骤基本一样,只是激活虚拟环境的命令换成yolov8_env\Scripts\activate。
装完之后可以跑一个简单的推理测试:
from ultralytics import YOLO # 加载预训练模型(第一次运行会自动下载) model = YOLO("yolov8n.pt") # 对一张图片做推理 results = model("test.jpg") results[0].show()如果能看到检测结果,说明CPU环境已经跑通了。CPU版本做推理是完全可以接受的,yolov8n这种小模型在普通CPU上单张图片推理大概几百毫秒,做demo或者轻量级应用足够了。
2.3 GPU版本的安装:CUDA版本匹配是关键
GPU版本的安装核心就一件事:PyTorch的CUDA版本要和你的显卡驱动兼容。ultralytics本身不直接依赖CUDA,它依赖的是PyTorch,所以关键是装对PyTorch。
我的建议是先去PyTorch官网查一下当前推荐的安装命令,不要盲目复制网上的教程。大致流程是:
# 先装PyTorch(以CUDA 11.8为例) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装ultralytics pip install ultralytics # 验证GPU是否可用 python -c "import torch; print(torch.cuda.is_available())"如果输出True,说明GPU环境配置成功。如果输出False,大概率是PyTorch版本和驱动不匹配,需要重新安装对应版本的PyTorch。
这里有个我踩过的坑:不要先装ultralytics再装PyTorch。因为pip安装ultralytics时会自动拉取一个CPU版本的PyTorch作为依赖,你再装GPU版本的时候可能会出现版本冲突。正确的顺序是先装好GPU版本的PyTorch,再装ultralytics。
2.4 验证环境是否完整可用
环境装好之后,建议跑一个完整的训练测试来验证。可以用官方提供的coco8数据集(只有8张图片,训练几秒钟就能跑完):
yolo detect train data=coco8.yaml model=yolov8n.pt epochs=10 imgsz=640如果这条命令能正常跑完并输出训练日志,说明你的环境从训练到验证的整条链路都是通的。这一步很重要,因为后面训练自己的数据集时如果出问题,你可以确定不是环境的问题。
3. 数据集准备:从标注到YOLO格式转换的完整流程
3.1 YOLO格式到底是什么样的
YOLOv8训练用的标注格式和YOLOv5是一样的,每张图片对应一个同名的.txt文件,每行表示一个目标,格式如下:
class_id center_x center_y width height其中坐标都是归一化到0-1之间的相对值。举个例子,如果一张640x480的图片上有一个类别为0的目标,中心点在(320, 240),宽高为(100, 80),那么对应的标注行就是:
0 0.5 0.5 0.15625 0.16667计算方式很简单:center_x = 320/640 = 0.5,width = 100/640 = 0.15625,以此类推。
3.2 用labelme标注然后转换
我平时标注数据用得最多的是labelme,因为它支持多边形标注,对于不规则形状的目标比较友好。但labelme默认输出的是JSON格式,需要转换成YOLO格式。
转换脚本的核心逻辑是:读取JSON中的多边形点,计算外接矩形的中心和宽高,然后归一化。网上有很多现成的转换脚本,但我在实际使用中发现几个需要注意的点:
- 类别映射要固定:每次转换时类别的顺序必须一致,否则训练出来的模型类别会乱
- 图片和标注要对应:转换后要检查是否有图片没有对应的标注文件,或者标注文件为空
- 坐标越界处理:有时候标注会超出图片边界,需要做clip处理
我一般会写一个简单的校验脚本,统计一下每个类别的样本数量,以及标注框的尺寸分布。如果某个类别的样本数特别少(比如少于50个),训练时就需要考虑做数据增强了。
3.3 数据集的目录结构
ultralytics要求的数据集目录结构是这样的:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml是数据集配置文件,内容大致如下:
path: /path/to/dataset train: images/train val: images/val names: 0: person 1: car 2: dog这里有个容易忽略的细节:train和val的图片不能有重叠。我见过有人直接把所有图片都放在train里,然后val也用同一批图片,这样训练出来的指标看起来很好,但实际部署时效果很差,因为模型已经"见过"这些图片了。
3.4 数据增强策略的取舍
ultralytics内置了一套默认的数据增强策略,包括随机翻转、缩放、色彩抖动等。对于大多数场景,默认配置就够用了。但有几个参数值得根据你的具体任务调整:
- mosaic:把4张图片拼成一张,对小目标检测效果很好,但如果你的目标都很大,开启mosaic反而可能引入噪声
- mixup:把两张图片按透明度混合,对分类任务有帮助,检测任务上要谨慎使用
- copy_paste:复制粘贴增强,适合实例分割任务
我的经验是:先用默认配置跑一版baseline,然后根据验证集上的表现来决定要不要调整增强策略。不要一上来就调一堆参数,那样你根本不知道哪个参数起了作用。
4. 训练自己的数据集:参数配置与过程监控
4.1 从预训练权重开始还是从零开始
这个问题我被问过很多次。结论很明确:除非你有超过10万张标注图片,否则一律从预训练权重开始。YOLOv8在COCO数据集上预训练的权重已经学到了非常通用的特征,你的数据集哪怕和COCO的类别完全不同,这些底层特征也是可以复用的。
from ultralytics import YOLO # 从预训练权重开始 model = YOLO("yolov8n.pt") # 开始训练 model.train( data="data.yaml", epochs=100, imgsz=640, batch=16, device=0 # 使用GPU 0,CPU则设为'cpu' )模型大小的选择上,n/s/m/l/x从小到大。我的建议是:先用yolov8n跑通流程,确认数据和配置没问题后,再换更大的模型。因为大模型训练一次可能要几个小时甚至几天,如果数据有问题,这些时间就白费了。
4.2 关键参数的含义和设置建议
下面这张表是我总结的常用参数及其设置建议:
| 参数 | 含义 | 建议值 | 说明 |
|---|---|---|---|
| epochs | 训练轮数 | 100-300 | 小数据集可以适当增加 |
| imgsz | 输入图片尺寸 | 640 | 根据目标大小调整 |
| batch | 批次大小 | 16 | 受显存限制,显存不够就调小 |
| lr0 | 初始学习率 | 0.01 | 默认值通常够用 |
| patience | 早停耐心值 | 50 | 验证指标不提升多少轮后停止 |
| workers | 数据加载线程数 | 8 | Windows上建议设为0 |
关于imgsz的选择,有个经验法则:如果你的目标在图片中占比很小,可以适当增大imgsz。比如遥感图像中的车辆检测,可能要用1280甚至更大的尺寸。但增大imgsz会显著增加显存占用和训练时间,需要权衡。
4.3 训练过程中的监控指标
训练开始后,ultralytics会在runs/detect/train/目录下生成一系列文件,其中最重要的是:
- results.csv:每个epoch的各项指标,可以用pandas读取后画曲线
- weights/best.pt:验证集上表现最好的权重
- weights/last.pt:最后一个epoch的权重
- confusion_matrix.png:混淆矩阵,直观看出哪些类别容易混淆
我习惯在训练过程中用tensorboard实时监控:
tensorboard --logdir runs/detect/train重点关注的指标是mAP50-95和mAP50。如果mAP50很高但mAP50-95很低,说明模型能找到目标但定位不够精确,可能需要调整回归损失或者增加定位相关的数据增强。
4.4 训练不收敛时的排查思路
训练过程中最常见的问题就是loss不下降或者mAP上不去。我一般按以下顺序排查:
- 检查数据标注是否正确:用可视化工具把标注框画到图片上,肉眼检查一遍
- 检查类别是否平衡:如果某个类别样本极少,模型会倾向于忽略它
- 检查学习率是否合适:学习率太大会震荡,太小会收敛慢
- 检查batch size是否太小:太小的batch会导致梯度估计不准
- 尝试冻结 backbone 训练几轮:有时候先冻结主干网络训练几轮,再解冻微调效果更好
还有一个容易被忽略的点:验证集的指标和实际部署效果可能有差距。因为验证集的数据分布和训练集是一样的,但实际场景中光照、角度、遮挡情况可能完全不同。所以如果有条件,最好留一部分"真正独立"的测试数据,不要参与训练和验证。
5. 模型测试与评估:不只看mAP
5.1 用验证集跑一遍完整评估
训练完成后,第一件事是在验证集上跑一遍评估:
yolo detect val model=runs/detect/train/weights/best.pt data=data.yaml这会输出每个类别的precision、recall、mAP50、mAP50-95等指标。但我要强调的是:这些数字只是参考,不能完全代表模型的实际表现。
5.2 混淆矩阵告诉你的信息比mAP更多
混淆矩阵是我最喜欢看的评估结果之一。它能直观地告诉你:
- 哪些类别容易被误检成其他类别
- 哪些类别的漏检率特别高
- 背景被误检为目标的情况有多严重
比如我之前做一个交通标志检测的项目,混淆矩阵显示"限速60"和"限速80"经常互相混淆。这就提示我需要针对这两个类别增加更多训练数据,或者在预处理时做更精细的裁剪。
5.3 实际场景测试:用视频和摄像头验证
验证集上的指标再好,也要用实际场景的数据测一遍。我通常会用三种方式测试:
- 图片批量测试:把实际场景中采集的图片批量跑一遍,人工检查结果
- 视频测试:用
yolo detect predict source=video.mp4跑一段视频,观察时序上的稳定性 - 摄像头实时测试:用
source=0调用摄像头,看推理速度是否满足要求
视频测试特别重要,因为单张图片上检测不到的目标,在视频中可能因为帧间连续性而被检测到。反之,单张图片上检测到的误检,在视频中可能会闪烁,影响体验。
5.4 推理速度的测量方法
推理速度是部署时最关心的指标之一。测量时要注意:
- 预热:前几次推理通常比较慢,因为要初始化CUDA上下文等,测量时要跳过前几次
- batch size:batch=1和batch=16的单张推理时间完全不同
- 预处理和后处理时间:不要只测模型前向传播的时间,要把图片resize、NMS等环节都算进去
import time from ultralytics import YOLO model = YOLO("best.pt") # 预热 for _ in range(10): model("test.jpg") # 正式测量 start = time.time() for _ in range(100): model("test.jpg") end = time.time() print(f"平均推理时间: {(end - start) / 100 * 1000:.2f} ms")6. 部署实战:从服务器到边缘设备的落地经验
6.1 导出ONNX格式:跨平台部署的第一步
ultralytics支持导出多种格式,最通用的是ONNX:
yolo export model=best.pt format=onnx opset=12 simplify=Truesimplify=True会调用onnx-simplifier对模型图做优化,通常能减小模型体积并提升推理速度。但要注意,simplify有时候会引入一些兼容性问题,如果导出后的模型推理结果不对,可以试试关掉这个选项。
导出ONNX后,可以用onnxruntime做推理:
import onnxruntime as ort import numpy as np session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name # 预处理图片 img = preprocess("test.jpg") # 需要自己实现resize和归一化 outputs = session.run(None, {input_name: img})这里有个坑:ultralytics导出的ONNX模型输出格式和YOLOv5不完全一样。YOLOv8的输出是(1, 84, 8400)这样的形状,其中84 = 4个坐标 + 80个类别分数。后处理时需要自己实现NMS,不能直接套用YOLOv5的后处理代码。
6.2 部署到RK3588等边缘设备
RK3588这类边缘AI芯片通常需要把模型转换成芯片专用的格式,比如RKNN。转换流程大致是:
- 先把PyTorch模型导出为ONNX
- 用RKNN-Toolkit2把ONNX转换成RKNN格式
- 在板端用RKNN Runtime加载推理
这个过程有几个需要注意的点:
- 量化:RKNN支持INT8量化,能大幅提升推理速度,但精度会有所下降。建议先用FP16跑通,再尝试INT8量化
- 输入尺寸:边缘设备通常对输入尺寸有限制,需要根据芯片的NPU能力调整
- 算子支持:不是所有ONNX算子都被RKNN支持,转换时如果报错,可能需要修改模型结构
我在RK3588上部署YOLOv8n的经验是:FP16精度下,640x640输入的推理时间大约在30-50ms左右,基本能满足实时性要求。如果换成INT8量化,可以降到20ms左右,但mAP可能会下降2-3个百分点。
6.3 用TensorRT加速推理
如果部署环境是NVIDIA GPU,TensorRT是最佳选择:
yolo export model=best.pt format=engine half=True device=0导出的engine文件是平台相关的,不能跨设备使用。half=True表示使用FP16精度,能在几乎不损失精度的情况下提升速度。
TensorRT的推理速度通常比PyTorch原生推理快2-5倍,具体取决于模型大小和GPU型号。但TensorRT的版本兼容性比较严格,engine文件在不同版本的TensorRT之间可能不兼容,部署时要注意版本匹配。
6.4 部署后的性能监控
模型部署上线后,监控是必不可少的。我一般会关注以下几个指标:
- 推理延迟:P50、P95、P99分位数,不能只看平均值
- 吞吐量:每秒能处理多少张图片
- GPU/CPU利用率:是否成为瓶颈
- 内存占用:是否有内存泄漏
如果发现推理延迟突然增大,可能是输入图片尺寸变了,或者有异常图片导致后处理时间暴增。这时候需要在预处理阶段加一些保护逻辑,比如限制输入图片的最大尺寸。
7. 几个我踩过的坑和对应的解决方案
7.1 训练时loss变成NaN
这个问题我遇到过两次。第一次是因为学习率设得太大,第二次是因为数据集中有标注框的宽高为0。排查方法:
- 检查数据集中是否有无效标注(宽或高为0)
- 降低初始学习率,比如从0.01降到0.001
- 开启梯度裁剪(ultralytics默认有,但可以调整阈值)
7.2 验证集mAP很高但实际部署效果差
这是最让人头疼的问题。根本原因通常是训练数据和实际场景的数据分布不一致。解决方案:
- 尽量用实际场景的数据做训练和验证
- 如果实际数据太少,可以用数据增强模拟实际场景的变化
- 在部署前用一批"真实测试数据"做最终验证
7.3 导出ONNX后推理结果和PyTorch不一致
这个问题通常出现在使用了自定义算子或者动态shape的情况下。排查步骤:
- 先用固定输入尺寸导出,排除动态shape的影响
- 对比PyTorch和ONNX在同一个输入上的输出,看差异有多大
- 如果差异很大,检查是否有算子不被ONNX支持
7.4 多GPU训练时的坑
ultralytics支持多GPU训练,只需要设置device=0,1即可。但有几个注意事项:
- batch size是总的batch size,会平均分配到每张GPU上
- 多GPU训练时数据加载可能成为瓶颈,可以适当增加workers
- 保存的权重是单GPU格式,可以直接用于单GPU推理
8. 一些提升效果的小技巧
8.1 用更大的模型做知识蒸馏
如果你有一个大模型(比如yolov8x)训练得很好,可以用它来蒸馏一个小模型(比如yolov8n)。ultralytics本身不直接支持蒸馏,但可以通过修改损失函数来实现。核心思想是让小模型的输出分布去逼近大模型的输出分布。
8.2 针对小目标的优化
小目标检测是YOLO系列的弱项。如果我的任务中小目标很多,我会做以下几件事:
- 增大输入尺寸(比如从640增加到1280)
- 使用P2层特征(ultralytics支持通过修改配置文件增加更浅层的特征)
- 增加小目标的训练样本
- 使用SAHI(Slicing Aided Hyper Inference)做切片推理
8.3 类别不平衡的处理
如果某些类别的样本特别少,可以:
- 在数据加载时对少样本类别做过采样
- 使用focal loss替代默认的BCE loss
- 调整类别权重
ultralytics的配置文件中可以设置cls参数来调整分类损失的权重,但更细粒度的类别权重需要自己修改代码。
8.4 模型剪枝和量化
如果部署环境资源受限,可以考虑对模型做剪枝和量化。剪枝的思路是去掉对输出影响不大的通道,量化是把FP32权重转换成INT8。这两个操作都会损失一定精度,需要根据实际需求权衡。
我的一般建议是:先尝试INT8量化,如果精度下降太多再考虑剪枝。因为量化通常能带来2-4倍的速度提升,而剪枝的效果更依赖于具体模型和任务。
9. 关于版本管理和可复现性
最后聊一个容易被忽视但非常重要的话题:版本管理和实验可复现性。
ultralytics更新很频繁,不同版本之间的API和行为可能有变化。我建议在项目开始时就把ultralytics的版本固定下来,写在requirements.txt里。同时记录以下信息:
- ultralytics版本号
- PyTorch版本号
- CUDA版本号
- 训练时的完整命令或配置文件
- 数据集的版本(最好用git-lfs或dvc管理)
这样当几个月后需要复现结果或者排查问题时,你能快速回到当时的环境。我吃过这个亏:一个项目过了半年要重新训练,结果发现ultralytics已经更新了好几个大版本,原来的训练脚本跑不通了,花了大半天才把环境恢复。
另外,训练时的随机种子也建议固定下来,虽然完全复现深度学习实验很难,但至少能减少一些随机性带来的差异。ultralytics默认会设置随机种子,但如果你修改了数据加载逻辑,可能需要自己确保种子的一致性。