1. 项目缘起与整体思路拆解
在Linux环境下训练自己的目标检测模型,是很多做视觉方向的朋友绕不开的一道坎。我最早接触YOLOv3是在一个工业质检的小项目里,当时手头只有几百张标注图片,硬件是一台带单卡的工作站,系统用的是Ubuntu。那会儿网上能找到的教程要么太老,要么跳步严重,踩了不少坑。这篇笔记就把整个流程从头到尾捋一遍,包括环境搭建、数据准备、配置文件修改、训练启动、以及训练过程中各种报错的排查思路。
先说清楚这个内容适合谁看。如果你手头有一批自己标注的数据集,想用YOLOv3跑一个能用的检测模型,同时对Linux命令不算完全陌生,那这篇笔记基本可以照着做。如果你连Ubuntu都没装过,建议先补一下基础的终端操作,不然中间很多步骤会卡住。整个流程的核心关键词就是:Ubuntu系统、YOLOv3、自定义数据集、训练配置、模型调优。
为什么选YOLOv3而不是更新的版本?这个问题我被问过很多次。YOLOv3虽然推出有些年头了,但它的结构清晰、配置文件直观、社区资料多,对于第一次完整走一遍目标检测训练流程的人来说,学习成本最低。而且很多实际项目里,YOLOv3的精度和速度已经够用,尤其是小目标不多的场景。你完全可以在跑通YOLOv3之后,再去尝试YOLOv4、YOLOv5甚至更新的架构,那时候你会发现很多概念是相通的。
整体思路分四步走:第一步是把训练环境搭好,包括系统依赖、显卡驱动、CUDA和cuDNN;第二步是准备数据集,包括标注、格式转换、划分训练验证集;第三步是修改配置文件,包括类别数、锚框、路径等;第四步是启动训练并监控过程,根据loss曲线和验证结果调整参数。这四步里,第一步和第二步最容易出问题,因为涉及系统层面的东西,一旦版本对不上,后面全白搭。
我个人的建议是,不要一上来就追求最新版本的CUDA和cuDNN,而是根据你用的深度学习框架版本来倒推。比如你打算用Darknet原版来训练YOLOv3,那CUDA版本就不宜太新,否则编译容易报错。这一点后面会详细说。
2. 环境搭建的核心细节与避坑要点
2.1 系统与显卡驱动的选择
Ubuntu的版本选择上,我推荐20.04 LTS或者18.04 LTS。这两个版本长期支持,社区资料最全,遇到问题容易搜到答案。22.04也不是不能用,但有些老版本的CUDA在22.04上装起来会比较折腾。我实测下来,20.04是最稳的。
显卡驱动这块,很多人习惯用系统自带的“附加驱动”来装,但我不推荐这么做。系统自带的驱动版本往往偏旧,而且和CUDA的兼容性不好保证。正确的做法是去显卡厂商官网下载对应型号的驱动,或者用apt源里的指定版本。安装之前一定要先禁用系统默认的nouveau驱动,否则装完驱动重启会黑屏。禁用方法是在blacklist配置文件里加入nouveau,然后更新initramfs。
具体操作上,先确认显卡型号:
lspci | grep -i vga然后去官网查对应驱动版本。安装驱动时建议加上--no-opengl-files参数,避免覆盖系统OpenGL库导致图形界面出问题。装完之后用nvidia-smi验证,能看到显卡信息和驱动版本就说明成功了。
注意:如果你是在服务器上训练,没有图形界面,那驱动安装会简单很多,不用担心OpenGL的问题。但如果是带桌面的工作站,一定要按上面的参数来。
2.2 CUDA与cuDNN的版本匹配
CUDA版本的选择取决于你用的框架。如果用Darknet原版,CUDA 10.0或10.2是比较稳妥的选择。如果用PyTorch版本的YOLOv3实现,那就要看PyTorch支持的CUDA版本了。我一般建议先确定框架版本,再装对应的CUDA。
安装CUDA时,官网的runfile方式比deb方式更灵活,因为可以自定义安装路径,也方便多版本共存。安装过程中会问是否安装显卡驱动,如果你已经装好了驱动,这里一定要选否,否则会覆盖掉之前的驱动。
cuDNN的安装就是下载对应CUDA版本的压缩包,解压后把文件拷贝到CUDA的安装目录里。这里要注意权限问题,拷贝时用sudo。拷贝完之后设置环境变量,把CUDA的bin和lib路径加到配置文件里。
验证CUDA是否装好:
nvcc -V能看到版本信息就对了。cuDNN的验证稍微麻烦一点,可以编译一个简单的测试程序,或者直接看头文件是否存在。
2.3 编译Darknet的关键步骤
Darknet的编译本身不复杂,但有几个地方容易出错。首先是把Makefile里的GPU、CUDNN、OPENCV这几个开关打开。默认情况下它们是0,需要手动改成1。改完之后执行make,如果报错说找不到某个库,通常是对应的开发包没装。
编译过程中最常见的错误是CUDA版本和显卡算力不匹配。比如你的显卡算力是8.6,但CUDA版本太老不支持这个算力,就会报错。解决办法是在Makefile里手动指定算力,或者升级CUDA版本。
编译成功后,用一张测试图片跑一下:
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg如果能输出检测结果,说明环境没问题。这一步很关键,一定要在训练自己的数据之前确认预训练模型能正常推理,否则后面训练出问题你都不知道是环境的事还是数据的事。
实操心得:编译Darknet之前,先把OpenCV装好。虽然OpenCV不是必须的,但有了它之后,训练过程中的图片显示和结果可视化会方便很多。装OpenCV时注意版本,太新的版本可能和Darknet的接口不兼容,我一般用3.4.x或者4.2.x。
3. 数据集准备与格式转换的完整流程
3.1 数据标注工具的选择与使用
标注工具我用过不少,LabelImg、CVAT、Labelme都试过。对于目标检测任务,LabelImg是最轻量的选择,安装简单,操作直观。它的标注格式是PASCAL VOC的XML格式,而YOLOv3需要的是txt格式,所以中间需要一个转换步骤。
标注的时候有几个细节要注意。第一,边界框要尽量贴紧目标,不要留太多空白,但也不要切掉目标边缘。第二,对于遮挡目标,如果遮挡面积超过一半,建议标为困难样本或者直接不标,否则会影响模型学习。第三,类别名称要统一,不要出现同一个类别有多种拼写的情况。
标注完成后,每个图片对应一个XML文件,里面记录了图片尺寸、目标类别和边界框坐标。这些信息在转换成YOLO格式时都会用到。
3.2 从VOC格式到YOLO格式的转换
YOLO格式的标注文件是txt,每行一个目标,格式是:类别索引、中心点x坐标、中心点y坐标、宽度、高度。这些坐标都是相对于图片宽高的归一化值,范围在0到1之间。
转换的核心逻辑是:从XML里读出图片宽高和边界框的左上角、右下角坐标,然后计算中心点和宽高,再分别除以图片宽高进行归一化。类别索引要根据你定义的类别列表来映射,比如person对应0,car对应1,以此类推。
转换脚本用Python写最方便,依赖xml解析库。写脚本时要注意处理图片尺寸和XML里记录的不一致的情况,有些标注工具会在图片旋转后更新XML,但图片本身没变,这种要特别小心。
转换完成后,建议随机抽几张图片,用脚本把YOLO格式的框画出来,和原图对比一下,确认转换没有出错。这一步花不了多少时间,但能避免后面训练时发现标注全错。
3.3 训练集与验证集的划分
数据集划分是个看似简单但容易埋坑的环节。常规做法是训练集占80%,验证集占20%。但如果你的数据里某些类别样本很少,随机划分可能导致验证集里没有这个类别,那验证结果就没有参考意义。
我的做法是先按类别分层抽样,确保每个类别在训练集和验证集里的比例大致相当。具体操作是先把所有标注文件按类别分组,然后每组按比例抽取。这样虽然麻烦一点,但验证结果更可靠。
划分完成后,生成两个txt文件,一个记录训练集图片的路径,一个记录验证集图片的路径。路径建议用绝对路径,避免因为工作目录变化导致找不到文件。
注意:图片路径里不要有中文或空格,否则Darknet读取时可能出问题。如果原始文件名有中文,建议先批量重命名。
4. 配置文件修改与训练参数设置
4.1 类别数与过滤器数量的对应关系
YOLOv3的配置文件里有三个yolo层,每个yolo层前面有一个卷积层,卷积层的filters数量决定了输出特征图的通道数。这个数量必须满足:filters = (类别数 + 5) × 3。其中5是边界框的四个坐标加一个置信度,3是每个网格预测的锚框数量。
举个例子,如果你有3个类别,那filters就是(3+5)×3=24。这个数字必须精确,多一个少一个都会导致训练时报错或者模型无法收敛。我见过有人直接复制别人的配置文件,忘了改这个数字,结果训练出来的模型完全不能用。
除了filters,每个yolo层上面的classes也要改成你的类别数。这个数字要和你的类别列表文件里的行数一致。
4.2 锚框的聚类与调整
YOLOv3默认的锚框是基于COCO数据集聚类出来的,如果你的数据集目标尺寸和COCO差异很大,建议重新聚类。比如你检测的是工业零件,目标尺寸比较统一,那默认锚框可能就不太合适。
聚类用k-means算法,输入是所有训练样本的边界框宽高。聚类的k值设为9,因为YOLOv3每个尺度用3个锚框,三个尺度共9个。聚类完成后,把得到的9个宽高按从小到大排序,然后分配到三个yolo层。通常小锚框给浅层特征图,大锚框给深层特征图。
聚类脚本网上有现成的,但要注意输入数据的格式。你需要从YOLO格式的标注文件里读出宽高,然后乘以图片尺寸还原成像素值,再进行聚类。
4.3 训练超参数的设置逻辑
配置文件里的超参数直接影响训练效果。batch size和subdivisions的关系要搞清楚:实际送入网络的批次大小是batch除以subdivisions。比如batch=64,subdivisions=16,那每次实际处理4张图片。这个设置是为了在显存有限的情况下模拟大批次训练。
学习率的设置上,初始学习率一般设0.001,然后用steps和scales来控制衰减。比如steps=400000,450000,scales=0.1,0.1,意思是到40万步时学习率乘以0.1,到45万步时再乘以0.1。最大迭代次数max_batches一般设为类别数乘以2000,但不少于4000。
width和height是输入图片的尺寸,必须是32的倍数。常用的有416、608等。尺寸越大精度越高但速度越慢,根据你的硬件和精度要求来选。
实操心得:第一次训练时,建议先用小尺寸(比如416)和较小的max_batches跑一遍,确认整个流程能跑通,loss能正常下降。然后再用大尺寸和完整迭代次数正式训练。这样能节省很多时间。
5. 训练启动与过程监控
5.1 启动训练的命令与参数
启动训练的命令是:
./darknet detector train data/obj.data cfg/yolov3-custom.cfg darknet53.conv.74其中obj.data是数据配置文件,里面记录了类别数、训练集路径、验证集路径、类别名称文件路径、以及权重保存目录。yolov3-custom.cfg是你修改后的配置文件。darknet53.conv.74是预训练权重,用它可以加速收敛。
如果你想从上次中断的地方继续训练,把最后的权重文件路径替换掉预训练权重即可。比如:
./darknet detector train data/obj.data cfg/yolov3-custom.cfg backup/yolov3-custom_last.weights训练过程中,终端会实时输出每个批次的loss、学习率、当前迭代次数等信息。同时,权重文件会定期保存到backup目录里。
5.2 loss曲线的解读与调优
loss曲线是判断训练是否正常的最直接依据。正常情况下,loss会随着迭代次数增加而逐渐下降,最后趋于平稳。如果loss一直不降,或者波动很大,说明有问题。
常见的loss异常及原因:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| loss不下降 | 学习率太小或太大 | 调整学习率,尝试0.01或0.0001 |
| loss波动大 | batch size太小 | 增大batch或减小subdivisions |
| loss突然变NaN | 学习率过大或数据有脏样本 | 降低学习率,检查标注文件 |
| 验证loss上升 | 过拟合 | 增加数据增强,减少迭代次数 |
我一般会在训练过程中定期用验证集跑一下评估,看看mAP的变化。如果训练loss在降但验证loss在升,那就是过拟合了,需要早停或者加正则化。
5.3 训练时间的预估与硬件占用
训练时间取决于硬件、图片尺寸、数据集大小和迭代次数。以单卡为例,416尺寸、5000张图片、40000次迭代,大概需要十几个小时。如果尺寸加到608,时间会翻倍。
显存占用方面,batch和subdivisions的设置直接影响显存使用。如果训练时报显存不足,先把subdivisions调大,或者把batch调小。也可以用nvidia-smi实时监控显存占用,找到合适的配置。
CPU占用通常不是瓶颈,但如果数据加载速度跟不上,GPU利用率会偏低。这时候可以增加数据加载的线程数,或者把图片预先缩放到目标尺寸,减少训练时的预处理开销。
6. 常见报错与排查技巧实录
6.1 编译与运行阶段的典型错误
编译Darknet时最常见的错误是找不到cudnn.h或者libcudnn.so。这通常是cuDNN没装好或者环境变量没设置。检查方法是确认cuDNN的头文件和库文件确实拷贝到了CUDA目录,并且LD_LIBRARY_PATH包含了CUDA的lib路径。
运行时报“CUDA out of memory”,说明显存不够。解决办法是减小batch、增大subdivisions、或者减小输入尺寸。如果都不行,只能换显卡。
还有一类错误是“Segmentation fault”,这个比较难排查。常见原因是配置文件里的参数不匹配,比如filters数量和classes数量对不上。建议先用默认的配置文件跑通,再逐步修改。
6.2 训练过程中的异常处理
训练过程中如果发现loss变成NaN,第一时间停止训练,检查学习率是否过大。如果学习率正常,那可能是数据里有问题样本,比如标注框超出图片边界、或者坐标是负数。写个脚本遍历所有标注文件,检查坐标范围。
如果训练到一半突然报错退出,先看终端输出的最后几行,通常会提示具体原因。常见的有磁盘空间不足、权重保存目录不存在、或者图片路径错误。
还有一种情况是训练能跑但mAP很低。这时候要检查标注质量、类别是否平衡、锚框是否合适。我遇到过一次,mAP一直上不去,后来发现是标注时把两个类别搞混了,修正后mAP直接涨了十几个点。
6.3 模型评估与推理的注意事项
训练完成后,用验证集评估模型:
./darknet detector map data/obj.data cfg/yolov3-custom.cfg backup/yolov3-custom_final.weights输出的mAP就是模型的精度指标。如果mAP不理想,不要急着重新训练,先分析是哪些类别拖了后腿。Darknet会输出每个类别的AP,找出低AP的类别,针对性补充数据或调整锚框。
推理时可以用:
./darknet detector test data/obj.data cfg/yolov3-custom.cfg backup/yolov3-custom_final.weights test.jpg如果推理速度慢,可以尝试把配置文件里的width和height调小,或者用TensorRT加速。但加速涉及到模型转换,又是另一个话题了。
避坑技巧:训练完成后,把最终的权重文件、配置文件、类别文件、以及训练时的loss日志一起归档。后面如果要复现或者调优,这些文件都是必需的。我吃过亏,过了一个月想重新训练,发现配置文件被改乱了,只能从头再来。
7. 从训练到部署的衔接思路
模型训练好只是第一步,真正要用起来还得考虑部署。YOLOv3的权重文件可以转成多种格式,比如ONNX、TensorRT、OpenVINO等,适配不同的推理平台。转换过程中要注意输入尺寸、归一化方式、以及后处理逻辑的一致性,否则精度会掉。
如果部署在边缘设备上,还要考虑模型量化。FP16量化通常精度损失很小,速度提升明显。INT8量化需要校准集,精度损失稍大但速度更快。具体选哪种,看你的精度要求和硬件支持。
另外,训练时用的数据增强方式在推理时不需要,但预处理要保持一致。比如训练时做了归一化,推理时也要做同样的归一化。这一点很容易被忽略,导致推理结果和训练时差异很大。
我个人在实际操作中的体会是,训练一个能用的模型不难,难的是让它在实际场景里稳定工作。数据质量、参数调优、部署适配,每个环节都有坑。但只要把流程走通一遍,后面再遇到类似任务,效率会高很多。