1. 环境准备从零搭建Darknet训练环境第一次接触YOLOv3和Darknet时最让人头疼的就是环境配置。我清楚地记得自己第一次编译Darknet时遇到的CUDA报错整整折腾了两天。为了让你们少走弯路我把关键步骤和常见问题都整理在这里。首先需要明确硬件要求至少4GB显存的NVIDIA显卡是刚需。我曾经用GTX 1050Ti4GB显存跑过基础训练batch_size只能设到8。如果你的显卡显存更小建议考虑YOLOv3-tiny版本。安装依赖项时最容易踩坑的是CUDA和OpenCV版本冲突。推荐使用以下组合Ubuntu 18.04/20.04 LTSCUDA 10.2 cuDNN 7.6.5OpenCV 3.4.6具体操作命令如下# 安装基础依赖 sudo apt-get install build-essential git cmake libopencv-dev # 克隆Darknet仓库建议用官方源 git clone https://github.com/pjreddie/darknet cd darknet # 修改Makefile关键参数 sed -i s/GPU0/GPU1/ Makefile sed -i s/CUDNN0/CUDNN1/ Makefile sed -i s/OPENCV0/OPENCV1/ Makefile # 编译安装 make -j$(nproc)编译成功后建议立即测试预训练模型wget https://pjreddie.com/media/files/yolov3.weights ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg常见问题排查make时报错nvcc not found检查CUDA安装路径是否加入PATH建议在~/.bashrc添加export PATH/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATHOpenCV报错undefined reference可能是版本冲突建议完全卸载后重装指定版本运行时报CUDA out of memory降低cfg文件中的batch和subdivisions值提示建议将编译好的darknet可执行文件备份后续重装系统时可以直接使用2. 数据准备从标注到Darknet格式转换数据准备是训练成功的关键但90%的新手都会在这里栽跟头。我见过最典型的问题是标注格式错误导致训练时loss值异常波动。下面分享我的标准化处理流程。2.1 数据集目录结构规范推荐采用以下目录结构这是我经过多个项目验证的最高效方案myData/ ├── JPEGImages/ # 存放所有原始图片(.jpg) ├── Annotations/ # 存放VOC格式标注文件(.xml) ├── ImageSets/ │ └── Main/ # 存放数据集划分文件 ├── labels/ # 转换后的YOLO格式标注 └── myData_train.txt # 最终训练文件列表2.2 标注格式转换实战VOC转YOLO格式最容易出错的是坐标归一化计算。这里给出经过验证的Python脚本import xml.etree.ElementTree as ET import os classes [cat, dog] # 修改为你的类别 def convert(size, box): dw 1./size[0] dh 1./size[1] x (box[0] box[1])/2.0 y (box[2] box[3])/2.0 w box[1] - box[0] h box[3] - box[2] x x*dw w w*dw y y*dh h h*dh return (x,y,w,h) def convert_annotation(xml_path, txt_path): in_file open(xml_path) out_file open(txt_path, w) tree ET.parse(in_file) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) xmlbox obj.find(bndbox) b (float(xmlbox.find(xmin).text), float(xmlbox.find(xmax).text), float(xmlbox.find(ymin).text), float(xmlbox.find(ymax).text)) bb convert((w,h), b) out_file.write(str(cls_id) .join([str(a) for a in bb]) \n)2.3 数据集划分技巧很多教程建议用固定比例划分数据集但根据我的经验更科学的做法是确保每个类别在训练集和验证集中都有代表对于小样本数据1000张建议80/10/10划分对于大样本数据可采用95/2.5/2.5划分这里给出改进版数据集划分脚本from sklearn.model_selection import train_test_split import os def split_dataset(xml_dir, output_dir, test_size0.1): xml_files [f for f in os.listdir(xml_dir) if f.endswith(.xml)] # 按类别分层抽样 classes [] for xml in xml_files: tree ET.parse(os.path.join(xml_dir, xml)) root tree.getroot() for obj in root.iter(object): classes.append(obj.find(name).text) # 分层划分 train_val, test train_test_split(xml_files, test_sizetest_size, stratifyclasses, random_state42) train, val train_test_split(train_val, test_size0.11, stratify[classes[i] for i in train_val], random_state42) # 保存划分结果 with open(os.path.join(output_dir, train.txt), w) as f: f.write(\n.join([os.path.splitext(x)[0] for x in train])) # 同理写入val.txt和test.txt3. 配置文件调参关键参数解析与优化YOLOv3的配置文件就像黑魔法咒语稍微改错一个参数就可能让训练完全失败。下面是我总结的最全参数调整指南。3.1 必须修改的基础参数在my_yolov3.cfg中这些参数直接影响训练效果[net] batch64 # 根据显存调整4GB显存建议8-16 subdivisions16 # 小显存可增大到32 width416 # 必须是32的倍数 height416 # 与width保持一致 max_batches50000 # 类数×20002类则4000 policysteps # 学习率策略 steps40000,45000 # 达到max_batches的80%,90%时调整 scales.1,.1 # 学习率衰减系数3.2 三类yolo层的修改要点在配置文件中搜索yolo会找到三个yolo层每个都需要修改两处修改classes数为你的类别数修改filters数为3×(5classes数)例如对于2分类任务[convolutional] filters21 # 3*(52)21 [yolo] classes2 # 你的类别数3.3 学习率调优经验经过多次实验我总结出不同场景下的学习率设置大数据集(1万张)初始lr0.001小数据集初始lr0.0005微调预训练模型初始lr0.0001如果训练初期出现loss爆炸显示为nan可以尝试降低学习率10倍增大burn_in参数如设为1000检查数据标注是否正确4. 训练启动与监控从首次运行到问题排查终于到了最激动人心的训练环节但别急着庆祝真正的挑战才刚刚开始。4.1 训练命令的进阶用法基础训练命令./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74实用技巧多GPU训练2卡示例./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74 -gpus 0,1记录训练日志./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74 | tee train.log断点续训./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3.backup4.2 训练过程监控指标关键指标解读avg loss理想情况下应该缓慢下降最终稳定在0.5-3之间IOU反映定位精度应逐步提升到0.6以上recall反映检测覆盖率理想值0.8异常情况处理lossnan立即停止训练检查学习率和数据标注loss波动剧烈适当降低学习率或增大batch指标长期不提升可能需要调整网络结构或增加数据4.3 可视化工具改进版原始文章提供的可视化脚本有些简陋我改进后的版本增加了以下功能动态平滑处理多指标同图对比自动异常点检测import matplotlib.pyplot as plt import pandas as pd from scipy.signal import savgol_filter def plot_log(log_path): # 解析日志文件 with open(log_path) as f: lines [line.strip() for line in f if avg in line] # 提取关键数据 iterations [] losses [] for i, line in enumerate(lines): parts line.split() iterations.append(i) losses.append(float(parts[1])) # 数据平滑 smoothed savgol_filter(losses, window_length51, polyorder3) # 绘制曲线 plt.figure(figsize(12,6)) plt.plot(iterations, losses, alpha0.3, labelRaw) plt.plot(iterations, smoothed, r, labelSmoothed) # 自动标注异常点 avg sum(losses[-100:])/100 std (sum([(x-avg)**2 for x in losses[-100:]])/100)**0.5 anomalies [i for i,x in enumerate(losses) if abs(x-avg)3*std] plt.scatter(anomalies, [losses[i] for i in anomalies], cg, labelAnomaly) plt.legend() plt.grid() plt.show()5. 模型测试与优化让检测效果更上一层楼训练完成只是第一步如何让模型在实际场景中表现更好才是真正的挑战。5.1 模型测试的正确姿势测试命令示例# 测试单张图片 ./darknet detector test cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights test.jpg # 测试视频流 ./darknet detector demo cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights test.mp4常见问题解决方案检测框偏移检查训练时是否启用了数据增强random1漏检率高降低检测阈值-thresh 0.3误检多提高阈值-thresh 0.7或增加负样本5.2 模型压缩技巧当需要在边缘设备部署时可以考虑量化训练修改cfg中的width/height为320×320剪枝处理使用darknet自带的prune工具转换为TensorRT格式需安装TensorRT5.3 持续改进策略根据我的项目经验模型优化是个迭代过程分析bad case建立错误样本库针对性补充训练数据调整anchor box使用k-means重新计算尝试不同backbone如darknet53→darknet19最后分享一个实用技巧训练完成后用以下命令可以生成模型检测能力的热力图帮助分析哪些区域关注度不足./darknet detector heatmap cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights
【YOLOv3实战】从零到一:在Darknet上训练自定义数据集的避坑指南
1. 环境准备从零搭建Darknet训练环境第一次接触YOLOv3和Darknet时最让人头疼的就是环境配置。我清楚地记得自己第一次编译Darknet时遇到的CUDA报错整整折腾了两天。为了让你们少走弯路我把关键步骤和常见问题都整理在这里。首先需要明确硬件要求至少4GB显存的NVIDIA显卡是刚需。我曾经用GTX 1050Ti4GB显存跑过基础训练batch_size只能设到8。如果你的显卡显存更小建议考虑YOLOv3-tiny版本。安装依赖项时最容易踩坑的是CUDA和OpenCV版本冲突。推荐使用以下组合Ubuntu 18.04/20.04 LTSCUDA 10.2 cuDNN 7.6.5OpenCV 3.4.6具体操作命令如下# 安装基础依赖 sudo apt-get install build-essential git cmake libopencv-dev # 克隆Darknet仓库建议用官方源 git clone https://github.com/pjreddie/darknet cd darknet # 修改Makefile关键参数 sed -i s/GPU0/GPU1/ Makefile sed -i s/CUDNN0/CUDNN1/ Makefile sed -i s/OPENCV0/OPENCV1/ Makefile # 编译安装 make -j$(nproc)编译成功后建议立即测试预训练模型wget https://pjreddie.com/media/files/yolov3.weights ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg常见问题排查make时报错nvcc not found检查CUDA安装路径是否加入PATH建议在~/.bashrc添加export PATH/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATHOpenCV报错undefined reference可能是版本冲突建议完全卸载后重装指定版本运行时报CUDA out of memory降低cfg文件中的batch和subdivisions值提示建议将编译好的darknet可执行文件备份后续重装系统时可以直接使用2. 数据准备从标注到Darknet格式转换数据准备是训练成功的关键但90%的新手都会在这里栽跟头。我见过最典型的问题是标注格式错误导致训练时loss值异常波动。下面分享我的标准化处理流程。2.1 数据集目录结构规范推荐采用以下目录结构这是我经过多个项目验证的最高效方案myData/ ├── JPEGImages/ # 存放所有原始图片(.jpg) ├── Annotations/ # 存放VOC格式标注文件(.xml) ├── ImageSets/ │ └── Main/ # 存放数据集划分文件 ├── labels/ # 转换后的YOLO格式标注 └── myData_train.txt # 最终训练文件列表2.2 标注格式转换实战VOC转YOLO格式最容易出错的是坐标归一化计算。这里给出经过验证的Python脚本import xml.etree.ElementTree as ET import os classes [cat, dog] # 修改为你的类别 def convert(size, box): dw 1./size[0] dh 1./size[1] x (box[0] box[1])/2.0 y (box[2] box[3])/2.0 w box[1] - box[0] h box[3] - box[2] x x*dw w w*dw y y*dh h h*dh return (x,y,w,h) def convert_annotation(xml_path, txt_path): in_file open(xml_path) out_file open(txt_path, w) tree ET.parse(in_file) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) xmlbox obj.find(bndbox) b (float(xmlbox.find(xmin).text), float(xmlbox.find(xmax).text), float(xmlbox.find(ymin).text), float(xmlbox.find(ymax).text)) bb convert((w,h), b) out_file.write(str(cls_id) .join([str(a) for a in bb]) \n)2.3 数据集划分技巧很多教程建议用固定比例划分数据集但根据我的经验更科学的做法是确保每个类别在训练集和验证集中都有代表对于小样本数据1000张建议80/10/10划分对于大样本数据可采用95/2.5/2.5划分这里给出改进版数据集划分脚本from sklearn.model_selection import train_test_split import os def split_dataset(xml_dir, output_dir, test_size0.1): xml_files [f for f in os.listdir(xml_dir) if f.endswith(.xml)] # 按类别分层抽样 classes [] for xml in xml_files: tree ET.parse(os.path.join(xml_dir, xml)) root tree.getroot() for obj in root.iter(object): classes.append(obj.find(name).text) # 分层划分 train_val, test train_test_split(xml_files, test_sizetest_size, stratifyclasses, random_state42) train, val train_test_split(train_val, test_size0.11, stratify[classes[i] for i in train_val], random_state42) # 保存划分结果 with open(os.path.join(output_dir, train.txt), w) as f: f.write(\n.join([os.path.splitext(x)[0] for x in train])) # 同理写入val.txt和test.txt3. 配置文件调参关键参数解析与优化YOLOv3的配置文件就像黑魔法咒语稍微改错一个参数就可能让训练完全失败。下面是我总结的最全参数调整指南。3.1 必须修改的基础参数在my_yolov3.cfg中这些参数直接影响训练效果[net] batch64 # 根据显存调整4GB显存建议8-16 subdivisions16 # 小显存可增大到32 width416 # 必须是32的倍数 height416 # 与width保持一致 max_batches50000 # 类数×20002类则4000 policysteps # 学习率策略 steps40000,45000 # 达到max_batches的80%,90%时调整 scales.1,.1 # 学习率衰减系数3.2 三类yolo层的修改要点在配置文件中搜索yolo会找到三个yolo层每个都需要修改两处修改classes数为你的类别数修改filters数为3×(5classes数)例如对于2分类任务[convolutional] filters21 # 3*(52)21 [yolo] classes2 # 你的类别数3.3 学习率调优经验经过多次实验我总结出不同场景下的学习率设置大数据集(1万张)初始lr0.001小数据集初始lr0.0005微调预训练模型初始lr0.0001如果训练初期出现loss爆炸显示为nan可以尝试降低学习率10倍增大burn_in参数如设为1000检查数据标注是否正确4. 训练启动与监控从首次运行到问题排查终于到了最激动人心的训练环节但别急着庆祝真正的挑战才刚刚开始。4.1 训练命令的进阶用法基础训练命令./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74实用技巧多GPU训练2卡示例./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74 -gpus 0,1记录训练日志./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg darknet53.conv.74 | tee train.log断点续训./darknet detector train cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3.backup4.2 训练过程监控指标关键指标解读avg loss理想情况下应该缓慢下降最终稳定在0.5-3之间IOU反映定位精度应逐步提升到0.6以上recall反映检测覆盖率理想值0.8异常情况处理lossnan立即停止训练检查学习率和数据标注loss波动剧烈适当降低学习率或增大batch指标长期不提升可能需要调整网络结构或增加数据4.3 可视化工具改进版原始文章提供的可视化脚本有些简陋我改进后的版本增加了以下功能动态平滑处理多指标同图对比自动异常点检测import matplotlib.pyplot as plt import pandas as pd from scipy.signal import savgol_filter def plot_log(log_path): # 解析日志文件 with open(log_path) as f: lines [line.strip() for line in f if avg in line] # 提取关键数据 iterations [] losses [] for i, line in enumerate(lines): parts line.split() iterations.append(i) losses.append(float(parts[1])) # 数据平滑 smoothed savgol_filter(losses, window_length51, polyorder3) # 绘制曲线 plt.figure(figsize(12,6)) plt.plot(iterations, losses, alpha0.3, labelRaw) plt.plot(iterations, smoothed, r, labelSmoothed) # 自动标注异常点 avg sum(losses[-100:])/100 std (sum([(x-avg)**2 for x in losses[-100:]])/100)**0.5 anomalies [i for i,x in enumerate(losses) if abs(x-avg)3*std] plt.scatter(anomalies, [losses[i] for i in anomalies], cg, labelAnomaly) plt.legend() plt.grid() plt.show()5. 模型测试与优化让检测效果更上一层楼训练完成只是第一步如何让模型在实际场景中表现更好才是真正的挑战。5.1 模型测试的正确姿势测试命令示例# 测试单张图片 ./darknet detector test cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights test.jpg # 测试视频流 ./darknet detector demo cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights test.mp4常见问题解决方案检测框偏移检查训练时是否启用了数据增强random1漏检率高降低检测阈值-thresh 0.3误检多提高阈值-thresh 0.7或增加负样本5.2 模型压缩技巧当需要在边缘设备部署时可以考虑量化训练修改cfg中的width/height为320×320剪枝处理使用darknet自带的prune工具转换为TensorRT格式需安装TensorRT5.3 持续改进策略根据我的项目经验模型优化是个迭代过程分析bad case建立错误样本库针对性补充训练数据调整anchor box使用k-means重新计算尝试不同backbone如darknet53→darknet19最后分享一个实用技巧训练完成后用以下命令可以生成模型检测能力的热力图帮助分析哪些区域关注度不足./darknet detector heatmap cfg/my_data.data cfg/my_yolov3.cfg backup/my_yolov3_final.weights