本篇博客将带你一步步从零开始,完成 YOLOv4 的环境配置、数据集准备与训练,并涵盖常见的优化和问题解决。本文将以 Darknet 框架下的 YOLOv4 实现为主,因为它是由 YOLOv4 原作者团队维护和优化的官方版本,能够提供最原汁原味的体验和性能。
我们将涵盖以下内容:
重要提示: 训练深度学习模型,尤其是 YOLOv4 这样参数量较大的模型,通常需要一块性能较好的 NVIDIA GPU。本文的环境配置部分将重点介绍如何在 Linux 系统下配置 CUDA 环境,这是 GPU 训练的基石。
这是进行深度学习训练的第一步,也是最容易遇到问题的一步。耐心、仔细是关键。
操作系统:
推荐使用 Linux (如 Ubuntu 18.04 或 20.04)。虽然 Darknet 也可以在 Windows 上编译,但在 Linux 环境下配置 CUDA 和各种库更加方便和稳定。
硬件要求:
前置依赖:
在安装 Darknet 之前,你需要安装一些基础的开发工具和库。
sudo apt update
sudo apt install cmake
# 使用 Miniconda 为例
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
# 按照提示安装
# 创建并激活虚拟环境 (可选但强烈推荐)
conda create -n yolov4_env python=3.8
conda activate yolov4_env
# 安装一些常用库
pip install numpy opencv-python matplotlib
PATH
中,将 lib64 目录添加到 LD_LIBRARY_PATH
中。通常在 ~/.bashrc
文件中添加: Bash export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
然后执行 source ~/.bashrc
使其生效。nvcc -V
。如果显示 CUDA 版本信息,则安装成功。cuDNN Library for Linux
的 tar 文件。include
目录下的头文件复制到 CUDA 的 include
目录,将 lib64
目录下的库文件复制到 CUDA 的 lib64
目录。 Bash tar -xvf cudnn-*-archive.tar.xz
sudo cp cuda/include/cudnn.h /usr/local/cuda/include/
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/
sudo chmod a+r /usr/local/cuda/lib64/libcudnn*
deviceQuery
, bandwidthTest
) 来验证 CUDA 和驱动,cuDNN 通常没有独立的验证程序,但如果后续 Darknet 编译开启 cuDNN 成功,则说明安装正确。sudo apt install libopencv-dev
安装 Darknet:
我们将使用 AlexeyAB 维护的 Darknet 开源项目,它是目前最活跃和功能最完善的 Darknet 版本。
git clone https://github.com/AlexeyAB/darknet.git
cd darknet
Makefile
文件。使用任意文本编辑器打开 Makefile
: Bash # 开启 GPU 支持 (必须)
GPU=1
# 开启 cuDNN 加速 (强烈推荐)
CUDNN=1
# 开启 OpenCV 支持 (推荐,用于可视化)
OPENCV=1
# 开启 LIBSO 支持 (可选,用于编译为动态库,方便 Python 调用)
# LIBSO=1
# 根据你的 CUDA 版本设置 ARCH (Compute Capability)
# 如果不确定,可以运行 nvidia-smi 查看 GPU 型号,然后到 NVIDIA 官网查询对应的 Compute Capability
# 例如,对于 RTX 30系列,ARCH=86
# 如果是较旧的显卡,可能需要设置多个 ARCH,例如 ARCH=61 75
# 或者尝试不设置 ARCH,让 make 自动检测 (有时会出错)
# ARCH= -gencode arch=compute_61,code=[sm_61,compute_61] \
# -gencode arch=compute_75,code=[sm_75,compute_75]
# ... (找到 ARCH 相关的行,根据注释或你的显卡修改)
# 如果使用了 CUDNN_HALF=1,需要 GPU 支持 FP16 (通常是 Volta 及更新架构)
# CUDNN_HALF=1
# 如果使用了 OPENCV=1,确保 PKG_CONFIG_PATH 包含了你的 OpenCV 安装路径
# (如果使用 apt 安装,通常不需要额外设置)
保存并关闭 Makefile
。make
编译过程可能需要几分钟到十几分钟,取决于你的 CPU 性能。如果一切顺利,你会看到编译成功的提示,并在 darknet
目录下生成可执行文件 darknet
。验证安装:
./darknet
,如果能看到 Darknet 的使用说明,说明编译成功。wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights
1 ./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/dog.jpg # 如果能看到检测结果图片 predictions.jpg,说明环境配置和 Darknet 基本功能正常 ```
1. github.com
github.com
训练目标检测模型需要大量的标注数据。每个数据样本包含一张图片和其中所有目标的位置和类别信息。
数据集格式 (YOLO Darknet 格式):
Darknet 期望的标注格式是针对每张图片生成一个同名的 .txt
文件。例如,图片文件是 image.jpg
,对应的标注文件就是 image.txt
。
在 .txt
文件中,每一行代表图片中的一个目标,格式如下:
: 目标的类别 ID,从 0 开始计数。例如,如果你有 "cat" 和 "dog" 两个类别,"cat" 是 0,"dog" 是 1。
,
,
,
: 这些都是 归一化 的浮点数,范围在 [0.0, 1.0] 之间。
: 目标框中心的 X 坐标,除以图片宽度进行归一化。
: 目标框中心的 Y 坐标,除以图片高度进行归一化。
: 目标框的宽度,除以图片宽度进行归一化。
: 目标框的高度,除以图片高度进行归一化。例子:
如果一张 640x480 的图片中有一个目标框,左上角坐标 (100, 50),右下角坐标 (300, 450),类别 ID 是 0 (猫)。
归一化后:
: 200 / 640 = 0.3125
: 250 / 480 = 0.5208
: 200 / 640 = 0.3125
: 400 / 480 = 0.8333则对应的 .txt
文件中的一行就是:
0 0.3125 0.5208 0.3125 0.8333
常用标注工具:
如果你有自己的图片需要从头标注,可以使用以下工具:
数据集文件组织:
通常将数据集组织成以下结构:
your_dataset/
├── images/ # 存放所有图片文件 (.jpg, .png等)
│ ├── image1.jpg
│ ├── image2.png
│ └── ...
└── labels/ # 存放所有标注文件 (.txt),与 images 下的文件一一对应且同名
├── image1.txt
├── image2.txt
└── ...
创建数据文件:
Darknet 需要几个文件来了解你的数据集:
.names
文件: 包含所有类别的名称,每行一个。例如 obj.names
: cat
dog
person
注意顺序要和你的标注中的
对应。.data
文件: 告诉 Darknet 数据集的相关信息。例如 obj.data
: classes = 3 # 类别数量
train = data/train.txt # 训练图片列表文件路径
valid = data/val.txt # 验证图片列表文件路径 (可选)
names = data/obj.names # .names 文件路径
backup = /path/to/backup/weights/ # 训练过程中保存权重文件的目录
# eval=coco # 评估方式 (可选,通常不需要设置)
train.txt
): 包含所有用于训练的图片文件的绝对路径或相对路径,每行一个。val.txt
): (可选) 包含所有用于验证的图片文件的路径,格式同 train.txt
。用于在训练过程中评估模型性能。你可以编写一个简单的 Python 脚本来生成 train.txt
和 val.txt
文件,将 images
目录下的图片路径随机划分到这两个文件中。
Python
import os
import random
image_dir = 'your_dataset/images/'
train_file = 'data/train.txt'
val_file = 'data/val.txt'
split_ratio = 0.9 # 90% 训练,10% 验证
image_list = [os.path.join(image_dir, img) for img in os.listdir(image_dir) if img.endswith(('.jpg', '.png', '.jpeg'))]
random.shuffle(image_list)
train_count = int(len(image_list) * split_ratio)
train_list = image_list[:train_count]
val_list = image_list[train_count:]
with open(train_file, 'w') as f:
for img_path in train_list:
f.write(img_path + '\n')
with open(val_file, 'w') as f:
for img_path in val_list:
f.write(img_path + '\n')
print(f"Generated {train_file} with {len(train_list)} images")
print(f"Generated {val_file} with {len(val_list)} images")
将上述 Python 脚本放在 darknet
目录下,修改 image_dir
路径,运行即可生成 data/train.txt
和 data/val.txt
(确保 darknet
目录下有 data
文件夹)。
训练 YOLOv4 需要配置模型结构、训练参数以及指定数据集信息。
模型配置文件 (.cfg
):
YOLOv4 的模型结构和许多训练参数都定义在 .cfg
文件中,例如 cfg/yolov4.cfg
。你需要根据你的数据集修改这个文件。
找到文件末尾的 [yolo]
层及其前面的 [convolutional]
层。对于每个 [yolo]
层 (通常有 3 个,对应不同尺度的检测):
[convolutional]
层 (在 [yolo]
前): 修改 filters
参数。其值应为 (classes + 5) * 3
。
classes
: 你的数据集的类别数量。5
: 表示边界框的 (center_x, center_y, width, height) 和目标性得分 (objectness score)。3
: 表示该层对应的 Anchor 数量 (YOLOv4 在每个检测层使用 3 个 Anchor)。 例如,如果你的数据集有 3 个类别,则 filters = (3 + 5) * 3 = 24
。[yolo]
层: 修改 classes
参数为你的类别数量。 修改 anchors
参数。这一步非常重要! 默认的 anchors 是在 COCO 数据集上聚类得到的,你需要根据你的数据集重新聚类生成适合你的目标的 anchors。
如何生成 Anchors: Darknet 提供了工具来根据你的数据集计算最佳的 anchors。运行以下命令:
Bash./darknet detector calc_anchors data/obj.data -cfg cfg/yolov4.cfg -num_of_clusters 9 -width 416 -height 416
# data/obj.data: 你的数据文件
# cfg/yolov4.cfg: 你的配置文件
# num_of_clusters: Anchor 数量 (通常 YOLOv4 使用 9 个)
# width/height: 你训练时将使用的输入图片尺寸 (比如 416x416)
该命令会输出计算得到的 9 个 anchors (9个数值对)。将这些数值复制粘贴到 cfg/yolov4.cfg
中所有 [yolo]
层的 anchors
参数后面,并删除原有的 anchors。确保格式正确,例如:anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
(注意逗号和空格)。
其他重要参数 (在文件顶部):
batch
: 训练时使用的图片数量。越大越稳定,但越耗显存。subdivisions
: 将一个 batch 分割成多少份送入 GPU。实际送入 GPU 的图片数量是 batch / subdivisions
。这是为了在显存不足时模拟大 batch size。例如 batch=64
, subdivisions=16
,则每次送入 4 张图片。width
, height
: 网络输入的图片尺寸。通常是 32 的倍数,例如 416x416, 608x608。更大的尺寸通常精度更高,但速度更慢且需要更多显存。learning_rate
: 初始学习率。policy
: 学习率衰减策略 (如 steps, polynomial, exponential)。steps
, scales
: 与 policy=steps
配合使用,定义在哪些迭代次数 (steps
) 将学习率乘以对应的因子 (scales
) 进行衰减。max_batches
: 总共训练的迭代次数。建议设置为 classes * 2000
,但不小于 6000。burn_in
: 在前 burn_in
迭代内,学习率会从一个很小的值逐渐增加到初始学习率。mosaic
, cutmix
: 数据增强技术,默认开启 (1)。hue
, saturation
, exposure
: 颜色抖动参数。random
: 是否使用随机尺寸输入进行训练 (每 10 个迭代随机改变一次输入尺寸,范围从 width/32*32
到 width
,步长为 32)。默认开启 (1)。这有助于提高模型的鲁棒性。迁移学习 (Transfer Learning):
从预训练权重开始训练可以大大缩短训练时间并提高性能,尤其是在数据集较小的情况下。
yolov4.conv.137
(约 160MB) Bash wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.conv.137
yolov4.weights
(约 245MB) Bash wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4.weights
yolov4.conv.137
从头开始在你的数据集上训练,因为 yolov4.weights
是在 80 个类别的 COCO 上训练的,直接在少量类别的数据集上微调可能会遇到类别不匹配的问题 (虽然 Darknet 也能处理,但从 conv 权重开始更规范)。开始训练:
使用 Darknet 的 detector train
命令开始训练。
Bash
./darknet detector train data/obj.data cfg/yolov4.cfg yolov4.conv.137 -map
# data/obj.data: 你的数据文件路径
# cfg/yolov4.cfg: 你的配置文件路径
# yolov4.conv.137: 用于迁移学习的预训练权重文件路径 (如果是从头训练,可以省略或使用一个较小的 backbone 权重如 darknet53.conv.74)
# -map: (可选) 在训练过程中定期计算验证集上的 mAP (Mean Average Precision),用于监控训练效果。这会稍微降低训练速度。
训练开始后,你会看到类似以下的输出:
...
Loading weights from yolov4.conv.137...Done!
Learning Rate: 0.00261
Batch Size: 64, Subdivisions: 16
...
Epoch Iteration Region Avg IOU Class Avg IOU Object Avg IOU Total Avg IOU Recall Precision Avg Loss Rate
1 100 0.7580 0.9032 0.7890 0.8167 0.88 0.91 0.543210 0.001000
1 200 0.7801 0.9115 0.8103 0.8340 0.90 0.93 0.487654 0.002610
...
Saving weights to /path/to/backup/weights/yolov4_xxxx.weights
Darknet 会定期在 backup
目录 (在 .data
文件中指定) 下保存权重文件 (.weights
),通常是每 100 次迭代保存一次 .weights
文件,以及每 1000 次迭代保存一次完整的 .weights
文件。
中断与恢复训练:
如果你需要中断训练 (例如关机或显存不足),可以直接关闭终端。下次训练时,使用以下命令从保存的最新权重文件继续:
Bash
./darknet detector train data/obj.data cfg/yolov4.cfg /path/to/backup/weights/yolov4_last.weights -map
# 将 yolov4_last.weights 替换为你想要恢复的权重文件路径
Darknet 会查找指定权重文件同目录下的 _last.weights
文件作为最新的保存点。
在 YOLOv4 训练过程中,可能会遇到各种问题。以下是一些常见问题及其解决方法:
undefined reference to 'cudaFree'
或其他 CUDA 函数。PATH
和 LD_LIBRARY_PATH
包含正确的 CUDA 路径。检查 Makefile 中的 GPU
和 CUDNN
是否开启,以及 ARCH
是否与你的显卡兼容。fatal error: cudnn.h: No such file or directory
/usr/local/cuda/include
和 /usr/local/cuda/lib64
。确保文件权限正确 (chmod a+r ...
)。undefined reference to 'cv::'
或其他 OpenCV 函数。OPENCV
没有开启。libopencv-dev
,确保 OPENCV=1
在 Makefile 中。Couldn't open file data/obj.data
或 Cannot load image data/train.txt
中的图片。.data
文件中指定的路径错误,或者 train.txt
/val.txt
中指定的图片路径错误。obj.data
文件中的 train
, valid
, names
, backup
路径是否正确,以及 train.txt
/val.txt
文件中的图片路径是否准确。使用绝对路径可以避免相对路径带来的问题。.cfg
):
Error in cfg file...
或训练开始后立即崩溃。.cfg
文件中有语法错误,或者修改 filters
, classes
, anchors
时计算或复制错误。[convolutional]
和 [yolo]
层中的 filters
, classes
, anchors
参数是否正确。filters
必须是 (classes + 5) * 3
。确保 anchors
格式正确。Avg Loss
突然变成 nan
。batch
和 subdivisions
。batch
和/或 增大 subdivisions
: 实际送入 GPU 的图片数量是 batch / subdivisions
,减小这个值可以降低显存占用。.cfg
文件中的 width
和 height
。CUDNN_HALF=1
: 如果你的 GPU 支持 FP16,开启这个选项可以减少显存使用 (需要修改 Makefile 并重新编译)。超参数的选择对模型的训练效果至关重要。调优是一个需要经验和耐心的过程。
以下是一些关键的超参数和调优技巧:
learning_rate
, policy
, steps
, scales
):
0.001
或 0.00261
是常见的起始值)。policy=steps
) 是必要的,可以在训练后期减小学习率,帮助模型更稳定地收敛到最优解。steps
和 scales
需要根据 max_batches
合理设置。例如,在总迭代次数的 80% 和 90% 进行两次衰减,每次衰减因子为 0.1 (scales=0.1,0.1
)。batch
, subdivisions
):
subdivisions
来模拟大 Batch size。width
, height
):
random=1
(随机尺寸训练) 是一个很有效的正则化手段,能提高模型对不同尺寸目标的鲁棒性。mosaic
, cutmix
, hue
, saturation
, exposure
等):
mosaic
和 cutmix
是很有效的增强技术,通常建议开启。hue
, saturation
, exposure
) 可以适度调整,模拟不同的光照条件。.cfg
文件中的 momentum
(动量) 和 decay
(权重衰减) 是重要的优化器参数。默认值通常工作良好,一般不需要大幅调整。decay
用于防止过拟合,较大的 decay
会惩罚大的权重。max_batches
):
max_batches
只会浪费计算资源,甚至可能导致过拟合。Avg Loss
和 -map
选项输出的 mAP 来判断何时停止。调优策略:
yolov4.conv.137
开始训练。Avg Loss
和验证集上的 mAP。subdivisions
模拟。训练完成后,你需要评估模型的性能并可视化检测结果。
性能评估 (mAP):
mAP (Mean Average Precision) 是目标检测中最常用的评估指标,它综合考虑了模型的准确率和召回率。Darknet 可以方便地计算 mAP。
修改 obj.data
文件,确保 valid
路径指向你的验证集图片列表文件。
运行以下命令计算 mAP:
Bash
./darknet detector map data/obj.data cfg/yolov4.cfg /path/to/your_trained_weights.weights
# data/obj.data: 你的数据文件
# cfg/yolov4.cfg: 你的配置文件
# /path/to/your_trained_weights.weights: 你训练好的权重文件路径 (通常选择验证集上 mAP 最高或 loss 最低的权重)
输出会显示不同 IoU 阈值下的 Average Precision (AP) 以及它们的平均值 (mAP)。例如:
...
Calculating mAP (mean average precision)...
For class_id = 0 name = cat: [email protected] = 0.856789
For class_id = 1 name = dog: [email protected] = 0.923456
For class_id = 2 name = person: [email protected] = 0.891234
avg [email protected] = 0.890493
...
[email protected]
: 在 IoU 阈值为 0.5 时的平均精确率。avg [email protected]
: 所有类别在 IoU 阈值为 0.5 时的平均 mAP。通常,avg [email protected]
是最重要的评估指标。一些比赛或研究也会关注更高 IoU 阈值下的 mAP (如 [email protected] 或 [email protected]:0.95,表示在 IoU 从 0.5 到 0.95 以 0.05 为步长的多个阈值下的平均 mAP)。
可视化分析:
将模型应用到新的图片或视频上,直观地查看检测结果。
检测单张图片:
Bash
./darknet detector test data/obj.data cfg/yolov4.cfg /path/to/your_trained_weights.weights /path/to/your_image.jpg
# data/obj.data: 你的数据文件
# cfg/yolov4.cfg: 你的配置文件
# /path/to/your_trained_weights.weights: 训练好的权重文件路径
# /path/to/your_image.jpg: 要检测的图片路径
# -thresh 0.5: (可选) 设置置信度阈值,只显示置信度高于该值的检测框 (默认通常是 0.25)
检测结果会保存在 predictions.jpg
文件中。
检测视频:
Bash
./darknet detector demo data/obj.data cfg/yolov4.cfg /path/to/your_trained_weights.weights /path/to/your_video.mp4 -thresh 0.5 -out_filename results.avi
# ... (前面参数同上)
# /path/to/your_video.mp4: 要检测的视频文件路径
# -out_filename results.avi: (可选) 将检测结果保存为视频文件
这会在窗口中实时显示检测结果。
绘制 loss/mAP 曲线:
Darknet 的训练日志包含了每次迭代的 loss 和 mAP 信息。你可以编写一个 Python 脚本来解析日志文件,并使用 Matplotlib 等库绘制 loss 曲线和 mAP 曲线,以便更直观地分析训练过程。
Python
import matplotlib.pyplot as plt
def parse_log(log_file):
iterations = []
losses = []
ious = []
maps = [] # 如果使用了 -map 选项
with open(log_file, 'r') as f:
for line in f:
if 'Iteration' in line and 'Avg Loss' in line:
parts = line.split()
iterations.append(int(parts[1]))
losses.append(float(parts[-2]))
ious.append(float(parts[5])) # Region Avg IOU
if 'avg [email protected]' in line: # 如果使用了 -map 选项
parts = line.split()
# 找到 avg mAP 值,注意日志格式可能略有不同
try:
map_val = float(parts[-1])
maps.append((iterations[-1], map_val)) # 记录迭代次数和mAP
except (ValueError, IndexError):
pass # 处理解析错误
return iterations, losses, ious, maps
# 修改为你的日志文件路径
log_file = 'path/to/your_darknet_train.log'
iterations, losses, ious, maps = parse_log(log_file)
# 绘制 Loss 和 IOU 曲线
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(iterations, losses)
plt.xlabel('Iteration')
plt.ylabel('Average Loss')
plt.title('Training Loss Curve')
plt.subplot(1, 2, 2)
plt.plot(iterations, ious)
plt.xlabel('Iteration')
plt.ylabel('Region Avg IOU')
plt.title('Training Region Avg IOU Curve')
plt.tight_layout()
plt.show()
# 绘制 mAP 曲线 (如果解析到 mAP 数据)
if maps:
map_iterations, map_values = zip(*maps)
plt.figure(figsize=(6, 5))
plt.plot(map_iterations, map_values)
plt.xlabel('Iteration')
plt.ylabel('[email protected]')
plt.title('Validation mAP Curve')
plt.show()
将上述脚本保存为 .py
文件,修改 log_file
路径并运行,即可看到训练过程的曲线图。
通过本篇博客,你应该已经了解了 YOLOv4 在 Darknet 框架下的环境配置、数据集准备、模型训练、问题解决、超参数调优以及性能评估的全流程。从实际动手的角度出发,我们详细讲解了每一个步骤,并提供了相应的命令和代码示例。
YOLOv4 是一个强大的目标检测模型,掌握其训练过程是进行目标检测项目的基础。希望这篇博客能够帮助你成功地在自己的数据集上训练出高性能的 YOLOv4 模型。
记住,深度学习模型的训练是一个不断尝试和优化的过程。耐心、细致地检查每一步,并结合训练日志进行分析,你一定能取得好结果。
下一步: