“装了两天,最后发现是 PyTorch 和 CUDA 版本对不上”——这句话我在实验室、在群里、在评论区至少见过几十次。YOLOv5 这个东西本身的代码结构非常干净,train.py一行命令就能把训练跑起来,真正劝退新手的从来都不是模型原理,而是前面那段看起来无聊透顶的 YOLOv5 环境配置。显卡驱动、CUDA、cuDNN、Anaconda、PyTorch、Python 版本,六个东西互相咬着,错一个就报一串看不懂的英文。这篇东西就是写给完全没碰过深度学习环境的人的:从一台干净的电脑开始,到把自己的数据集训练出best.pt,中间每一步为什么这么做、哪一步最容易出事、出事了怎么自己排查,我按顺序全写清楚。不管你最后是想做水果识别、车牌号识别,还是单纯想跑通一次训练流程交作业,跟着走一遍就行。有显卡的用显卡,没显卡的租一台云 GPU 也能跑。
1. YOLOv5 环境配置为什么总在第一步就卡死人
1.1 六个版本号互相咬合,这才是真正的难点
先把这个事情的复杂度讲清楚,你才知道为什么别人给的命令你复制粘贴就是不行。
YOLOv5 跑起来需要一条完整的链路:显卡硬件 → 显卡驱动 → CUDA → PyTorch → Python → YOLOv5 代码依赖。这条链上的每个环节都有版本要求,而且不是随便搭配都行。显卡驱动版本决定了你能用的最高 CUDA 版本;CUDA 版本决定了你能装哪个版本的 PyTorch;PyTorch 版本又要求 Python 在某个区间内;YOLOv5 的requirements.txt里还有一堆包(numpy、opencv-python、pillow、matplotlib、tqdm、pyyaml、requests、scipy、thop 等)对 numpy 和 Python 版本有隐性要求。
新手最常见的错误是“看到哪个教程就照抄”,结果 A 教程写的是torch==1.7.1+cu110,B 教程写的是torch==2.0.1+cu118,你电脑上的驱动是两年前的老版本,装完torch.cuda.is_available()返回False,然后你就懵了。
这里有个必须纠正的认知误区:pip 安装的 PyTorch 是自带 CUDA runtime 的,你不需要单独去 NVIDIA 官网下载安装 CUDA Toolkit,也不需要手动配 cuDNN。很多教程让你去装 CUDA Toolkit 11.3、再把 cuDNN 的文件拷到 CUDA 目录里,那套流程是给编译源码或者用 TensorFlow 老版本的人准备的。YOLOv5 + pip 版 PyTorch 这条路线里,多装 CUDA Toolkit 反而容易造成版本冲突,因为系统里出现了两套 CUDA 库,动态链接的时候加载错了就报DLL load failed或者undefined symbol。
所以你真正需要准备的只有两样东西:一张 NVIDIA 显卡 + 一个足够新的显卡驱动。其余的交给 conda 和 pip。
1.2 一张表把版本搭配说清楚
先确认你的显卡驱动支持到哪个 CUDA 版本。打开命令行敲:
nvidia-smi输出右上角有一行CUDA Version: 12.1之类的字样。注意,这个数字表示你的驱动“最高能支持”CUDA 12.1,不是说你已经装了 CUDA 12.1,很多人在这里理解错,然后满世界找“我什么时候装的 CUDA”。
拿到这个数字后,对照下面的表格选 PyTorch:
| 驱动显示的 CUDA Version | 推荐 PyTorch 版本 | pip 安装后缀 | 备注 |
|---|---|---|---|
| 11.0 及以上 | torch 1.8 ~ 1.10 | cu111 | 老驱动能用的最稳组合 |
| 11.4 及以上 | torch 1.11 / 1.12 | cu113 | YOLOv5 v6.0 时代的主流 |
| 11.7 及以上 | torch 1.13 / 2.0 | cu117 | 兼容性最广的一档 |
| 11.8 及以上 | torch 2.0 / 2.1 | cu118 | 目前装得最多的组合 |
| 12.1 及以上 | torch 2.1+ | cu121 | 新卡新驱动直接上 |
“及以上”这三个字很关键:驱动是向下兼容的。你的驱动支持 CUDA 12.1,那装 cu118 的 PyTorch 完全没问题;反过来驱动只支持 11.4,你硬装 cu118 的版本,torch.cuda.is_available()就会给你一个冷冷的False。
如果你实在懒得算,用最土但最有效的办法:打开 PyTorch 官网的 Get Started 页面,在选项里选你的系统、pip、Python 版本、CUDA 版本,它会把完整命令生成给你。这个页面上的组合都是官方验证过的,出错概率最低。
1.3 为什么一定要用 conda 建虚拟环境
我见过太多人所有东西都往 base 环境里塞,半年后 numpy 版本打架,PyTorch 莫名其妙导入失败,只能重装 Anaconda。这种事完全没必要经历一次。
虚拟环境的价值在于隔离。你可以在同一台电脑上建三个环境:一个跑 YOLOv5,一个跑 YOLOv8,一个跑别的框架,各自锁死自己的依赖版本,互不干扰。哪天某个环境搞崩了,conda remove -n 环境名 --all删掉重建,五分钟的事,不会连累其他项目。
建环境的时候有个小细节:环境名别用中文,别带空格。我用yolov5这个名字,简单直接。另外,创建环境时把 Python 版本一起定死,不要事后在环境里升级 Python,那是给自己找麻烦。
2. 从一台干净电脑到 detect.py 跑通的完整路径
2.1 创建 conda 虚拟环境并激活
假设你已经装好了 Anaconda 或者更轻量的 Miniconda,打开 Anaconda Prompt(Windows)或者终端(Linux/macOS),执行:
conda create -n yolov5 python=3.9 -y conda activate yolov5Python 版本我建议3.9或者3.10。选 3.9 的理由是它对 PyTorch 1.8 到 2.1 全系列都友好,覆盖面最广;选 3.10 也没问题,但再往上(3.11、3.12)就有些依赖包会装不上或者编译失败,新手别去踩。至于 3.7、3.8,YOLOv5 官方现在要求Python>=3.8.0,3.8 还能用但很多新包已经不支持了,也不推荐。
激活成功后命令行前面会出现(yolov5)的前缀。这个前缀很重要,它告诉你接下来所有操作都在这个环境里,装包不会污染全局。如果你后面发现命令行没有这个前缀,说明环境掉了,重新conda activate yolov5一下。
提醒:Windows 上关闭终端再打开,环境是需要重新激活的。这不是 bug,是 conda 的默认行为。
2.2 装 PyTorch:这条命令决定后面顺不顺
以上面表格里的 cu118 + torch 2.0 为例,在激活的yolov5环境里执行:
pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118装完之后必须验证,别急着往下走:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))"期望看到三行输出:版本号、True、你的显卡型号。如果第二行是False,先别慌,按这个顺序排查:
- 你的电脑是不是根本没有 NVIDIA 显卡,或者只有集显。没有 N 卡的话,只能跑 CPU,训练会慢到让你怀疑人生,这时候建议租云 GPU。
- 装的包是不是 CPU 版本。用
pip list | grep torch(Linux/macOS)或pip list | findstr torch(Windows)看看版本号后缀,2.0.1+cpu就是 CPU 版,得卸了重装。 - 显卡驱动太老。回
nvidia-smi看看版本,和 PyTorch 要求的 CUDA 版本对不上的话,先升级驱动。
这三步走完基本能解决 95% 的False问题。
2.3 下载 YOLOv5 源码与安装依赖
有了 Git 直接克隆,没有 Git 就去 GitHub 上下载 zip 包解压:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt关于版本选择有个实用建议:YOLOv5 的 v6.0、v6.2、v7.0 这三个 tag 是社区里资料最多、教程最多的。如果你后面要查问题,用最新的 main 分支有时会遇到“别人的教程里根本没有这个文件”的尴尬。想切换版本:
git checkout v7.0requirements.txt里的包装完之后,我强烈建议再手动确认一下 numpy 和 opencv:
pip install numpy==1.23.5 pip install opencv-python==4.6.0.66这两个包是版本冲突的高发区。numpy 2.x 出来之后,不少老版本的 YOLOv5 代码会出现np.int之类的兼容性问题,锁一个 1.23.x 能避开一大片坑。opencv 选 4.6 左右的版本,兼容性最好。
2.4 下载预训练权重,跑通第一次推理
YOLOv5 提供了 n/s/m/l/x 五个尺寸的模型,参数量从 1.9M 到 86M 不等。第一次一定用 s,不要用 l 或 x。s 版本在 640 分辨率下单张推理速度很快,显存占用小,拿来做流程验证最合适。
权重文件可以直接从官方 release 页面下载,也可以在跑detect.py的时候让代码自动下载。国内网络环境下自动下载经常卡住,所以我习惯手动下载yolov5s.pt放到项目根目录。
然后:
python detect.py --weights yolov5s.pt --source data/images --conf 0.25K.这是整个流程里最爽的一步。跑完之后终端会打印每张图的检测结果和耗时,结果图片默认保存在runs/detect/exp/下面,打开看一眼,人、车、狗被框出来了,说明环境彻底通了。
如果这一步跑通了,后面 90% 的环境问题都不会再出现了。因为推理和训练用的是同一套 CUDA、同一套 PyTorch,推理能跑说明底层链路是好的。所以我一直建议新手把“跑通 detect.py”作为环境配置的验收标准,而不是“装完包就算完事”。
3. 自己的数据集怎么组织才不会被 train.py 嫌弃
3.1 LabelImg 标注,以及标注里那些细节坑
YOLOv5 用的是 YOLO 格式的 txt 标签,每个 txt 对应一张图,每行一个目标,格式是:
类别编号 中心点x 中心点y 宽 高后面四个数字都是归一化到 0~1 的。比如0 0.523 0.441 0.212 0.334就表示第 0 类的目标,中心点在图片宽度的 52.3%、高度的 44.1% 处,框宽占整图 21.2%,框高占 33.4%。
用 LabelImg 标注的话,有几个坑我必须提前说:
- 启动 LabelImg 时,左侧工具栏要手动切换到 YOLO 模式,默认的 PascalVOC 生成的是 xml,格式不对。
- 标注之前先在设置里勾上“自动保存”,并且把默认保存目录设成和图片同一个文件夹。不然后面你会得到一堆只有图片没有标签的“半成品”。
- 类别名的拼写必须完全一致。
apple和Apple在 YOLO 里是两个不同的类,混着标就会出现“我明明只标了 3 类,训练日志说找到 4 类”的诡异情况。 - 一个目标框要贴紧物体边缘,不要框得太松也不要太小。框得太松,模型学到的是背景;框得太小,模型学不到完整的目标。这是个手工活,练习几十张之后就熟练了。
- 如果一张图里有多个目标,必须全部标完,不能只标一个剩下的不管。YOLO 训练时把没标的部分当背景处理,漏标会造成严重的误检。
标注完成后,正常你会得到一个classes.txt,里面是类别列表。这个文件的顺序就是类别编号的顺序,第 0 行是 0 类,第 1 行是 1 类。后面写data.yaml的时候要跟它保持一致,否则会出现“标签编号和类别名对不上”的问题。
3.2 目录结构必须严格镜像对应
YOLOv5 对目录结构有硬性要求,最简洁的版本是这样的:
mydata/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── val/ │ ├── 003.jpg │ └── 004.jpg └── labels/ ├── train/ │ ├── 001.txt │ └── 002.txt └── val/ ├── 003.txt └── 004.txt核心规则:images 和 labels 的目录结构必须完全平行,图片和 txt 必须同名(除了扩展名)。images/train/001.jpg对应的就是labels/train/001.txt。
YOLOv5 在训练时会自动把images路径里的/images/替换成/labels/,再把扩展名换成.txt去找标签。如果你的目录不是这个结构,它就会找不到标签,然后给你一句No labels found。
还有一个隐藏在后台的机制:训练前 YOLOv5 会把所有图片路径和标签信息缓存成train.cache和val.cache两个文件,放在 labels 目录旁边。这个缓存在大数据集上能大幅提速,但它同时也是最容易坑人的东西——你改了标签,缓存没更新,训练用的还是老数据。所以记住一句话:动过标签或者重新划分过数据集,先去把.cache文件删掉。
3.3 data.yaml 每个字段到底在说什么
新建一个data/mydata.yaml:
train: ../mydata/images/train val: ../mydata/images/val nc: 3 names: ['apple', 'banana', 'orange']四个字段,逐个解释:
train和val:训练集和验证集的图片目录,不是标签目录。YOLOv5 会自己推导出标签路径。nc:number of classes,类别数量。注意是不含背景类的纯目标类别数。很多人从别的框架转过来会把背景算作一类,结果设成 4,训练时直接报索引越界。names:类别名列表,顺序必须和标注时的编号严格对应。
路径写法上有个高频错误:YOLOv5 里相对路径是相对train.py所在目录计算的,不是相对 yaml 文件。所以我一般直接写绝对路径,省得纠结:
train: /home/user/mydata/images/train val: /home/user/mydata/images/valWindows 上用绝对路径记得把反斜杠转义或者改成正斜杠,D:/mydata/images/train这样写就行。
数据集大小的经验值:每个类别至少 100~200 张带标注的图,类别之间数量尽量均衡。如果某一类只有 10 张,模型基本学不会,训练日志里那个类的 mAP 会一直是 0 或者接近 0。数据量实在少的话,多用数据增强(YOLOv5 默认就开了 mosaic、HSV 抖动、随机缩放等),同时把 epochs 调大一点、把模型换成 s 或 n 这种小模型,减少过拟合。
4. train.py 参数逐个拆开讲,别抄命令抄得不明不白
4.1 五个必改参数,其余保持默认
一条典型的训练命令:
python train.py --img 640 --batch 16 --epochs 100 --data data/mydata.yaml --weights yolov5s.pt --project runs/train --name exp1逐个说:
--img 640:训练时的输入分辨率。640 是 YOLOv5 的标准尺寸,也是预训练权重的默认尺寸。改小(比如 416)能省显存但掉精度,改大(比如 1280)提精度但显存翻着涨。新手就用 640。--batch 16:一个批次塞多少张图。这个参数直接决定显存占用,是后面 OOM 报错的第一嫌疑人。一般来说 8G 显存用 8 或 16,12G 用 16 或 32,24G 可以上 64。显存不够就降这个数,没有别的办法。--epochs 100:整个数据集过 100 遍。小数据集(几百张)建议 200~300,中等数据集 100~200 够用。看 loss 曲线不再下降就可以提前停。--data:指向你写好的 yaml。--weights yolov5s.pt:从预训练权重开始训练(迁移学习)。这一步极其重要,不要写成--weights ''从头训。从头训需要的数据量和算力是个人玩家扛不住的,用预训练权重能让模型在几百张图上就有不错的效果。
4.2 Windows 下 --workers 和 batch size 的显存账
--workers控制数据加载的进程数,默认 8。这个参数在 Linux 上一般不用管,但在 Windows 上经常出事。
Windows 下如果--workers设得太大,或者你的数据集放在机械硬盘上,很容易报RuntimeError: DataLoader worker (pid(s) ...) exited unexpectedly。遇到这个错,第一反应就是把--workers改成 0 或者 2。改成 0 意味着数据加载在主进程里做,慢一点但绝对稳。等训练能跑起来了,再慢慢往上加,找一个不报错的临界值。
关于 batch size 和显存的关系,给个粗略的估算参照:
| 模型 | imgsz | batch | 显存占用(近似) | 建议显卡 |
|---|---|---|---|---|
| yolov5n | 640 | 32 | 2~3 GB | 6G 及以上 |
| yolov5s | 640 | 16 | 4~6 GB | 8G 及以上 |
| yolov5m | 640 | 16 | 8~10 GB | 12G 及以上 |
| yolov5l | 640 | 16 | 12~16 GB | 16G 及以上 |
| yolov5x | 640 | 8 | 16~20 GB | 24G |
这只是个大概。实际占用还跟数据集图片尺寸、增强策略有关。训练启动后马上敲一次nvidia-smi,看显存实际用了多少,这比任何估算都准。
还有一个常被忽略的参数是--device。多卡机器上可以用--device 0,1指定用哪几张卡。单卡就--device 0。
4.3 训练日志里哪些数字值得盯
训练开始后,终端会刷新一行一行的进度,格式大概是:
Epoch gpu_mem box_loss obj_loss cls_loss Instances Size 1/100 5.2G 0.0721 0.0312 0.0145 32 640gpu_mem:显存占用,看看有没有贴着上限跑,贴着跑就要小心后面崩。box_loss:边界框回归损失,反映框得准不准。应该随着训练下降。obj_loss:目标置信度损失,反映“这里有没有东西”判断得对不对。cls_loss:分类损失,反映“这是什么东西”判断得对不对。
判断训练是否正常,最简单的标准就是这三条 loss 是不是在稳定下降。如果训练几十个 epoch 后 box_loss 还在 0.05 以上不下来,可能是标注质量有问题;如果 obj_loss 一直很高,可能有大量漏标;如果 cls_loss 不降反升,大概率是类别不平衡或者学习率太大。
验证阶段会打印mAP@0.5和mAP@0.5:0.95。前者表示 IoU 阈值取 0.5 时的平均精度,后者是 0.5 到 0.95 每隔 0.05 取一次再平均,后者更严格。
所有训练结果都存在runs/train/exp1/下面,其中:
weights/best.pt:验证集上表现最好的权重。weights/last.pt:最后一个 epoch 的权重。results.csv:每个 epoch 的所有指标,可以用 Excel 打开画图。results.png:自动生成的损失和指标曲线图,最直观。confusion_matrix.png:混淆矩阵,能看出哪些类别容易混。labels.jpg:标注框的分布统计,能看出目标大小、位置分布是否合理。
labels.jpg这张图新手经常忽略,但它特别有用。如果图上显示大量框挤在图片正中间,说明你的数据集里目标位置太单一,模型泛化能力会差;如果框的宽高分布差异很大,说明目标尺度跨度大,可能需要考虑多尺度训练。
4.4 断点续训怎么操作
训练被中断(断电、手滑关窗口、显存崩了)之后不用从头再来,YOLOv5 支持续训:
python train.py --resume runs/train/exp1/weights/last.pt注意--resume后面跟的必须是含有优化器状态的last.pt,best.pt是保存模型权重的格式,续训时用它不会恢复学习率调度和优化器动量,效果会打折扣。
5. 训练过程中真正会遇到的报错与排查链路
5.1 CUDA out of memory:从降 batch 到降 imgsz
这是出现频率最高的报错,全称RuntimeError: CUDA out of memory. Tried to allocate ...。
排查顺序应该是这样的:
- 先降 batch size。这是最直接有效的,
--batch 16改成--batch 8,还不行改 4。改到能跑为止。 - 再考虑降 imgsz。640 改 512 或者 416,显存占用会明显下降,但小目标的检测精度会掉。
- 换小一号的模型。yolov5m 换成 yolov5s,参数量少一半以上,显存压力小很多。
- 检查是不是别的东西占了显存。跑一次
nvidia-smi,看看除了你的训练进程,还有没有别的 Python 进程赖着不走。这种情况在 Jupyter 里特别常见——改了代码重新运行,旧的进程没释放。用kill -9 PID(Linux)或者在任务管理器里结束进程(Windows)。 - 确认没有同时跑两个训练。有人为了“加快速度”开两个终端跑两次训练,结果互相抢显存,两个都崩。
还有一个隐蔽的坑:验证阶段的显存占用通常比训练阶段更高,因为验证时 batch size 会被自动放大(默认是训练 batch 的两倍左右)。所以有时候训练跑了几个 epoch 都好好的,一到验证就 OOM。修改方法是在train.py里找到验证的 batch 设置,或者直接降低整体 batch size。
5.2 DLL load failed 与版本错位的识别方法
Windows 上最常见的另一个报错是:
ImportError: DLL load failed while importing _C: 找不到指定的模块这个错误 99% 是 PyTorch 和 CUDA 版本不匹配造成的。排查步骤:
- 打印版本信息:
python -c "import torch; print(torch.__version__)",看后缀是不是+cuXXX。如果后缀是+cpu而你想用 GPU,或者后缀是+cu118但你的驱动只支持 11.4,那就是这里的问题。 - 检查驱动:
nvidia-smi看 CUDA Version。 - 卸载重装:
pip uninstall torch torchvision,然后装匹配版本。
另一个可能的原因是系统 PATH 里有多个 CUDA 相关的 dll 在打架,尤其是你之前手动装过 CUDA Toolkit 的情况。如果对当初装过什么没把握,最干净的办法是新建一个 conda 环境重装,别在旧环境里折腾。
还有一个长得有点像但原因完全不同的错误:
OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.这个跟 CUDA 无关,是 Intel OpenMP 库的重复初始化问题。临时解决方案是设置环境变量:
# Windows set KMP_DUPLICATE_LIB_OK=TRUE # Linux / macOS export KMP_DUPLICATE_LIB_OK=TRUE或者在代码最前面加:
import os os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"不过要说明的是,这个只是让程序不崩,根本原因是环境里装了多份 OpenMP 库。如果是长期使用的环境,建议还是查一下是哪个包装进来的。
5.3 No labels found 与缓存文件的坑
报错长这样:
AssertionError: No labels found in /path/to/labels/train.cache或者更温和的一个警告:
WARNING: No labels found in /path/to/labels/train, can not train without labels.排查链路:
- 确认 images 和 labels 目录结构是否镜像对应。这是最容易出问题的地方,尤其是在 Windows 上从别处拷数据集,路径大小写、斜杠方向都可能出问题。
- 确认图片和标签是否同名。
001.jpg必须对应001.txt。如果图片是001.JPG大写,标签是001.txt,大多数情况下没问题,但保险起见统一成小写。 - 删除
.cache文件重试。这一步能解决一半以上的诡异情况。缓存文件里存的是旧的文件列表,你新加了图片它不知道。 - 检查标签文件是不是空的。有时候 LabelImg 保存出问题,txt 是 0 字节。用脚本扫一遍,把空文件列出来。
我写过一个特别简单的检查脚本,几行代码就能定位大部分问题:
import os from pathlib import Path img_dir = Path("mydata/images/train") lbl_dir = Path("mydata/labels/train") img_stems = {p.stem for p in img_dir.glob("*.jpg")} lbl_stems = {p.stem for p in lbl_dir.glob("*.txt")} print("图片没有对应标签:", sorted(img_stems - lbl_stems)[:20]) print("标签没有对应图片:", sorted(lbl_stems - img_stems)[:20]) print("空标签文件:", [p.name for p in lbl_dir.glob("*.txt") if p.stat().st_size == 0][:20])跑一次,三类问题一目了然。
5.4 GPU 占用为 0、训练慢得像蜗牛:数据加载瓶颈怎么查
现象是:nvidia-smi显示 GPU 利用率只有 10%~30%,显存占用也不高,但一个 epoch 跑得特别久。
这是典型的数据加载成了瓶颈。GPU 在等 CPU 把图片读出来、解码、做增强。排查方向:
- 调
--workers。设得太小(比如 0),数据加载全在主进程,GPU 大把时间在等。在 Linux 上可以设到 8 甚至 16,Windows 上保守一点从 4 开始试。 - 数据集别放机械硬盘。放 SSD 上速度差异非常明显。
- 图片别太大。如果你原始的图片是 4000x3000 的高清图,即使训练用 640,读取和解码的开销仍然很大。建议预处理阶段就把图片统一缩放到长边 1280 左右,既保留细节又不浪费 IO。
- 检查是不是在做特别重的增强。mosaic、mixup 这些增强如果开得太猛,会明显增加 CPU 负担。
另外一个容易误导人的点:GPU 利用率是瞬时采样值,波动很大,偶尔看到 0% 不一定是问题。真正该看的是一个 epoch 的总耗时,以及nvidia-smi连续看几秒的平均值。
5.5 页面文件太小与 Windows 共享内存问题
Windows 上还有一类报错:
OSError: [WinError 1455] 页面文件太小,无法完成操作。这个和显卡显存没关系,是系统虚拟内存不够。PyTorch 的 DataLoader 在多进程模式下会通过共享内存在进程间传递数据,这个共享内存走的是系统虚拟内存。
解决方法是手动调大虚拟内存:右键“此电脑” → 属性 → 高级系统设置 → 性能设置 → 高级 → 虚拟内存 → 更改,取消自动管理,把初始大小和最大值都设成物理内存的 1.5~2 倍(比如 16G 内存就设 24576 MB 到 32768 MB)。改完需要重启。
或者简单粗暴地把--workers改成 0,绕开多进程共享内存机制。代价是数据加载慢一点,但对于中小数据集完全能接受。
6. 训练跑完之后:验证、推理和导出部署
6.1 用 val.py 独立评估,看清每个类的表现
训练结束时的那次验证是快速的,如果想认真评估,单独跑一次:
python val.py --weights runs/train/exp1/weights/best.pt --data data/mydata.yaml --img 640 --task val输出会给出每个类别的 P(精确率)、R(召回率)、mAP@0.5、mAP@0.5:0.95。
看这几个数字有个经验判断:
- 如果某个类别的 P 很高(比如 0.95)但 R 很低(比如 0.3),说明模型很保守,只敢在特别有把握的时候才报,漏检多。这时候可以降低推理时的置信度阈值,或者给这个类补充更多训练数据。
- 如果 P 低 R 高,说明模型太激进,什么都敢报,误检多。可以提高置信度阈值,或者检查这一类是不是有大量标注错误的样本。
- 如果 mAP@0.5 有 0.8 以上但 mAP@0.5:0.95 只有 0.4,说明框的位置还不够精准,可以试试加大 imgsz 或者在标注时把框贴得更紧。
runs/val/目录下会生成PR_curve.png、F1_curve.png、confusion_matrix.png,这几张图比数字更能说明问题,值得花时间看。
6.2 best.pt 和 last.pt 到底选哪个
这是个高频问题。简单结论:默认用 best.pt,除非你怀疑 best.pt 过拟合了。
best.pt是在验证集上 mAP 最高的那一版。但因为验证集和训练集来自同一个分布,再加上小数据集上波动大,有时候 best.pt 是“运气好”的产物。有个简单的判断方法:训练结束后,把 best.pt 和 last.pt 分别在验证集上跑一次val.py,如果两者 mAP 差距在 1~2 个点以内,用 best.pt;如果 best.pt 明显高出一大截,说明验证集可能太小导致波动,这时候考虑重新划分数据集。
还有一个更靠谱的做法:留一份真正独立的测试集,不参与训练也不参与验证,只用它来最终评估。这能给你一个相对客观的泛化能力估计。
6.3 detect.py 批量推理与阈值调优
推理自己的图片:
python detect.py --weights runs/train/exp1/weights/best.pt --source my_test_images --conf-thres 0.25 --iou-thres 0.45 --save-txt --save-conf--source可以是单张图、整个文件夹、视频文件,也可以填0调用摄像头。--conf-thres:置信度阈值。默认 0.25,调高减少误检,调低减少漏检。--iou-thres:NMS 的 IoU 阈值。当两个框重叠度超过这个值,保留分数高的那个。默认 0.45,目标密集的场景可以调低一点(比如 0.3)避免把相邻目标合并掉。--save-txt:把检测结果保存成 YOLO 格式的 txt,方便后续二次处理。--save-conf:在 txt 里带上置信度分数。
这两个阈值没有放之四海皆准的答案,必须针对你的场景调。拿 100 张测试图,从 0.1 试到 0.6,每次都统计一下漏检和误检的数量,找到平衡点。这个过程看着笨,但比任何理论推导都管用。
6.4 导出 ONNX 与其他格式的注意事项
想在 C++、移动端或者其他框架里用,需要把 pt 转成通用格式:
python export.py --weights runs/train/exp1/weights/best.pt --include onnx --img 640 --batch 1有个坑一定要说清楚:训练时用的--img和导出时的--img必须一致。如果训练用 640,导出用 1280,导出过程可能不报错,但在别的框架里推理结果会明显不对。同样,--batch 1导出的模型只能处理单张图,要批量推理就得用对应的 batch 导出,或者用动态 batch。
还有一个更隐蔽的问题:预处理必须对齐。YOLOv5 推理时的预处理包含 letterbox(保持宽高比缩放并填充灰边)、BGR 转 RGB、归一化除以 255、HWC 转 CHW 这几个步骤。你在别的框架里部署时,只要漏掉或者顺序搞错任意一步,检测结果就会离谱。如果部署后效果不对,第一件事就是拿同一张图,把 Python 端和部署端的预处理中间结果逐层打印出来对比。
7. 几个能省下大半天时间的实操经验
聊完了整个流程,说几个我踩过之后才明白的点。
关于环境,最省时间的做法不是修,是重建。环境问题排查到半小时还没头绪,直接conda remove -n yolov5 --all重建一个,往往比继续 debug 快。前提是你把安装命令记下来了——所以我建议把整个流程写成一个setup.sh或者一个 markdown 笔记,重建的时候复制粘贴就行。
关于数据集,花在标注上的时间永远不亏。我见过太多人为了赶进度,几百张图两小时标完,框都是随手拉的,最后训练出来 mAP 死活上不去,回头重标一遍花的时间更多。标注这一关,慢就是快。
先跑通小流程,再上大数据集。拿到一个新数据集,先挑 50 张图做一个迷你版数据集,把data.yaml配好,用--epochs 10跑一遍,确认整条链路没问题。确认之后再换成完整数据集跑正式训练。这样做的好处是,如果配置有问题,你 5 分钟就能发现,而不是等到正式训练跑了 3 小时之后才报错。
训练日志的第一屏一定要认真看。YOLOv5 启动时会打印一堆信息:用的什么设备、检测到多少个类别、每个类多少张图、预训练权重加载了多少层。这里面藏着很多线索,比如“每个类多少张图”能立刻暴露类别不平衡的问题,“加载了多少层”如果不是 100% 就说明预训练权重和模型结构对不上。很多人直接跳过这一屏,然后在训练崩了之后一脸茫然。
最后一个小技巧:把runs/目录按项目分开放。train.py的--project参数可以指定输出根目录,我习惯写成--project runs/fruit或者--project runs/license_plate这样,--name写日期。跑十几次实验之后,你会庆幸自己当初这么做了——不然全堆在runs/train/exp1到runs/train/exp37里,想找三周前那个效果最好的权重,只能一个个点进去看。
至于训练本身,其实没什么玄学:数据够、标注准、参数合理,剩下的交给时间和显卡。真正难的是把前面那一整套环境理顺,而这件事,跑通一次之后就不会再难了。