[YOLO系列④] YOLOv5模型训练与流程解析

1.基本使用

1.YOLOv5整体概述

YOLOv5本质上是一个经过大量优化的工程项目,不像前几代那样有对应的学术论文。它主要是在YOLOv4的基础上做了更实用的工程改进,让使用者能更轻松地应用到实际场景中。主要有以下特点:

  1. 工程优化为主
    • 没有官方论文,核心改进在于代码实现,比如训练效率、代码可读性
    • 相比YOLOv4,工程结构更简洁,配置更直观,适合直接拿来训练自己的数据
  2. 使用体验升级
    • 作者把数据增强、模型结构(如CSP、SPP模块)等复杂逻辑封装得很好,使用者几乎不用改代码
    • 支持混合精度训练,训练速度更快,对硬件要求更友好
  1. 简化训练自定义数据
    • 不需要用庞大的COCO数据集,准备好自己的标注数据(格式和目录按规范整理)就能直接开跑
    • 数据增强、模型配置都通过配置文件(YAML)管理,改几个参数就能适配不同任务
  2. 代码简洁
    • 相比YOLOv3/v4的“堆料式”代码,V5的代码结构清晰,模块化程度高,容易二次开发
    • 项目文档详细,从安装到训练、测试都有现成指令,能快速上手

YOLOv5 的代码量虽然多一点,但设计非常细致、实用,并且持续更新维护,不同于很多“一发论文就停止维护”的学术项目。

2.训练自定义数据集

如何训练自定义数据集,可分为三步:

  1. 下载(准备)标注好的数据集
  2. 按指定文件夹结构存放
  3. 改配置文件里的路径和类别数

训练数据也可以从Roboflow上下载,Roboflow​ 是一个面向开发者和企业的端到端计算机视觉(CV)开发平台,可以帮助用户高效构建、训练、部署和管理AI视觉模型,上面也有多个轻量级的目标检测数据集(如手写数字、象棋、扑克牌、口罩等),比较适合单机或低配置训练。

我们选择口罩数据集,下载的时候选择格式为 YOLOv5 PyTorch(.txt 标签格式),下载后将数据集解压到与 YOLOv5 源码(如 yolov5-master)同级目录下,例如:mask_wearing_data。

1
2
3
4
5
6
7
mask_wearing_data/
├── train/
│ ├── images/ # 训练图片
│ └── labels/ # 对应txt标注文件
├── valid/ # 验证集(结构同train)
├── test/ # 测试集(结构同train)
└── data.yaml # 配置文件(改路径和类别数就用它)

配置文件(data.yaml)需要指定图像路径、标签路径、类别数(如口罩数据集为2类:戴口罩 / 未戴口罩)。

1
2
3
4
5
6
data.yaml/
train: ../mask_wearing_data/train/images
val: ../mask_wearing_data/valid/images
test: ../mask_wearing_data/test/images
nc: 2
names: ['mask', 'no-mask']

标签为 .txt 格式,第一列是类别编号,后四列是归一化后的边框坐标(YOLO格式)。标签文件需与图片文件名对应,如果想定义自己的数据集可以参考这个数据格式。

1
2
3
4
5
6
7
126202-untitled-design-13_jpg.rf.baa3d2e55d469ae5d5d4cd81c4603e1d.txt

0 0.855119825708061 0.18280632411067194 0.15250544662309368 0.3458498023715415
0 0.6726579520697168 0.48023715415019763 0.21895424836601307 0.5612648221343873
0 0.48257080610021785 0.35276679841897235 0.1895424836601307 0.44861660079051385
0 0.2587145969498911 0.5632411067193676 0.17973856209150327 0.41106719367588934
0 0.09477124183006536 0.41699604743083 0.18082788671023964 0.5217391304347826

YOLOv5 会在训练时自动生成缓存文件(如 .cache),提高后续训练速度。

2.下载预训练模型与配置参数

YOLOv5给我们提供了很多预训练模型,这些模型在参数量、计算量和性能上都有所区别,主要面向不同的应用场景和硬件需求。模型名称后缀越大,参数量越多,精度越高,但训练速度会越慢:

  • YOLOv5n​(Nano)
    • 参数量:约 1.9M
    • 计算量:约 4.5 GFLOPs
    • 适用场景:​移动端/嵌入式设备​(如 Jetson Nano、树莓派)或对实时性要求极高的场景。
  • YOLOv5s​(Small)
    • 参数量:约 7.2M
    • 计算量:约 16.5 GFLOPs
    • 适用场景:​轻量级通用检测,平衡速度与精度,适合边缘计算设备(如 Jetson TX2)。
  • YOLOv5m​(Medium)
    • 参数量:约 21.2M
    • 计算量:约 49.0 GFLOPs
    • 适用场景:​通用场景下的实时检测​(如视频监控、无人机航拍)。
  • YOLOv5l​(Large)
    • 参数量:约 46.5M
    • 计算量:约 109.1 GFLOPs
    • 适用场景:​服务器端或高性能 GPU,对精度要求较高的任务。
  • YOLOv5x​(X-Large)
    • 参数量:约 86.7M
    • 计算量:约 205.7 GFLOPs
    • 适用场景:​研究或高精度需求场景​(如医学图像分析、卫星图像检测)。

一般我们在学习和测试的时候建议先用 yolov5s,好处是小模型、十几 MB,训练速度快一些。

使用train.py训练当前自定义数据集mask_wearing_data命令:

1
python train.py --data ../mask_wearing_data/data.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --batch-size 16 --freeze 10 --epochs 10    

如果命令在运行的时候判断如果指定模型结构还没有下载预训练模型就会自动下载到项目根目录。

参数 作用
--data 指定数据配置文件 data.yaml,描述数据集路径、类别数、类别名称等信息
--cfg 指定模型结构配置文件 yolov5s.yaml,定义 YOLOv5s 的网络结构
--weight 使用yolov5s预训练权重,可加速收敛并提升精度
--batch-size 设置每个批次的样本数为 16,影响训练速度和显存占用
--freeze 冻结骨干网络的前 10 层,减少计算量
--epochs 训练轮数

最后的一个Epoch+summary日志:

2.数据处理

下面就来分析一下YOLOv5的代码,看看如何实现,如何加载数据集, 数据增强特别是 Mosaic 的实现逻辑,最终返回可用于训练的数据与标签

1.数据加载

utils/dataloaders.py 文件是 YOLOv5 的数据加载和处理模块,支持多种训练和推理场景,大致包含以下主要功能:

  1. 数据加载器​:支持图像/视频文件、实时视频流、屏幕截图及分类数据集的加载(LoadImages/LoadStreams/LoadImagesAndLabels等)
  2. 预处理与增强​:包含图像缩放、填充、Mosaic/MixUp增强、随机透视变换、HSV调整等,支持训练时动态数据增强
  3. 视频处理​:多线程处理本地视频、YouTube流和IP摄像头,实现高效帧读取
  4. 数据集管理​:提供缓存机制、数据集验证、自动分割(训练/验证/测试)及统计信息生成(HUBDatasetStats
  5. 分布式训练支持​:通过SmartDistributedSampler实现多GPU数据分配,确保数据加载与并行训练协调
  6. 格式转换工具​:自动转换图像路径到标签路径、处理EXIF信息、验证数据合法性(verify_image_label
  7. 性能优化​:采用内存/磁盘缓存、多线程加载、批处理优化(如四合一拼接)加速数据流水线
  8. 错误处理​:检测损坏图像/标签,记录异常并跳过无效数据,保障训练稳定性
  9. 分类任务扩展​:专用ClassificationDataset支持图像分类数据加载与增强
  10. 导出与兼容性​:支持数据集压缩、格式转换及统计报告生成,便于模型部署和迁移

如何取数据,来看看create_dataloader的各个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
utils/dataloaders.py
def create_dataloader(
path, # 数据集路径
imgsz, # 输入图像的尺寸(如640表示图像会被调整为 640x640)
batch_size, # 每个批次的样本数量
stride, # 模型的下采样步长(用于调整标签尺寸)
single_cls=False, # 是否将所有类别视为同一类别(用于单类别任务)
hyp=None, # 超参数字典(包含数据增强相关的参数)
augment=False, # 是否启用数据增强
cache=False, # 是否缓存图像到内存或磁盘(加速后续加载)
pad=0.0, # 图像填充比例(用于矩形训练)
rect=False, # 是否使用矩形批次(非方形图像,减少填充)
rank=-1, # 分布式训练的进程排名(-1表示非分布式)
workers=8, # 数据加载的子进程数
image_weights=False, # 是否根据类别分布对图像进行加权采样
quad=False, # 是否使用四合一拼接的数据加载(用于更大的批次)
prefix="", # 日志信息的前缀(用于调试)
shuffle=False, # 是否打乱数据顺序
seed=0, # 随机种子
):
"""Creates and returns a configured DataLoader instance for loading and processing image datasets."""
if rect and shuffle:
LOGGER.warning("WARNING ⚠️ --rect is incompatible with DataLoader shuffle, setting shuffle=False")
# 如果启用矩形训练(rect=True),则强制关闭shuffle(因为矩形批次需要固定顺序)
shuffle = False
#  确保在分布式训练中,数据集缓存(如.cache文件)只由rank=0的进程生成一次
with torch_distributed_zero_first(rank):
# 负责加载图像和标签,支持数据增强、缓存、矩形训练等功能,内部会解析标签文件(如 YOLO 格式的.txt),调整图像尺寸,并应用增强(如旋转、缩放等)
dataset = LoadImagesAndLabels(
path,
imgsz,
batch_size,
augment=augment, # augmentation
hyp=hyp, # hyperparameters
rect=rect, # rectangular batches
cache_images=cache,
single_cls=single_cls,
stride=int(stride),
pad=pad,
image_weights=image_weights,
prefix=prefix,
rank=rank,
)

batch_size = min(batch_size, len(dataset))
nd = torch.cuda.device_count() # number of CUDA devices
# 计算实际使用的子进程数nw,考虑 CPU 核心数、GPU 数量和批次大小
nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers])
# ​配置分布式采样器,分布式训练支持​,如果rank != -1(分布式训练),使用 SmartDistributedSampler 分配数据到各进程;否则不使用采样器
sampler = None if rank == -1 else SmartDistributedSampler(dataset, shuffle=shuffle)
# 选择DataLoader类型,使用标准DataLoader(支持动态权重更新);否则使用InfiniteDataLoader(无限循环迭代数据)
loader = DataLoader if image_weights else InfiniteDataLoader
# 通过generator控制数据加载的随机性,确保实验可复现
generator = torch.Generator()
generator.manual_seed(6148914691236517205 + seed + RANK)
return loader(
dataset,
batch_size=batch_size,
shuffle=shuffle and sampler is None,
num_workers=nw,
sampler=sampler,
# 是否丢弃不完整的批次(quad时启用)
drop_last=quad,
# 是否将数据固定到内存(加速 GPU 传输)
pin_memory=PIN_MEMORY,
# 处理批次数据的函数(quad时使用四合一拼接的collate_fn4)
collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn,
# 进程的初始化函数(seed_worker 设置子进程的随机种子)
worker_init_fn=seed_worker,
generator=generator,
), dataset

返回值:

  1. loader: 配置好的 DataLoader 实例,用于迭代训练数据
  2. dataset: 数据集对象,可用于获取样本数、类别数等元信息

2.数据增强

数据和标签已经准备好了接下来要读取每一个样本并进行处理(如数据增强)
整体流程如下:

  1. 调用 __getitem__ 加载图像
  2. 判断是否启用 Mosaic 增强
  3. 随机设定 Mosaic 中心点
  4. 拼接 4 张图像(1 当前索引 + 3 随机图)
  5. 将图像和标签重新组合,作为一个新的训练样本返回

1.__getitem__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __getitem__(self, index):  
"""Fetches the dataset item at the given index, considering linear, shuffled, or weighted sampling."""
# 采样策略适配,根据预设的采样策略(线性/随机/加权)转换索引,支持多种数据加载模式
index = self.indices[index] # linear, shuffled, or image_weights

hyp = self.hyp
# Mosaic增强,概率性启用Mosaic
if mosaic := self.mosaic and random.random() < hyp["mosaic"]:
# 加载4图拼接的大图及标签
img, labels = self.load_mosaic(index)
shapes = None

# 以hyp["mixup"]概率叠加另一张Mosaic图,增强目标混合效果
if random.random() < hyp["mixup"]:
img, labels = mixup(img, labels, *self.load_mosaic(random.choice(self.indices)))

else:
# Load image
img, (h0, w0), (h, w) = self.load_image(index)
# Letterbox 调整
shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape
img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling

labels = self.labels[index].copy()
# 标签坐标转换
if labels.size: # normalized xywh to pixel xyxy format
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])

if self.augment:
# 数据增强:随机透视变换(旋转/缩放/平移/剪切/透视)(opencv实现)
img, labels = random_perspective(
img,
labels,
degrees=hyp["degrees"],
translate=hyp["translate"],
scale=hyp["scale"],
shear=hyp["shear"],
perspective=hyp["perspective"],
)

# 标签归一化处理
nl = len(labels) # number of labels
if nl:
labels[:, 1:5] = xyxy2xywhn(labels[:, 1:5], w=img.shape[1], h=img.shape[0], clip=True, eps=1e-3)

if self.augment:
# 调用第三方库实现多样化增强(如模糊、噪声、仿射变换等)
img, labels = self.albumentations(img, labels)
nl = len(labels) # update after albumentations

# 随机调整HSV通道,模拟光照、饱和度变化
augment_hsv(img, hgain=hyp["hsv_h"], sgain=hyp["hsv_s"], vgain=hyp["hsv_v"])

# 上下随机翻转
if random.random() < hyp["flipud"]:
img = np.flipud(img)
if nl:
labels[:, 2] = 1 - labels[:, 2]

# 左右随机翻转
if random.random() < hyp["fliplr"]:
img = np.fliplr(img)
if nl:
labels[:, 1] = 1 - labels[:, 1]

# 随机遮挡区域增强鲁棒性
# labels = cutout(img, labels, p=0.5)
# nl = len(labels) # update after cutout

# 数据格式标准化​
labels_out = torch.zeros((nl, 6))
if nl:
# 标签张量化
labels_out[:, 1:] = torch.from_numpy(labels)

# ​图像通道处理
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
img = np.ascontiguousarray(img)

# (图像张量, 标签张量, 图像路径, 原始与调整后的尺寸信息)
return torch.from_numpy(img), labels_out, self.im_files[index], shapes

__getitem__核心作用:

  1. 动态数据增强​:通过Mosaic、MixUp、空间/颜色变换等提升模型泛化能力
  2. 多策略采样​:支持多种索引分配方式,适配不同训练需求
  3. 坐标系统一​:确保标签在不同增强步骤后仍保持归一化格式
  4. 格式标准化​:输出直接适配模型训练的Tensor格式

2.Mosaic 增强

load_mosaic是用于实现Mosaic数据增强的核心函数,其主要作用是将四张图像拼接成一张大图,并调整对应的标注信息,以提升模型训练时的数据多样性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def load_mosaic(self, index):  # index(当前样本的索引)
"""Loads a 4-image mosaic for YOLOv5, combining 1 selected and 3 random images, with labels and segments."""
labels4, segments4 = [], [] # 存储四张图像的标注和分割信息
s = self.img_size # 输入图像的尺寸(如640x640)
yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border) # 随机生成在[-border, 2*s + border]范围内的马赛克中心点坐标

# 生成4张图的索引:1张当前索引 + 3张随机索引
indices = [index] + random.choices(self.indices, k=3)
random.shuffle(indices) # 打乱顺序,位置随机

# 遍历处理每张图
for i, index in enumerate(indices):
# 加载图像和尺寸 (h,w)
img, _, (h, w) = self.load_image(index)

# === 计算当前图像在大图中的放置位置 ===
if i == 0: # 左上角
# 创建2s x 2s的基图,初始填充为114(灰色)
img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)

# 大图中的坐标范围(可能被裁剪)xmin, ymin, xmax, ymax (large image)
x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc
# 原图中的对应裁剪区域 xmin, ymin, xmax, ymax (small image)
x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h
elif i == 1: # 右上角
x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
elif i == 2: # 左下角
x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
elif i == 3: # 右下角
x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

# 将原图区域img[y1b:y2b, x1b:x2b] 粘贴到大图img4[y1a:y2a, x1a:x2a]
img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]

# 计算坐标偏移量(用于调整标注框)
padw = x1a - x1b # x方向偏移
padh = y1a - y1b # y方向偏移

# === 处理标注信息 ===
labels, segments = self.labels[index].copy(), self.segments[index].copy()
if labels.size:
# 将归一化的xywh转换为大图中的绝对坐标xyxy
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh)
# 调整分割点坐标
segments = [xyn2xy(x, w, h, padw, padh) for x in segments]

labels4.append(labels) # 收集标签
segments4.extend(segments) # 收集分割点

# === 后处理 ===
# 合并所有标签 (shape=[N, 5])
labels4 = np.concatenate(labels4, 0)

# 裁剪坐标到[0, 2s]范围内,防止越界
for x in (labels4[:, 1:], *segments4):
np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective()
# img4, labels4 = replicate(img4, labels4) # replicate

# 数据增强:随机复制粘贴目标(根据概率)
img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp["copy_paste"])

# 数据增强:随机透视变换(旋转/缩放/平移/剪切/透视)(opencv实现)
img4, labels4 = random_perspective(
img4,
labels4,
segments4,
degrees=self.hyp["degrees"],
translate=self.hyp["translate"],
scale=self.hyp["scale"],
shear=self.hyp["shear"],
perspective=self.hyp["perspective"],
border=self.mosaic_border,
) # border to remove

# 拼接后的图像img4和对应的标注labels4
return img4, labels4

3.网络结构

从输入层到输出层,讲解网络各层及结构的逻辑 会用清晰的“主线逻辑”串联复杂结构

1.网络结构可视化

如果想要更直观的查看模型结构,可以使用模型可视化工具,比如Netron,但如果直接导入yolov5s.pt 模型时,展示的结构会不完整,只显示模块名称,看不出实际的数据流动细节,这时需要将pt格式转化为onnx格式。

ONNX(Open Neural Network Exchange)​​ 是一种开放格式,用于表示和交换深度学习模型。它的核心作用是让不同框架(如PyTorch、TensorFlow)训练的模型能够互相转换和运行。

1.ONNX模型导出

1
2
3
4
5
# 先安装
pip install onnx

# 转换成onnx文件
python export.py --weights yolov5s.pt --include onnx

2.ONNX模型可视化

Netron支持多种方式可视化,我们可以通过浏览器地址Netron加载.onnx模型:

2.网络结构配置解读

1.核心模块

1.Conv模块(标准卷积模块)

结构:Conv → BatchNorm → SiLU(激活函数)
作用:

  • 提取局部特征(边缘、纹理、角点等)
  • 下采样(如果步长 > 1)
  • 控制通道数量,压缩或扩展特征图维度
2.C3模块(残差模块)

结构:

1
2
3
4
       ┌────────────┐
input──┴─┐ ┌─┴─────┐
└─> Bottleneck ──┤
× N次 └──> Concat + Conv → output
  • 输入被一分为二
  • 一部分走多个 Bottleneck 残差单元(默认含卷积 + 残差连接)
  • 一部分不变
  • 最后两者 Concatenate → 再接个卷积融合
    作用:
  • 提高特征提取能力
  • 控制模型计算量(参数量比 ResNet 少)
  • 保留不同路径的特征信息(浅+深)
3.SPPF模块(Spatial Pyramid Pooling - Fast)

结构:

1
2
3
4
5
6
7
8
9
input ──────────────┐
↓ │
MaxPool(k=5) │
↓ │
MaxPool(k=5) │
↓ │
MaxPool(k=5) │
↓ ↓
└─────Concat────→ Conv → output
  • 连续做 3 次相同核大小的最大池化
  • 所得多个特征图拼接
  • 最后再通过卷积融合
    作用:
  • 扩展感受野:融合不同尺度空间上下文信息
  • 增强特征表达:尤其适合大目标或上下文强依赖的情况
4.Concat(特征拼接)

结构:
Concat 模块没有卷积,它只是把来自不同层的特征图在通道维度拼接在一起
作用:

  • 融合上采样后的特征图和 backbone 中较浅层(如 P3/P4)的特征
  • 提高模型对不同尺度目标的检测能力
5.上采样模块(nn.Upsample)

结构:
PyTorch 内置上采样模块

1
nn.Upsample(scale_factor=2, mode='nearest')

作用:
将特征图放大(恢复空间分辨率),常用于:

  • 将深层(小尺寸)特征图上采样,和浅层(大尺寸)特征图进行拼接
  • 类似 U-Net 中的上采样路径
5.Detect

为每个网格(每个 anchor)预测:

  • 边界框坐标(x, y, w, h)
  • 置信度(objectness)
  • 类别概率(cls)

2.网络结构

YOLOv5s(small 版本)网络结构,包括超参数、骨干网络(backbone)和检测头(head):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
models/yolov5s.yaml/
# 1. 超参数配置Parameters
nc: 80 # 分类数量(COCO数据集中为80类)
depth_multiple: 0.33 # 模块深度缩放因子(用于控制网络深度)
width_multiple: 0.50 # 通道宽度缩放因子(用于控制网络宽度)
anchors: # 不同特征层的锚框设置
- [10, 13, 16, 30, 33, 23] # P3/8
- [30, 61, 62, 45, 59, 119] # P4/16
- [116, 90, 156, 198, 373, 326] # P5/32

# depth_multiple和width_multiple控制模型轻量化,S版用的是最小的系数,表示较浅且窄的网络结构
# anchors是先验框的大小列表,每行表示一个输出尺度(3个)

# YOLOv5 v6.0 backbone(主干网络)
# 主干网络负责提取特征,基本上是由卷积层(Conv)、C3模块、SPPF模块组成
backbone:
# [from, number, module, args]
# from: 当前层的输入来自哪一层(-1 表示上一层)
# number: 当前模块重复几次(比如 C3 模块可重复多次)
# module: 模块名称,比如 Conv, C3, SPPF
# args: 模块的参数,具体由模块类型决定
[
[-1, 1, Conv, [64, 6, 2, 2]], # 通道64 卷积6*6 步长2 padding2
[-1, 1, Conv, [128, 3, 2]], # 通道128 卷积3*3 步长2
[-1, 3, C3, [128]], # 通道128
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]],
[-1, 1, SPPF, [1024, 5]], # 通道1024 池化核 5
]

# YOLOv5 v6.0 head
# head作用:将主干网络(backbone)提取到的特征图转化为目标检测的最终结果:即边界框、置信度、以及每个类别的概率
head: [
[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, "nearest"]], # 使用最近邻插值方法特征图尺寸扩大2倍
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 输出通道数512 不使用残差连接

[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, "nearest"]],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)

[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)

[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)

[[17, 20, 23], 1, Detect, [nc, anchors]], # nc分类数量 anchors锚框
]

3.模型解析

1.模型构建

DetectionModel.__init__

1
def __init__(self, cfg="yolov5s.yaml", ch=3, nc=None, anchors=None)
  • 加载并解析 YAML 配置文件,构建模型字典 self.yaml
  • 如果传入 nc 或 anchors 会覆盖配置文件中的默认值
  • 调用 parse_model(…) 来解析模型结构(构建 backbone、neck、head 等)
  • 设置类别名 self.names,默认是 [0, 1, …, nc-1]
  • 若最后一层是 Detect 或 Segment,还会:
    • 构建 stride(下采样倍数)
    • 调整 anchors(除以 stride)
    • 初始化偏置项 self._initialize_biases()
  • 初始化网络权重:initialize_weights(self)
  • 打印模型信息:self.info()
2.前向传播阶段

调用 model(x) 会触发:

1
def forward(self, x, augment=False, profile=False, visualize=False)
  • 默认进入 _forward_once(x)
  • 如果 augment=True,则走增强推理 _forward_augment(x) 分支(多尺度+翻转)
  • profile 和 visualize 是可选功能:性能分析、特征可视化
3.forward_once
1
def _forward_once(self, x, profile=False, visualize=False)
  • 遍历 self.model 中的每一层模块
  • 如果 m.f != -1,说明当前模块依赖前面某一层的输出
  • 执行前向传播:x = m(x)
  • 如果 m.i in self.save,就将输出 x 保存到 y 中
  • 如果 visualize=True,还会进行特征可视化
    最终返回最后一层输出 x,对于 Detect/Segment 模块,输出格式如下:
1
2
3
4
# Detect:
return (batch, num_predictions, 85) -> (x, y, w, h, obj_conf, class_probs)
# Segment:
return (x, proto) or (x0, proto, x1) depending on mode

最终返回最后一层输出 x,对于 Detect/Segment 模块,输出格式如下:

4.Detect/Segment 模块解析

Detect 是检测 head,会根据 anchor/grid 解码预测框:

  • 分类数 nc,每个输出通道为 no = nc + 5(中心、宽高、置信度、分类概率)
  • anchor/grid 是在首次前向推理中动态计算的
  • 推理时输出 (batch, anchors × grid_x × grid_y, no)

Segment 继承自 Detect,额外处理 mask 信息:

  • 输出通道为 no = 5 + nc + nm,其中 nm 是 mask 数量
  • 使用 Proto 结构生成原型掩膜
5.增强推理(_forward_augment)

通过缩放和翻转图像进行多尺度推理增强:

1
def _forward_augment(self, x):
  • 对每个缩放比例和翻转方向进行推理
  • 对输出结果反向还原:_descale_pred()
  • 拼接所有预测结果:torch.cat(…)
  • 修剪增强推理中的冗余部分:_clip_augmented(…)

4.训练流程与策略

1.训练流程

1.日志记录与文件保存​

YOLOv5 在训练过程中,不仅会在控制台输出训练信息,还会将大量有价值的训练信息保存到日志中。每次训练都会在runs/train下生成一个exp文件夹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
runs/train/exp4/
├── F1_curve.png # F1 分数随阈值变化的曲线
├── PR_curve.png # Precision-Recall 曲线,用于衡量模型在不同阈值下的性能
├── P_curve.png # Precision(查准率)随置信度阈值的变化曲线
├── R_curve.png # Recall(查全率)曲线
├── confusion_matrix.png # 混淆矩阵图,用于展示不同类别的预测混淆情况(分类任务中常用)
├── events.out.tfevents.xxx.xx # TensorBoard 记录文件
├── hyp.yaml # 训练超参数(如学习率、IoU 阈值、损失权重等)
├── labels.jpg # 展示训练集中标注框的分布情况
├── labels_correlogram.jpg # 类别之间的相关性热力图(适合多标签任务)
├── opt.yaml # 训练过程中的各种配置参数(命令行参数的备份)
├── results.csv # 每个epoch的指标记录表格
├── results.png # 包含loss、mAP、Precision、Recall等关键训练指标随epoch变化的曲线图
├── train_batch0.jpg # 训练集中部分图片及其标注框预览图
├── train_batch1.jpg # 训练集中部分图片及其标注框预览图
├── train_batch2.jpg # 训练集中部分图片及其标注框预览图
├── val_batch0_labels.jpg # 验证集图像及其真实标签框
├── val_batch0_pred.jpg # 验证集图像的模型预测结果
└── weights # 用于保存模型权重文件
├── best.pt # 验证集上效果最好的模型权重(val_loss 最低的)
└── last.pt # 训练过程中最后一次保存的模型权重(最后一个 epoch)

2. 模型加载与初始化​

训练开始前,YOLOv5 会进行一系列初始化设置:

  • 设置随机种子,保证结果可复现
  • 读取数据配置文件(如 data.yaml),确定类别数和路径
  • 检查数据集路径和配置是否正确,防止路径错误导致训练失败

3.模型构建与加载权重

模型的构建依赖配置文件(如 yolov5s.yaml),源码会根据结构定义逐层构建模型。
如果本地已经存在预训练模型(如 yolov5s.pt),则会优先加载本地模型;否则,会从 YOLOv5 的官方仓库自动下载。建议提前下载,以避免自动下载速度较慢。

4.迁移学习与冻结层设置

YOLOv5 支持迁移学习,即在预训练模型基础上进行微调。你可以选择是否“冻结”部分网络层(通常是 backbone 部分)来保留其预训练特征。
实践中发现:即便只用几十张图片,在不冻结层的情况下也能获得不错的检测效果。因此除非数据量特别小或训练不稳定,通常不需要特意冻结层。

5.梯度累积机制:nbs 参数的作用

YOLOv5 中引入了 梯度累积(Gradient Accumulation) 机制,主要通过 nbs 参数控制。
举个例子:

  • 如果 batch size 为 16,nbs 为 64,那么模型会累积 4 个 batch 的梯度再进行一次反向传播更新。
  • 优点:在显存受限的设备上模拟更大的 batch size,提高训练稳定性和效果。
    这个机制通过代码实现了变相扩大 batch size,有助于更稳定地更新模型权重。

6.优化器与学习率调度

优化器方面,YOLOv5 将模型参数分组处理(如偏置项、权重项分别优化),并支持如下优化策略:

  • 初始学习率、动量、权重衰减等参数可配置
  • 支持多种 学习率衰减策略(如余弦衰减 CosineLR)
  • 提供完整的学习率调度函数,借鉴了多个经典文献
    虽然这部分源码涉及的数学细节较多,但理解其核心目的即可——随着训练迭代,逐步降低学习率,提高模型稳定性与精度

2.训练策略

1.断点续训逻辑

YOLOv5 支持断点续训,便于训练中断后的恢复。其机制如下:

  1. 加载断点模型
    • 当我们指定了一个断点模型(如 –weights=weights/last.pt),YOLOv5 会自动加载上次训练时保存的所有状态(模型参数、优化器状态、学习率调度器状态、EMA等)。
  2. 自动判断是否继续训练
    • 假设我们上次训练了 150 个 epoch,现在使用 –epochs 100 启动新的训练。
    • YOLOv5 会检查当前模型的已训练轮数。如果已经超过 100(如已训练 150),则不会再训练;如果未达到(如当前为 80),则会 继续训练剩余的轮数(此例中为 20)。
  3. 额外保存模型(双重保险)
    • 加载断点模型后,YOLOv5 会额外保存一次模型权重。这一操作的初衷似乎是为了保险——防止覆盖原有模型。
    • 实测表明:加载模型并不会覆盖原始权重文件。这个多保存一次的逻辑目前看起来意义不大,反而增加了存储负担,不过可以理解为一种“冗余备份机制”。

2.图像尺寸校验逻辑(gs 检查)

在训练前,YOLOv5 会校验输入图像的尺寸是否符合网络结构要求。主要逻辑如下:

  • YOLOv5 的 backbone 下采样倍数为 32,因此输入图像尺寸必须能被 32 整除(如 640×640)。
  • gs(grid size) 就是该下采样因子。如果尺寸不满足要求,会报错或自动调整图像尺寸。
    这一检查确保了网络中卷积和特征图尺寸的兼容性,是非常基础但关键的一步。

3.EMA(滑动平均)机制的作用

YOLOv5 默认启用了 EMA(Exponential Moving Average)

  • 通过对模型参数的历史状态进行滑动平均,生成一个更稳定的权重;
  • EMA 权重会用于最终评估和保存(best.pt),因为其结果通常更平滑、鲁棒性更强。

5.总结

YOLOv5是一个高度工程优化的目标检测框架,核心特点包括:

  1. 工程友好​:模块化代码结构清晰,支持快速训练自定义数据集(只需规范数据格式并修改YAML配置)
  2. 高效训练​:集成Mosaic/MixUp数据增强、梯度累积(nbs参数)、EMA权重平均等技术,提升模型泛化能力
  3. 灵活部署​:提供Nano到XLarge多规格预训练模型,适配从嵌入式设备到服务器的不同场景
  4. 全流程支持​:训练中自动记录日志、可视化指标(如PR曲线)、保存最佳模型(best.pt),便于调试和部署