☰
YOLOv8训练自定义数据集全流程:从环境配置到边缘部署避坑指南
2026/9/29 15:22:29 网站建设 项目流程

简介:YOLOv8 训练自定义数据集是环境搭建、标注整理、格式转换与参数调优相互衔接的完整过程,这份文档正是为正在跑通该流程的开发者准备,尤其适合刚接触目标检测、需要从零准备自有数据的初学者参考。文档先交代安装依赖工具的做法,再说明如何存放图片与标注文件、如何切分训练集和验证集、如何把标注数据转换成 YOLO 需要的文本格式;随后介绍选择预训练模型、启动训练命令以及任务类型、训练模式、模型配置、数据配置、迭代轮数、批大小等参数的作用,并对训练时可能出现的字体下载报错给出切实可行的处理方式。同时补充了断点续训的要点,说明怎样借助默认配置中的恢复开关接着训练,节省重复劳动。资源以单个 docx 笔记形式提供,压缩包约 2.18MB,已吸引 448 人学习;内容偏操作指导,便于对照步骤逐步实践,可作为跑通 YOLOv8 自定义数据集的随身参考。

1. YOLOv8训练自定义数据集:最花时间的不是显卡,是数据本身

做工业巡检那会儿我第一次跑通YOLOv8训练自定义数据集,以为难点在模型、在调参、在显存。真把流程走完才发现,百分之七十的时间花在标注、格式转换和目录整理上,模型训练本身反而是最省心的一段。这篇文章就把我踩过的坑和验证过的路径按顺序写出来,覆盖从环境配置、数据标注到训练参数、增量训练、边缘端部署的完整链路。适合两类人:一类是刚接触YOLOv8、手头有自己图片但不知道怎么喂给模型的初学者;另一类是有过一两次训练经历、想系统梳理参数和排错思路的从业者。标题里的核心词是YOLOv8、自定义数据集、训练,这里不聊论文,只聊能落地的方案。

2. 把数据变成YOLOv8能吃的格式:环境配置与标注目录

2.1 先跑通ultralytics环境:安装、验证GPU和下载预训练权重

YOLOv8的官方实现是ultralytics这个Python包,训练入口也就是一行yolo train命令。但很多人在环境这步就被卡住,最常见的问题是装错了包——装成了yolov8而不是ultralytics。另一个问题是用conda装完就忘了验证torch能不能调用CUDA,结果拿CPU硬跑,一个小数据集跑几十个epoch要熬一整晚。

推荐用Python 3.10以上的虚拟环境,pip安装:

pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

torch单独装而不是让ultralytics自动带,是因为自动依赖通常装CPU版。cu121对应CUDA 12.1,具体版本以你机器驱动为准,可以用nvidia-smi先看驱动支持的CUDA版本。装完跑一下验证:

import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))

如果is_available()返回False,先别急着重装,检查nvidia-smi驱动是否正常、虚拟环境是否真的是当前激活的那个。很多人是装完torch之后又新建了环境,旧环境里还是CPU版。

预训练权重在第一次训练时会自动下载,但网速不稳时经常下到一半失败。我的习惯是手动下载:从GitHub的ultralytics assets地址把yolov8n.pt、yolov8s.pt这些权重下载下来,放到项目根目录下的weights文件夹,然后在代码里显式指定路径。yolov8n.pt大约6MB,yolov8x.pt大约130MB,按需下载,没必要全下。下载地址对应关系可以看ultralytics官方文档里Assets一节,标题里的YOLOv8下载问题基本都能在那边找到答案。

2.2 标注工具与目录结构:从LabelImg到YOLO格式的完整搬运

YOLOv8训练自定义数据集,标注格式是TXT文件,每行包含类别id x_center y_center width height,坐标都是归一化到0到1之间的相对值。如果你用的是LabelImg或LabelMe,导出的可能是VOC的XML或JSON格式,需要做一步转换。

标注工具的选择上,我一般用LabelImg的YOLO模式直接输出TXT,省去转换。但LabelImg对高分辨率大图不太友好,缩放卡顿,这时候可以换用CVAT或X-AnyLabeling这类在线或半自动工具。半自动标注器配合一个预训练好的模型做预标注,效率能翻好几倍,这是做自定义数据集时最实用的省力手段。

目录结构按ultralytics约定来:

datasets/ ├── custom/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── custom.yaml

一个非常容易翻车的点:图片和标签文件名必须一模一样,只是扩展名不同。image_001.jpg对应image_001.txt,大小写都要一致。标注完检查文件是否配对,我用一个小脚本核对:

import os img_dir = 'datasets/custom/images/train' label_dir = 'datasets/custom/labels/train' img_names = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} label_names = {os.path.splitext(f)[0] for f in os.listdir(label_dir)} print('缺标签的图片:', img_names - label_names) print('缺图片的标签:', label_names - img_names)

如果没有配对,训练时ultralytics会直接跳过对应图片,但不会报错,只会在日志里出现警告,很容易被忽略。数据划分也很关键,不要在train和val里放同一场景的连续帧,否则验证集形同虚设。我一般按整个文件夹或视频片段为单位划分,宁可训练集少一点,也要保证验证集独立。

2.3 一份能跑的训练yaml:路径、类别和验证集划分

训练YOLOv8需要一份数据集配置文件,也就是yaml。注意这个yaml和部署时的配置文件不一样,训练时它告诉ultralytics三件事:数据在哪、有几类、类名是什么。

path: datasets/custom train: images/train val: images/val names: 0: defect 1: scratch 2: stain

path建议写相对路径,这样换机器不用改。train和val是相对于path的路径。有两个容易错的细节:第一,names必须从0开始连续编号,不能跳号,否则训练会报索引越界;第二,类别顺序一旦确定,整个训练和推理周期内不要再改,否则换模型权重时类别会错位。我经历过一次最血的教训是前后两批标注数据类别顺序不同,合并后训练出来的模型预测结果全乱,后来写了个脚本统一检查所有TXT里的类别id是否在合法范围内。

如果标注文件里出现类别id超出names长度的行,ultralytics会报AssertionError: class number out of range。这时候可以快速筛查标注文件:

awk -F' ' '$1 >= 3 {print FILENAME, $0}' datasets/custom/labels/train/*.txt

这个命令把第一列(类别id)大于等于3的行打印出来,检查是不是标注软件输出时多写了类别。这类脏数据在训练前清掉,比训练后看loss曲线再回头找要省事得多。

3. 训练参数怎么设:从yolov8n到yolov8l的选型与命令

3.1 模型大小选型:n/s/m/l/x不是越大越好

YOLOv8按网络深度和宽度分成n/s/m/l/x五个档位。很多人第一反应是直接上最大档yolov8x,但在自定义数据集场景下这往往不是最优解。选型的核心逻辑是数据量、算力和精度需求三者的平衡。

模型参数量(约)适合场景
yolov8n3.2M快速验证、边缘设备、数据量少于2000张
yolov8s11.2M中等数据量,精度与速度均衡
yolov8m25.9M数据量大且目标较小,精度优先
yolov8l/x43.7M+服务器端推理,足够多的数据支撑

在数据量只有几百张时,yolov8x很容易过拟合,验证集mAP反而不如yolov8s。我之前跑一个钢材表面缺陷数据集,总共900张图,用yolov8s训练到300轮,mAP50在0.89,换成yolov8x同样参数只有0.84,而且训练时间翻了三倍。选型不是越高越好,数据量不够时,大模型的拟合能力反而成为负担。

确定模型档位后,还要选预训练权重。yolov8s.pt是在COCO上预训练的,类别是80类通用的物体,虽然和你自己的类别不一样,但特征提取层已经学到很强的通用视觉表征。如果场景差异极大,比如医学影像或工业内窥镜图像,用COCO预训练权重可能不如从零开始在特定数据上训练效果好,但绝大多数场景下,迁移学习带来的收敛速度优势明显,我建议还是从预训练开始。

3.2 train.py必调参数:epochs、batch、imgsz和它们之间的联动

ultralytics的训练入口有两种写法:命令行yolo train ...,或者Python脚本里model.train(...)。参数含义完全一致,我个人习惯用Python脚本,因为可以把参数写进一个字典,方便多次实验对比。

from ultralytics import YOLO model = YOLO('weights/yolov8s.pt') results = model.train( data='datasets/custom/custom.yaml', epochs=300, batch=16, imgsz=640, patience=50, lr0=0.01, device=0, workers=8, seed=42, )

需要重点理解的是batch和imgsz这两个参数。imgsz是输入图片的边长,YOLOv8会自适应缩放,但训练时通常固定到一个值。batch受显存限制,两者乘积决定了单次前向的内存占用。显存不够时优先考虑减小batch而不是imgsz,因为imgsz影响目标尺寸的感知,降得太低小目标就学不到了。

patience是早停参数,训练很多轮后指标不再提升就自动停止。不要设太小,模型可能在某个指标上暂时震荡,过几轮又涨回来。我一般设epochs的一半左右,最多不超过100。lr0是初始学习率,默认0.01,在小数据集上可以调低到0.005,能减少后期loss震荡。

训练过程中最常见的判断依据是看train loss和val loss两条曲线。ultralytics在训练结束后会输出results.png,包含box loss、cls loss、dfl loss和precision、recall、mAP等曲线。里面最该关心的是mAP50和mAP50-95的曲线走向,如果mAP50-95一直上不去而mAP50已经很高,说明模型框定位置不够准,可以尝试增大imgsz或者检查标注框的精细度。

3.3 一条可复制的训练命令与loss曲线怎么看

如果你想用命令行直接跑,对应上面Python脚本的命令是这样的:

yolo train data=datasets/custom/custom.yaml model=weights/yolov8s.pt epochs=300 batch=16 imgsz=640 patience=50 lr0=0.01 device=0

训练日志会实时打印每一轮的box_loss、cls_loss、dfl_loss以及precision、recall、mAP50、mAP50-95。新手最容易盯着的loss下降,但loss下降不代表模型好用,还要看验证集指标。如果loss下降而mAP不涨,大概率是过拟合了,这时候去看训练集求出的loss和验证集loss的差距,差距越拉越大就是过拟合的信号。

画损失函数曲线图时,我不直接用ultralytics的输出,而是用results.csv重新画,可以更精细地对比不同实验组的曲线。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/detect/train/results.csv') plt.plot(df['epoch'], df['train/box_loss'], label='train box_loss') plt.plot(df['epoch'], df['val/box_loss'], label='val box_loss') plt.xlabel('epoch') plt.ylabel('loss') plt.legend() plt.show()

这样画的好处是可以随时放大看某个区间,比如训练中期是否出现loss反弹。ultralytics自带的是整张图画完,细节被压缩了。训练过程中每过一段时间去瞄一眼曲线,比等全部训完再看能节省大量时间,发现发散可以立刻Ctrl+C停掉调整参数,而不是等它跑完两三百轮才发现白跑了。

4. 训练进阶:增量训练、超参调整与部署前置

4.1 用预训练权重做增量训练:resume和pretrained的区别

增量训练是这个领域最容易踩坑的概念,很多人把resume和pretrained混成一件事。pretrained指的是用COCO上训好的权重作为初始化,这是迁移学习;resume是从某次中断的训练状态继续,加载的是当时的优化器状态、epoch数、学习率调度器位置。

# 从预训练权重开始增量训练 model = YOLO('weights/yolov8s.pt') model.train(data='datasets/custom/custom.yaml', epochs=100) # 从断点继续训练 model = YOLO('runs/detect/train/weights/last.pt') model.train(data='datasets/custom/custom.yaml', epochs=300, resume=True)

用last.pt续训时,epochs要写完整的目标epoch数而不是剩余epoch数,ultralytics会从checkpoint里记录的epoch继续往上加。这个细节坑过不少人,写了100结果实际跑了两遍。

增量训练实战中还有一个常见场景:先训少量数据快速验证流程,数据标注补齐后再用同样的数据路径续训。此时不要用resume=True,因为数据集已经变了,优化器状态中的类别分布信息已失效。正确做法是重新用预训练权重初始化,合并新旧数据后完整训练。

在我做过的项目里,增量训练价值最大的场景是边缘端设备上的快速适配。比如基座型号在服务器上用大模型训练,到了现场设备上用小模型做迁移,一个月后积累了一批新场景数据,用这批数据在预训练权重上增量训练,比从头重新标注训练快很多。

4.2 超参数调优:lr、optimizer和cosine衰减的取舍

YOLOv8默认用SGD优化器,学习率调度默认是cosine衰减。如果你的数据集比较小,SGD配合cosine衰减在后期容易学习率降得太快,导致模型停在次优解。我一般会在小数据集上换成optimizer='AdamW',它对学习率的敏感度低一些,收敛更稳定。

model.train( data='datasets/custom/custom.yaml', epochs=300, optimizer='AdamW', lr0=0.001, weight_decay=0.0005, cos_lr=True, warmup_epochs=3, )

关键参数说明:

  • lr0:初始学习率,AdamW推荐0.001到0.002,SGD推荐0.01到0.02
  • weight_decay:权重衰减,SGD用0.0005,AdamW可以小一个量级,0.00005到0.0005之间
  • warmup_epochs:前几个epoch用较小学习率热身,数据噪声大时调到5或更大
  • cos_lr:余弦退火,True时学习率从lr0按余弦曲线降到接近0

这些超参数之间是联动的。比如warmup_epochs太小,初始学习率又大,前几个epoch可能出现loss飙升然后才回落的震荡;weight_decay过大会让模型的权重被压得过于平滑,小目标检测能力下降。想系统调参,可以先跑一组默认参数看基线,再单独动一个变量,每次都留好记录。训练记录命名用project和name参数区分:

model.train(data='datasets/custom/custom.yaml', project='exp', name='adamw_lr0001')

这一步做好,后面整理实验对比时能省大量时间。

4.3 为rk3588等边缘设备导出模型:export的坑

训练完成后,很多人要部署到边缘设备。rk3588部署YOLOv8是当前热门场景,但直接拿训练出的.pt文件部署是不可行的,rk3588的NPU只认RKNN格式,流程是.pt → .onnx → .rknn。

from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') model.export(format='onnx', imgsz=640, opset=12)

导出的onnx文件可以用rknn-toolkit2转成rknn。这里有一个遇到就要绕道的坑:ONNX的opset版本。RKNN工具链对高opset支持不完整,建议固定opset=12。另一个坑是imgsz要与rknn转换时设置的一致,很多人在训练时用640,导出时用640,但转rknn时用了416,部署后检测框位置全偏,排查半天才发现是这个不一致。

还有一点要提前想好:导出onnx时是否需要把NMS留在模型里。YOLOv8默认是不带NMS的,输出是原始预测结果,需要在上位机或推理代码里做非极大值抑制。如果你不想在端侧写NMS逻辑,可以导出时加nms=True,但arknn工具体系下游的NMS芯片支持情况各异,我一般推荐端侧自己做NMS。这部分的详细代码逻辑各推理框架差异较大,按你使用的rknn示例代码来改就行,但记住一点:端侧推理输入的预处理要和训练时保持一致,包括归一化方式、通道顺序和letterbox的填充灰度值。YOLOv8的letterbox填充默认是灰色调,和COCO训练时一致,如果你在推理代码里改成黑色填充,精度会明显下降。

5. 避坑:YOLOv8训练自定义数据集最常见的5个翻车现场

5.1 训练几轮后loss变成nan:BN崩溃和梯度爆炸

现象是训练正常启动,前几个epoch loss正常下降,突然某轮开始loss变成nan,之后永远回不来。

原因最常见的是两个:一个是batch size太小,BN层的统计量不稳定,积累了异常值后梯度爆炸;另一个是学习率太大,参数更新一步跨太远,loss直接冲爆。还有一个隐蔽原因是标注数据里出现了空标签文件,模型在某个batch里完全没有目标,正样本缺失导致loss计算出现除零或对数无效值。

解决路径是先检查标注文件里有没有0字节的空TXT,有就删掉;然后降低lr0到原来的五分之一;最后把batch调大一倍或换用AdamW。如果还有问题,检查代码里有没有自己写的loss加权逻辑,自定义loss权重项数值过大也会导致梯度爆炸。

5.2 验证集mAP很低但训练集很高:数据划分泄漏

现象是训练集上mAP50接近0.95,但验证集只有0.5,两条曲线总差着一大截。

原因是数据划分出了问题,最常见的泄漏是同一个物体或同一段连续画面同时出现在train和val里。比如拍摄的视频每隔几帧抽一帧,没有按时间段切分,导致val里的画面和train高度相似,val指标虚高;还有一种反向泄漏是标注时把同一张图复制到两个集合但内容不同步。前一种情况会让verification失真,你看到的0.95是假的;后一种会让模型在验证集上的表现低于真实水平。

解决做法:划分数据前先对图片做去重,可以用md5sum算哈希删除重复文件;视频抽帧的按时间段切,前80%时间段的帧做train,后20%做val;场景分类的数据按场景文件夹划分,不要随机打散。

5.3 类别数量对但标签全空:标注文件坐标归一化问题

现象是训练能跑,但loss一直不降,查看训练集的图片发现没有对应的框被画出来。

原因大概率是标注文件的坐标格式不对。YOLO的TXT格式要求x_center y_center width height全部归一化,但很多人用目标检测软件导出的坐标是像素值,比如1537 452 180 260,没有归一化成0到1之间的小数。模型读进去后这些框远超图像尺寸,训练直接理解为无效目标。

解决做法是自己写一个归一化脚本,把像素坐标除以图片宽高:

import cv2 x_min, y_min, x_max, y_max = 1537, 452, 1717, 712 img = cv2.imread(image_path) h, w = img.shape[:2] x_center = (x_min + x_max) / 2 / w y_center = (y_min + y_max) / 2 / h box_w = (x_max - x_min) / w box_h = (y_max - y_min) / h line = f'0 {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}'

5.4 显存明明够却OOM:batch和imgsz的乘积关系

现象是nvidia-smi看显存还剩很多,但训练一启动就报CUDA out of memory。

原因是PyTorch的显存分配策略和nvidia-smi看到的进程占用不一致。nvidia-smi显示的剩余显存可能被其他程序缓存占着,或者训练本身有一次性峰值占用,比如数据加载阶段把整个batch的增强后图像同时放进显存。

解决做法是先把batch从16降到8试试,同时把workers调低到4以下。另外检查imgsz,batch=16, imgsz=640的显存占用大约是batch=8, imgsz=640的两倍,这个乘积关系在改参数时心里要有数。还有一种情况是显存碎片化严重,重启训练进程可以释放。

5.5 预训练权重下载失败:断点续传与手动放置

现象是第一次训练时卡在Downloading阶段很久,或者下载到一半就报断连错误,然后训练退出。

原因很明显,访问官方权重地址网络不稳定。yolov8n.pt这种小文件可能碰巧成功,yolov8l.pt和yolov8x.pt大文件大概率失败。

解决做法是提前手动下载到本地,然后指定模型路径。下载后放在项目某个固定目录,比如weights文件夹,之后所有训练脚本都从这个目录加载,不再依赖网络。

wget -c https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8s.pt -O weights/yolov8s.pt

-c参数支持断点续传,下载中断后重新执行会从断点继续,不用从头开始。这个命令里的版本号v8.2.0是示例,实际按官方release页的最新版本调整。也可以直接用model = YOLO('weights/yolov8s.pt'),这行代码会优先读本地文件,只有文件不存在时才会触发自动下载。

6. 验证与落地:用混淆矩阵和测试集视频给训练收尾

6.1 验证集指标怎么看:混淆矩阵比mAP更诚实

训练结束后,runs/detect/train/目录下会生成confusion_matrix.png。这张图比mAP更能反映模型到底错在哪。比如mAP50有0.85但混淆矩阵里某个类别的召回率很低,说明这个类别被其他类别大量吞噬,需要回去检查标注框是否太小或者类别重叠。

还有一张results.png里的F1_curve,它能告诉你置信度阈值设在多少时F1分数最高。实际部署时,不要默认用0.25的置信度,把F1_curve上最优的分数截出来,作为推理时的conf threshold,这个值通常能让误检和漏检的平衡更好。

6.2 推理脚本与部署:从pt到onnx再到rknn

验证完成后,把测试集视频完整跑一遍推理,这是我最喜欢做的收尾工作。一张一张看图片很难发现时序问题,但视频会暴露检测框抖动、目标丢失、误检闪烁等指标上看不出的问题。

from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model.predict( source='test_video.mp4', conf=0.35, iou=0.45, imgsz=640, save=True, )

conf按混淆矩阵的F1最优值调整,iou控制非极大值抑制的阈值,0.45左右在重叠目标多的场景还算合理。如果检测框在相邻帧跳动明显,可以把iou调高到0.55,减少同一目标在视频中被重复抑制的情况。

我把训练当成一次手艺活,参数抄别人的事无补,真要摸清自己的数据集,还是要亲手跑几组对比。这些年下来最深的教训是:不要相信任何一次训练的mAP50就下结论,至少要跑同一参数两次,因为随机种子和数据增强会让结果有一点抖动。现在每次训练完,我都会把数据集划分脚本、训练命令和参数记录一起放到runs目录的说明文件里,几个月后再回来翻,仍然能复现当时的结果。YOLOv8训练自定义数据集这条路,结构清晰、坑也明确,按流程走一遍再回头搭自己的数据流水线,会顺畅很多,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询