1. 为什么在 Ubuntu 上折腾 YOLOv3 训练自己的数据集
如果你手头有一堆标注好的图片,想让机器帮你自动框出目标,YOLOv3 到今天依然是一个值得上手的起点。它不像最新那些大模型那样吃显存、依赖复杂,一台带普通独显的 Ubuntu 机器就能跑起来,训练速度也还能接受。我这次做的项目,就是在一台 Ubuntu 20.04 的台式机上,用 YOLOv3 训练一套自己采集的工业零件数据集,从环境搭建、数据准备、配置修改到训练调参、推理验证,完整走了一遍。
这篇文章适合谁看?如果你已经装好了 Ubuntu,会基本的命令行操作,想自己训练一个目标检测模型但不知道从哪下手,那这篇笔记就是写给你的。我会把每一步为什么这么做、参数怎么算、踩过哪些坑都讲清楚,你照着抄作业基本能跑通。需要说明的是,YOLOv3 本身是一个经典的目标检测网络,它的核心思想是把输入图像划分成网格,每个网格负责预测若干边界框和类别概率,通过多尺度特征图来兼顾大小目标。这套机制决定了它在中小规模数据集上表现稳定,训练成本可控,这也是我选它而不是更复杂方案的主要原因。
整个流程我拆成四大块:环境与依赖准备、数据集制作与格式转换、配置文件修改与训练启动、推理验证与问题排查。每一块都有具体的命令和参数,我会尽量把“为什么是这个值”讲明白,而不是只丢一堆配置让你复制。
2. 环境准备与依赖安装的取舍
2.1 系统与硬件的基本盘
我用的机器配置是 Ubuntu 20.04、一张 8GB 显存的显卡、16GB 内存、500GB 固态。这个配置训练 YOLOv3 在 416×416 输入尺寸下是够用的,batch size 开到 8 到 16 之间比较稳。如果你显存只有 4GB,那就得把输入尺寸降到 320×320,batch 降到 4 甚至 2,否则训练中途会直接报显存不足。
系统层面我建议用 Ubuntu 18.04 或 20.04,这两个版本对显卡驱动和 CUDA 的兼容性最成熟。22.04 也能用,但有些老版本的 CUDA 装起来会多绕几步。装系统时记得把根分区留大一点,训练过程中会不断保存权重文件,一个权重动辄两三百兆,数据集大的话磁盘消耗很快。
2.2 显卡驱动与 CUDA 的安装顺序
这里有个很多人会踩的坑:先装显卡驱动,再装 CUDA,顺序反了容易出问题。我的做法是先用系统自带的软件源装驱动,命令是sudo ubuntu-drivers autoinstall,装完重启,用nvidia-smi确认驱动正常。这个命令会输出显卡型号、驱动版本和当前显存占用,看到表格就说明驱动没问题。
接着装 CUDA。YOLOv3 的原始实现依赖 CUDA 做加速,我选的是 CUDA 10.2 配 cuDNN 7.6.5,这个组合和 YOLOv3 的兼容性经过大量验证,比较稳。装 CUDA 时注意不要勾选它自带的显卡驱动,否则会覆盖你刚装好的驱动。装完在~/.bashrc里加上环境变量:
export PATH=/usr/local/cuda-10.2/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH然后source ~/.bashrc,用nvcc -V验证,能看到版本号就对了。cuDNN 是把解压出来的文件复制到 CUDA 对应目录,记得改权限。
注意:如果你不想折腾 CUDA,也可以用 CPU 训练,但速度会慢到让你怀疑人生。我实测过,同样数据集 GPU 训练一轮两分钟,CPU 要将近一小时,所以强烈建议用 GPU。
2.3 编译 YOLOv3 源码的关键点
YOLOv3 的源码需要编译,编译前先确认Makefile里的几个开关。打开Makefile,把GPU=1、CUDNN=1、OPENCV=1这三项打开。OPENCV 打开是为了后面推理时能直接显示图片和画框,不打开的话只能保存结果图再看,调试起来很麻烦。
编译命令就是make,如果报错说找不到某个头文件,多半是 OpenCV 的开发包没装,用sudo apt install libopencv-dev补上。编译完成后会在当前目录生成一个darknet可执行文件,运行./darknet能看到用法提示,就说明编译成功了。
这里补充一个经验:如果你之前装过多个版本的 CUDA,编译时可能会链接到错误的版本。解决办法是在编译前确认nvcc指向的是你要用的那个版本,或者直接在Makefile里把 CUDA 路径写死。
3. 数据集制作与格式转换的完整流程
3.1 数据采集与标注的实操细节
数据集是整个训练的地基,地基没打好,后面调参调到天亮也没用。我这次采集了大约 1200 张图片,涵盖不同光照、不同角度、不同背景。采集时有个原则:训练集里出现的场景,要尽量覆盖你实际使用时可能遇到的情况。比如零件可能被遮挡、可能反光、可能放在不同颜色的台面上,这些都要拍进去。
标注工具我用的是 labelImg,安装很简单,pip install labelImg就能跑。标注时画框要贴紧目标边缘,不要留太多空白,也不要把目标切掉一部分。每张图标注完会生成一个同名的 xml 文件,里面记录了每个框的坐标和类别。类别名建议用英文小写,避免中文和特殊字符,后面配置文件里要用到。
标注数量上,每个类别至少准备 200 个实例,太少的话模型学不到稳定特征。我这次是 3 个类别,总共 1200 张图,平均每类 400 个实例左右,训练下来效果还可以。
3.2 从 xml 到 txt 的格式转换
YOLOv3 不直接吃 xml,需要转成它自己的 txt 格式。每行格式是:类别索引 中心x 中心y 宽 高,后四个值都是相对于图片宽高的归一化值,范围 0 到 1。这个归一化很关键,它让模型不受图片绝对尺寸影响。
我写了一个 Python 脚本批量转换,核心逻辑是读 xml 里的bndbox,算出中心点和宽高,再除以图片的宽高。转换脚本里要注意图片路径和 xml 路径的对应关系,我习惯把图片和 xml 放在同一目录,脚本遍历 xml 时用同名去找到图片读取尺寸。
转换完成后,每个图片对应一个 txt 文件,内容就是若干行归一化坐标。你可以随便打开一个检查,如果数值都在 0 到 1 之间,且行数和标注框数量一致,就说明转换正确。
3.3 训练集与验证集的划分
数据集要分成训练集和验证集,比例一般是 8:2 或 9:1。我这次用 9:1,1080 张训练,120 张验证。划分时要注意随机打散,不能按顺序切,否则如果数据是按时间采集的,验证集可能全是某个时段的场景,评估结果就不准了。
划分完成后生成两个清单文件:train.txt和val.txt,每行是一张图片的绝对路径。这两个文件后面在配置文件里要引用。生成清单时用find命令配合shuf打散比较方便:
find $(pwd)/images -name "*.jpg" | shuf > all.txt head -n 1080 all.txt > train.txt tail -n 120 all.txt > val.txt提示:清单文件里的路径一定要是绝对路径,相对路径在训练时容易因为工作目录变化而找不到图片,这是新手很常见的报错来源。
4. 配置文件修改与训练启动
4.1 类别数与滤波器数量的计算关系
YOLOv3 的配置文件里,有几个地方必须根据你的类别数改。最关键的是每个 yolo 层前面的卷积层,它的filters值要等于(类别数 + 5) × 3。这个公式怎么来的?因为每个预测框要输出 4 个坐标、1 个置信度,再加上类别数个类别概率,一共是类别数 + 5个数,而每个网格预测 3 个框,所以滤波器数量就是(类别数 + 5) × 3。
我这次是 3 个类别,所以filters = (3 + 5) × 3 = 24。配置文件里有三处这样的卷积层,都要改成 24。同时每个 yolo 层里的classes也要改成 3。这两个地方改错,训练时要么报维度不匹配,要么训练出来检测不到目标。
另外cfg文件里还有max_batches、steps、learning_rate这些训练超参。max_batches一般设为类别数 × 2000,但不少于 4000,我设的是 6000。steps设为max_batches的 80% 和 90%,也就是 4800 和 5400,在这两个点学习率会下降。这些值不是死的,数据集大就调大,小就调小。
4.2 数据配置文件与预训练权重
除了模型配置文件,还要建一个数据配置文件,里面写清楚类别数、训练清单、验证清单、类别名文件、备份目录。类别名文件就是每行一个类别名,顺序要和标注时的索引一致。备份目录用来存训练过程中的权重,建议单独建一个目录,别和代码混在一起。
预训练权重我强烈建议用官方的 darknet53 权重,它是在大规模数据集上训过的,能帮你省很多时间。用法是在训练命令里加上-pretrained参数指向权重文件。如果不加,模型从随机初始化开始学,收敛会慢很多,小数据集上甚至可能学不出来。
4.3 启动训练与观察日志
训练命令大概长这样:
./darknet detector train data/obj.data cfg/yolov3-custom.cfg darknet53.conv.74启动后终端会不断刷日志,重点看几个数:avg loss是平均损失,整体趋势应该是下降的,中间有波动正常;Region Avg IOU是预测框和真实框的平均交并比,这个值越高说明框得越准,一般能到 0.7 以上就不错。如果 loss 一直不降或者 IOU 一直很低,那就要回头检查数据和配置。
训练过程中权重会定期保存到备份目录,文件名带迭代次数。我一般训到 loss 稳定在 0.1 到 0.3 之间就停,再训下去容易过拟合。整个训练在我这台机器上跑了大约 6 个小时,6000 轮。
注意:训练时不要频繁中断,每次中断再续训虽然可以,但学习率调度会受影响。如果非要中断,用保存的权重接着训,命令里把预训练权重换成你保存的那个。
5. 推理验证与效果评估
5.1 单张图片与批量测试
训练完拿保存的权重做推理,命令是:
./darknet detector test data/obj.data cfg/yolov3-custom.cfg backup/yolov3-custom_final.weights test.jpg它会输出检测到的目标类别、置信度和坐标,同时生成一张画了框的predictions.jpg。置信度阈值默认是 0.25,可以在命令里调,也可以改配置文件。我一般先用默认值看整体效果,再根据漏检和误检情况调整。
批量测试的话,把测试图片路径写成一个清单文件,用detector test配合清单跑,结果会依次保存。批量测试能帮你快速发现模型在哪些场景下表现差,比如反光场景漏检多,那就针对性地补这类数据再训。
5.2 评估指标的计算与解读
光看几张图的框还不够,要有量化指标。YOLOv3 自带recall和map的计算,用detector recall命令跑验证集,会输出每个类别的平均精度和整体 mAP。mAP 是衡量检测效果的核心指标,0.5 以上算能用,0.7 以上算不错。
我这次三个类别的 mAP 分别是 0.82、0.76、0.79,整体 0.79。第二个类别偏低,回头看数据发现它的样本里遮挡情况比较多,标注时有些框画得不够准。后来补了 200 张遮挡样本重新训,mAP 提到了 0.83。这说明数据质量对结果的影响,往往比调参更大。
5.3 推理速度与部署考量
推理速度也是实际使用时要考虑的。在 416×416 输入下,我这台机器单张图推理大约 30 毫秒,换算下来每秒 30 多帧,做实时检测够用。如果换成 608×608,精度会高一点,但速度降到 15 帧左右。所以输入尺寸的选择,本质是精度和速度的权衡,看你的场景更看重哪个。
部署时如果要在没有 GPU 的机器上跑,可以用 CPU 版本,但速度会慢很多。另一种思路是把模型转成其他推理框架的格式,不过这就超出这篇笔记的范围了,后面有机会再单独聊。
6. 常见问题与排查技巧实录
6.1 训练报错速查表
| 报错信息 | 可能原因 | 解决办法 |
|---|---|---|
| Cannot load image | 清单里路径错误或图片损坏 | 检查路径是否为绝对路径,用工具验证图片能否打开 |
| Out of memory | batch 或输入尺寸太大 | 降低 batch 或输入尺寸,或换更大显存显卡 |
| Dimensions mismatch | filters 或 classes 没改对 | 按公式重算 filters,确认三处 yolo 层都改了 |
| Loss 不下降 | 学习率过高或数据有问题 | 降低学习率,检查标注和格式转换是否正确 |
| 检测不到目标 | 置信度阈值过高或训练不足 | 降低阈值,增加训练轮数,检查类别索引是否对应 |
6.2 几个容易忽略的细节
第一个细节是类别索引。标注时类别名到索引的映射,必须和类别名文件里的顺序完全一致。我曾经因为把两个类别的顺序写反,训练出来模型把 A 类全认成 B 类,排查了半天才发现是顺序问题。
第二个细节是图片格式。YOLOv3 对 jpg 支持最好,png 也能用但偶尔会有通道问题。如果数据集里混了灰度图,最好统一转成三通道,否则训练时可能报通道数不匹配。
第三个细节是备份目录的权限。如果目录没有写权限,训练到保存权重时会直接崩,而且前面的训练白费。启动训练前先确认目录可写。
6.3 提升效果的经验技巧
数据增强是提升效果最划算的手段。YOLOv3 配置文件里自带angle、saturation、exposure、hue这些增强参数,适当调大能让模型对旋转、光照变化更鲁棒。我一般把angle设到 10 到 15,saturation和exposure设到 1.5 左右,效果比较明显。
另一个技巧是分阶段训练。先用较大学习率快速收敛,再降低学习率精调。YOLOv3 的steps机制就是干这个的,把两个 step 设好,学习率会自动下降。如果发现后期 loss 震荡,可以手动把学习率再调小一档。
最后,验证集的表现和实际使用场景要匹配。如果你的验证集全是清晰图片,但实际用的时候图片模糊,那 mAP 再高也没用。所以验证集要尽量贴近真实使用场景,这样评估结果才有参考价值。
7. 我个人的几点实操体会
这套流程我前后跑过好几遍,每次都会遇到新问题,但整体框架是稳的。Ubuntu 上用 YOLOv3 训练自己的数据集,难点不在代码本身,而在数据准备和参数理解上。很多人卡在格式转换和配置文件修改,其实只要把filters公式和归一化逻辑搞明白,剩下的就是耐心调。
显存不够的时候,别硬撑,降输入尺寸和 batch 是最直接的办法,精度损失可以通过增加训练轮数补回来一部分。训练日志要养成看的习惯,loss 和 IOU 的变化能告诉你很多信息,比盲目调参有用得多。
还有一点,权重文件要定期备份,尤其是训练到后期的时候。我有一次训练到 5000 轮,机器突然断电,没备份的话就得从头再来。后来我养成了习惯,每 1000 轮就把权重复制一份到别的目录,心里踏实很多。
这套东西后续还能扩展,比如换更轻量的网络结构做移动端部署,或者把检测结果接一个跟踪算法做视频分析。等你把基础流程跑通,这些扩展就是水到渠成的事。