搞了半天环境,训练完一看mAP才0.2,这种滋味我懂。最近好几个朋友私信问YOLOv11怎么训练自己的数据集,问题五花八门,从“标注完怎么生成txt”到“loss不降怎么办”都有。这玩意儿说难不难,说简单也真有不少坑,尤其是第一次跑数据集的人,很容易卡在数据格式和参数理解上。这篇文章我就把从零到一的全流程捋一遍,包括数据准备、配置文件、训练参数、模型评估和部署,全是实操过的东西,照着做基本能跑通。
这篇文章适合谁看?刚入门深度学习目标检测的,或者已经在用YOLOv5/v8但想换新版本试试的,再就是手头有自己图像数据、想做成检测模型的工程师。我会把每一步为什么这么做讲清楚,顺便把常见报错和我的排查思路写出来,省得你一遍遍去论坛翻帖。
1. 训练前必须想清楚的三件事
很多朋友拿到代码就直接开跑,结果不是显存爆了就是训练完效果莫名其妙。其实在动手之前,有几个决定性问题得先理清,这比调参重要得多。
1.1 环境选型:为什么建议用Python虚拟环境跑
YOLOv11是用PyTorch实现的,跑训练的第一步是装好环境。我强烈建议你创建一个干净的conda虚拟环境来跑,别直接装在系统Python里。原因很简单——深度学习框架之间的依赖冲突很常见,今天装个OpenCV,明天pip一个某个库,很可能把PyTorch的版本动掉,到时候报错你会怀疑人生。
虚拟环境创建其实就几条命令的事,我以Python 3.10为例:
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics安装的时候注意一下,ultralytics这个包会自动拉取依赖,包括torch和torchvision。如果你想用GPU训练,建议提前装好CUDA版的PyTorch,直接装CPU版本会跑得非常慢,尤其是数据集稍微大一点,一个epoch可能就要几十分钟。检查GPU环境是否可用,可以用下面这段:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU only")如果输出True,恭喜你,GPU环境OK。如果是CPU only,也不是不能跑,但小数据集几百张图还能忍,上千张图我建议还是想办法弄个显卡,哪怕云端租卡也行,不然调参效率太低。
1.2 任务定向:先确定你要做的检测任务类型
训练之前先明确你的任务属于哪种类型,因为YOLOv11的模型命名跟任务类型是绑定的。常见的有这三种:
- 目标检测:框出物体并给出类别,对应yolo11n.pt、yolo11s.pt这类权重
- 实例分割:在检测基础上还要抠出物体轮廓,对应yolo11n-seg.pt
- 姿态估计:检测人并输出关键点,对应yolo11n-pose.pt
我见过有人拿实例分割的模型去跑检测数据,结果训练报错或者输出不对。你先搞清楚自己需要的是哪一种,再去下载对应的预训练权重。对于大多数入门项目来说,目标检测就够用了,下面也围绕检测来讲。
1.3 数据现状:看看你手里有多少图、什么格式
数据这块没有捷径,但可以先做一个快速评估。建议打开你的图片文件夹,统计一下总张数和类别的分布。目标检测一般要求每个类别至少几百个实例,如果某个类别只有几十个框,训练出来的模型基本只能“死记硬背”那几十个样本,泛化能力很差。
另外一个容易忽略的点是图片格式。YOLO系列的训练代码一般能自动读取jpg、png、bmp等常见格式,但要注意部分标注工具对exif方向信息处理不好,导致标注框和图片实际内容对不上。这种情况在手机拍摄的照片里比较常见,建议预处理时统一用脚本把图片转成jpg并处理掉exif旋转信息,然后再标注,后面能省很多麻烦。
2. 数据集的准备与标注
数据是模型的上限,这句话真不是鸡汤。同样的模型结构,数据质量不同,效果天差地别。我就见过同一个装置检测项目,有人用手机随手拍几百张训练,mAP 0.5勉强到0.6;后来规规矩矩用固定机位、不同光照和时间段拍了2000张,mAP直接到0.91。所以这块值得花时间好好搞。
2.1 图像采集:一张一张抠图还是用视频抽帧
采集图像通常有两条路。一是直接用相机/手机拍,适合物体种类少、背景相对固定的场景。二是录一段视频,然后抽帧成图片,适合需要覆盖大量不同角度、不同距离的情况。我个人更推荐后者,因为视频抽帧效率高,而且能保证帧与帧之间有连续性,模型能学到目标在运动中的形态变化。
抽帧可以用OpenCV写个简单脚本,几行代码搞定:
import cv2 import os video_path = "data.mp4" output_dir = "frames" os.makedirs(output_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_count = 0 extract_every = int(fps) # 每秒抽一帧 while True: ret, frame = cap.read() if not ret: break if frame_count % extract_every == 0: cv2.imwrite(f"{output_dir}/frame_{frame_count}.jpg", frame) frame_count += 1 cap.release()抽完帧之后,建议人工过一遍,把模糊的、重复度太高的、目标被严重遮挡的帧删掉。这个动作别省,因为你删掉的是“坏样本”,留下的是“有效信息”。
2.2 标注工具选择与标注规范
常用的标注工具有labelImg、labelme、CVAT等。如果只做检测,我个人最推荐labelImg,操作简单直接,界面一眼就能看懂,打开图片,画框,填类别,保存即可。
标注的一个关键点是框的规范。边界框要尽量贴合目标的实际轮廓,不要留太多背景,也不要切掉物体的一部分。别小看这个细节,框得太松,模型会学到很多背景信息;框得太紧,又可能丢掉物体的边缘特征。
还有一个很重要的规范——标注框的最小尺寸。如果某个目标只有十几个像素,模型几乎无法学到有效特征,这种标注建议直接放弃,或者换一个更近距离的场景去采集数据。
2.3 标注格式转换:从XML到TXT
labelImg默认保存成Pascal VOC格式的XML文件,YOLO需要的却是YOLO格式的TXT文件。两者的区别挺大:XML写的是坐标的绝对值,YOLO格式写的是归一化后的中心点坐标和宽高,而且类别编号从0开始。
格式不转换就会训练报错,找半天发现数据没喂进去。转换工具可以用ultralytics官方数据转换脚本,也可以自己写一个。我提供一个简单的思路:
import os import xml.etree.ElementTree as ET def convert_xml_to_yolo(xml_file, class_list, output_dir): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text cls_id = class_list.index(name) box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(os.path.join(output_dir, xml_file.split('.')[0] + ".txt"), "w") as f: f.write("\n".join(lines))转换完之后记得抽几张图,把TXT里的坐标反画到图上检查一遍,确认框和物体对齐了再往下走。这个“可视化检查”非常重要,我每次做完转换都会跑一段脚本在图上画框确认,能及时发现坐标原点不一致等坑。
2.4 数据集划分:train、val、test一个都不能少
划分数据集时要保证训练集和验证集不要有同一段视频抽出来的连续帧,这是因为连续帧的背景和目标姿态都高度相似,模型在验证集上表现好,不代表在真实场景中表现好,容易造成“假高分”。划分配比方面,常见的是8:1:1,即80%训练、10%验证、10%测试。数据量大可以不单独拆测试集,但一定要留验证集,因为训练过程中需要靠val集来监控过拟合。
划分的时候注意随机种子,保证每次运行结果一致,方便复现。如果你用手动拖动的办法分文件,很容易造成类别分布不均衡,建议还是用脚本按类别比例采样划分。
3. 写配置文件与核心代码解析
YOLOv11和V8一脉相承,用ultralytics的Python接口或者CLI都能训练。这里我建议刚开始别直接复制网上一长串代码,先把配置文件和几个核心参数搞明白,后面跑起来会顺畅很多。
3.1 数据配置YAML文件写法
在项目中新建一个data.yaml文件,里面写三块内容:数据集路径、类别列表、类别个数。
path: ./datasets/my_project train: images/train val: images/val test: images/test names: 0: person 1: car 2: helmet nc: 3这个文件有几个容易踩的坑。path路径建议写相对路径,写绝对路径的话,换机器跑就得改半天。train和val后面跟的是相对于path的图片目录,YOLO会自动去找同名的txt标注文件——它的默认规则是图片放在images/train,标注放在labels/train,两个目录必须在同一个父目录下,且目录名必须完全对应。
num_classes这个参数在data.yaml里就是nc,一定不要填错,填错了训练会直接崩,因为模型预测头的通道数和你给的标签数对不上。
3.2 训练入口:model参数怎么选
YOLOv11提供了不同尺寸的预训练模型,常见的有n(nano)、s(small)、m(medium)、l(large)、x(extra)。模型越大,精度越高,但对显存的要求也越高,训练时间越长。
我第一次跑数据集时上来就用yolo11l.pt,6G显存的显卡直接OOM(显存溢出)。后来换了yolo11s.pt,不仅跑起来了,精度反而比预期好很多,因为small模型在小数据集上不容易过拟合。建议根据你的显存和数据量来选:数据集几百张就选n或s,几千张且显存够大再考虑m以上。
一个小细节是,预训练权重下载需要联网。如果你在离线环境,需要先把权重文件传到服务器上,然后指定本地路径。别到时候卡在这一步半天不知道怎么回事。
3.3 train.py核心参数详解
训练脚本的核心部分如下:
from ultralytics import YOLO model = YOLO("yolo11s.pt") model.train( data="data.yaml", epochs=200, imgsz=640, batch=16, lr0=0.01, optimizer="SGD", patience=20, device=0, workers=4, )关键参数我来逐个解释,方便你知道调什么、怎么调:
- epochs:总训练轮数。不要盲目设很大,配合早停机制用。我一般设200,如果100轮之前模型已经在验证集上停滞很久,后面的轮次基本都是浪费算力。
- imgsz:输入图像尺寸。YOLO官方默认640,这个尺寸在小目标较多的场景可以调到800或960,但训练速度和显存占用也会显著上升。先按640跑通,后面再优化。
- batch:批大小。显存不够就调小,但batch太小会导致训练不稳定,我一般最小不低于8。
- lr0:初始学习率。预训练权重继续训练的话,0.01是比较保守和稳妥的起步值。如果你用的是纯随机初始化的权重,建议适当调大到0.02左右。
- optimizer:优化器。SGD在目标检测里依然稳,Adam收敛快但容易早停到次优解,小数据集上我强烈推荐SGD。
- patience:早停耐心轮数。验证集指标连续多少轮不提升就提前终止训练。设20代表最多允许20轮没有进步就停,省时省电。
3.4 断点续训与结果保存
训练过程中如果不小心中断了,ultralytics会保留last.pt权重。恢复训练的方式很简单:
model = YOLO("runs/detect/train/weights/last.pt") model.train(data="data.yaml", epochs=200, resume=True)注意resume训练时epochs要填完整的剩余轮数,不然模型会直接从0开始算。训练完成之后,runs/detect/train/目录下会保存best.pt和last.pt,best.pt就是验证集上表现最好的权重,后面验证和部署都用它。
另外,我建议把训练输出目录里保存的args.yaml保留好,它记录了本次训练的所有参数,对你后续复盘调参很有帮助。
4. 训练过程的监控、调参与踩坑
训练不是把命令扔进去等结果就完事了。你要学会看训练日志,观察曲线走向,才能判断模型是在正常收敛还是在瞎学。
4.1 训练日志和指标曲线怎么看
训练过程中会打印两类loss:box_loss、cls_loss等训练集上的损失,以及验证集上的精确率和召回率。建议盯着验证集上的mAP50和mAP50-95这两个指标。
- mAP50:IoU阈值取0.5时的平均精度,是“宽松标准”下的检测效果
- mAP50-95:IoU从0.5到0.95每间隔0.05算一次平均精度再取均值,标准更严格,更能反映模型定位和分类的综合能力
如果你的训练轮数很多,mAP50-95却在某个位置上波动不上涨,可能说明模型已经到达当前数据量和模型大小下的“知识上限”,继续加大epochs意义不大,还不如去增加数据或者做数据增强。
4.2 过拟合与欠拟合的典型表现
过拟合的典型特征是训练集loss一路往下掉,验证集loss掉到一定程度开始反弹,mAP50-95停滞不前甚至下降。遇到这种情况,优先考虑加数据增强、调大正则化系数(weight_decay)、或者换小一点的模型(从l换成s)。
欠拟合则表现为训练集loss和验证集loss都偏高,模型在训练集上学得还不够充分。这时候不要马上盲目加数据,先检查学习率是否太高导致loss震荡,或者训练轮数太少,再者就是标注质量可能有硬伤——比如某类别的框没标全,模型学来学去都学不到完整特征。
4.3 常见坑:类别不平衡与框贴边
类别不平衡在目标检测里很常见,比如工地场景里安全帽样本有几千个,而反光背心只有几十个。这种情况下模型往往偏向学大样本类别。处理思路有几种:最简单的是你不改变数据,而是给数据增强中加一些针对少数类别的复制粘贴,把小样本物体在合法位置多贴几次;或者在损失权重上做调整。
另一个容易被忽视的坑是标注框贴边。如果物体的标注框刚好顶到图片边缘,模型在预测时会倾向于预测靠近边缘的框,而图片边缘经常存在相机畸变、截断等问题。建议在标注时对贴边物体单独处理,要么删除,要么保证框不超出图片边界太多。
4.4 排查技巧:loss为nan或者不下降
最让人心态炸裂的问题是训练一开始loss就是nan。根据我踩过的坑,原因基本集中在两类:学习率太大,或者数据里有空标注的图片。空标注的图片就是没有任何目标框的图片,会让模型输出恒定为0的预测,反向传播时梯度异常。排查方法很简单,写段脚本遍历所有txt文件,统计一下里面有多少个空的,直接把空标注文件删掉并连带移除对应图片。
还有一个情况是loss不下降,卡在一个固定值附近。这时候先别急着调参,打开一张训练图片确认数据是否正常读取了。很多人卡在这是因为图片和标注没对应上,模型学到的其实是随机噪声,loss自然降不下去。
5. 模型评估与导出部署
训练完不是终点,你得知道模型到底能不能投入实际使用。评估指标是给你看的一张“成绩单”,部署则是把模型变成真正能用的东西。
5.1 正确理解混淆矩阵与PR曲线
训练结果目录下会有混淆矩阵和PR曲线的图。混淆矩阵看的是模型把哪个类别误判成了哪个类别,比如把背景误判成安全帽,或者把安全帽误判成头。每行每列的名称要仔细看,如果某个类别的召回率很低,说明它漏检严重,需要补充它不同姿态下的训练样本。
PR曲线的横轴是召回率,纵轴是精确率,曲线越往右上走越好。AP值就是PR曲线下的面积,mAP是所有类别AP的均值。这里有个容易忽视的点:单看mAP高不代表一切,如果你要检测的目标属于安全关键场景,可能更看重召回率,这时候可以适当下调置信度阈值来提高检出率,哪怕精确率下降一些也可以接受。
5.2 可视化验证:让模型“说人话”
只看数字指标是不够的,我每次训练完都会用best.pt跑几张真实场景的测试图,把检测结果可视化出来看。重点关注三类错误:漏检、误检、重复框。漏检说明模型对某些姿态、尺度、光照不鲁棒;误检说明模型学到了错误纹理,可能是标注框太松,把背景学进去了;重复框通常可以用NMS(非极大值抑制)参数调整解决,但重复很多的话可能是模型输出置信度分布有问题。
跑验证结果可以用下面这段代码:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("test_images/", save=True, conf=0.25, iou=0.5)conf是置信度阈值,iou是NMS的IoU阈值。测试时conf不要设太高,可以先从0.25开始,再根据可视化结果微调。
5.3 导出为ONNX模型并部署
部署环节最常用的方式是导出成ONNX格式,这样可以在多种推理框架上运行,比如CPU上的ONNXRuntime、移动端的NCNN。导出命令一行就行:
model.export(format="onnx", imgsz=640, half=True)导出成功后,ONNX模型就不依赖PyTorch环境了,可以直接用ONNXRuntime来做推理。实际部署时注意两点:一是输入图像的预处理方式必须跟训练时一致,即resize到640x640并且做归一化;二是模型输出的后处理逻辑要自己写,包括解码框坐标、过滤低置信度框、做NMS。你不要觉得麻烦,这是部署最常出问题的地方,80%的“模型跑起来结果不对”都出在前处理和后处理不匹配上。
还有一种更轻量的部署方式是直接用PyTorch的模型文件加torchscript进行推理,环境里要有torch。真实落地的时候,我一般会优先用ONNX,因为后续迁移到服务端还是边缘设备都方便。
5.4 部署后仍需维护:数据闭环
训练完部署上线了,不代表事情就结束了。真实场景里的数据和训练集往往有差异,比如角度变了、光照变了、出现了新类型的干扰物。建议在部署后定期收集一些效果不好的样本,加回到数据集里重新迭代。
我自己常用的做法是:每周跑一批现场采集的图片到模型上,把置信度低的检测结果和漏检样本截下来,人工筛选后送到标注工具中。这个“采集-标注-训练-部署”的闭环跑上两个月,模型在真实场景里的表现会肉眼可见地提升。
6. 一些实操中的私人经验
训练自己的数据集这件事,我摸索中也走过不少弯路。先把几个最关键的提醒放在这里。
第一个是用预训练权重尽量别改模型结构。我刚开始学的时候,总想改点什么体现“自定义”,于是顺手把模型里的某个模块裁剪掉了,结果训练出来的精度惨不忍睹。预训练权重和模型结构是强绑定的,你改了结构,预训练权重就得丢掉重练,而你的数据量往往又不足以支撑从零训练出一个基础模型。所以没到熟练阶段,老老实实用官方权重做迁移学习,性价比最高。
第二个是显存不够时别只想到换小模型。其实调低imgsz、调小batch都能解决,但batch太小训练不稳定,imgsz降低过度会丢细节。我的做法是先把batch降到最小可接受值,然后试着把imgsz从640降到512,如果还爆就换小一档的模型。另外梯度累积也可以考虑,相当于变相增大batch size。
第三个是训练时长要心里有数。GPU好一些,几百张图、200轮训练,一两个小时能完事;数据集上千张还用CPU的话,一个epoch可能就跑几分钟,整体下来要等很久。我建议先在50张图的小数据子集上把代码流程跑通,再上全量数据,这样能快速验证数据和配置文件是否正确,也方便排查报错。
最后再多说一句,数据标注这事别图省事外包一扔就完事。你至少要把最终数据集里20%的标注自己快检一遍,亲眼看看框的位置和类别是否合理。机器可以帮你提速,但质量把关必须自己来,因为模型学到的完全取决于你给它的标注。我到现在还记得有次标注人员把帽子类别统一标成了头,模型训练完惨不忍睹,浪费了两天时间才排查出来。
这个内容后续可以怎么扩展呢?等基础设施都跑通之后,你可以试试YOLOv11的实例分割、OBB旋转框检测,或者做模型蒸馏,把小模型在边缘设备上跑起来。不过那是后话了,先把眼前的数据集训练跑出一个满意的模型,再谈之后的优化。