【YOLO系列07】YOLOv5详解——PyTorch时代的工程化典范

本文是YOLO系列博客的第七篇,深入解析YOLOv5的网络架构,包括Focus模块、CSP结构、自适应Anchor、完善的训练策略以及Ultralytics的工程化实现。

1. 引言

2020年6月,Ultralytics公司发布了YOLOv5。尽管没有正式论文,YOLOv5凭借其优秀的工程实现易用的API活跃的社区维护,成为目前最流行的目标检测框架之一。

项目信息

  • 作者:Glenn Jocher(Ultralytics)
  • 框架:PyTorch
  • 代码:https://github.com/ultralytics/yolov5
  • Star:40K+

2. YOLOv5的争议与定位

2.1 命名争议

YOLOv5发布时引发了社区争议:

  • 没有发表正式论文
  • 性能数据受质疑
  • 是否有资格称为"v5"

2.2 实际价值

尽管有争议,YOLOv5的价值不可否认:

优势 说明
工程化 完善的训练、评估、部署流程
易用性 简洁的API,快速上手
社区 活跃的维护和更新
多格式导出 ONNX、TensorRT、CoreML等
文档 详尽的使用文档

3. 网络架构总览

3.1 整体结构

检测头

Detect

三尺度输出

颈部

PANet

特征融合

骨干网络

Focus

CSPDarknet

SPPF

3.2 模型变体

YOLOv5提供5种规模的模型:

模型 深度倍数 宽度倍数 参数量 FLOPs mAP
YOLOv5n 0.33 0.25 1.9M 4.5G 28.0
YOLOv5s 0.33 0.50 7.2M 16.5G 37.4
YOLOv5m 0.67 0.75 21.2M 49.0G 45.4
YOLOv5l 1.00 1.00 46.5M 109.1G 49.0
YOLOv5x 1.33 1.25 86.7M 205.7G 50.7

缩放公式

channels=base_channels×width_multiple\text{channels} = \text{base\_channels} \times \text{width\_multiple}channels=base_channels×width_multiple
num_blocks=base_blocks×depth_multiple\text{num\_blocks} = \text{base\_blocks} \times \text{depth\_multiple}num_blocks=base_blocks×depth_multiple

4. Focus模块

4.1 设计动机

将空间信息重组到通道维度,在不丢失信息的情况下进行下采样。

4.2 工作原理

输出

Focus切片

输入

concat

640×640×3

patch 1

patch 2

patch 3

patch 4

320×320×12

Conv → 320×320×64

4.3 切片方式

将图像按照步长2进行切片:

class Focus(nn.Module):
    def __init__(self, c1, c2, k=1):
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k)

    def forward(self, x):
        # x: (b, c, h, w) -> (b, 4c, h/2, w/2)
        return self.conv(torch.cat([
            x[..., ::2, ::2],    # 左上
            x[..., 1::2, ::2],   # 右上
            x[..., ::2, 1::2],   # 左下
            x[..., 1::2, 1::2]   # 右下
        ], dim=1))

4.4 Focus vs 卷积

方式 输入 输出 信息保留
Conv 6×6/2 640×640×3 320×320×64 部分重叠
Focus 640×640×3 320×320×64 完全保留

注意:YOLOv5 v6.0后,Focus被替换为普通6×6卷积以提升部署兼容性。

5. CSP结构

5.1 C3模块

YOLOv5使用C3模块(CSP Bottleneck with 3 convolutions):

C3模块

输入

Conv 1×1

Conv 1×1

Bottleneck ×n

Concat

Conv 1×1

输出

5.2 实现代码

class Bottleneck(nn.Module):
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class C3(nn.Module):
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        return self.cv3(torch.cat([self.m(self.cv1(x)), self.cv2(x)], dim=1))

6. SPPF模块

6.1 与SPP对比

模块 结构 速度
SPP 并行多尺度池化 较慢
SPPF 串行多次5×5池化 快2倍

6.2 SPPF结构

输入

Conv

MaxPool 5×5

MaxPool 5×5

MaxPool 5×5

Concat

Conv

6.3 实现

class SPPF(nn.Module):
    def __init__(self, c1, c2, k=5):
        super().__init__()
        c_ = c1 // 2
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        y1 = self.m(x)
        y2 = self.m(y1)
        return self.cv2(torch.cat([x, y1, y2, self.m(y2)], dim=1))

7. 自适应Anchor

7.1 自动计算

YOLOv5在训练开始时自动为数据集计算最优Anchor:

def kmean_anchors(dataset, n=9, img_size=640, thr=4.0, gen=1000):
    """使用K-Means计算最优Anchor"""
    from scipy.cluster.vq import kmeans

    # 获取所有GT框的宽高
    shapes = np.concatenate([
        l[:, 3:5] * img_size for l in dataset.labels
    ], axis=0)

    # K-Means聚类
    anchors, _ = kmeans(shapes, n)

    # 按面积排序
    anchors = anchors[np.argsort(anchors.prod(1))]

    return anchors

7.2 Anchor分配

YOLOv5的默认Anchor(640×640输入):

尺度 特征图 Anchors (w, h)
P3/8 80×80 (10,13), (16,30), (33,23)
P4/16 40×40 (30,61), (62,45), (59,119)
P5/32 20×20 (116,90), (156,198), (373,326)

7.3 正样本分配

使用基于Anchor的匹配策略:

match=max⁡(wgtwanchor,wanchorwgt)<anchor_t\text{match} = \max\left(\frac{w_{gt}}{w_{anchor}}, \frac{w_{anchor}}{w_{gt}}\right) < \text{anchor\_t}match=max(wanchorwgt,wgtwanchor)<anchor_t

同时检查宽度和高度,anchor_t默认为4.0。

8. 损失函数

8.1 总损失

L=λboxLbox+λobjLobj+λclsLcls\mathcal{L} = \lambda_{box}\mathcal{L}_{box} + \lambda_{obj}\mathcal{L}_{obj} + \lambda_{cls}\mathcal{L}_{cls}L=λboxLbox+λobjLobj+λclsLcls

默认权重:λbox=0.05\lambda_{box}=0.05λbox=0.05λobj=1.0\lambda_{obj}=1.0λobj=1.0λcls=0.5\lambda_{cls}=0.5λcls=0.5

8.2 边界框损失

使用CIoU Loss:

Lbox=1−CIoU\mathcal{L}_{box} = 1 - \text{CIoU}Lbox=1CIoU

8.3 目标损失

使用BCE Loss,带有目标置信度:

Lobj=BCE(o^,o⋅IoU)\mathcal{L}_{obj} = \text{BCE}(\hat{o}, o \cdot \text{IoU})Lobj=BCE(o^,oIoU)

其中 IoU\text{IoU}IoU 是预测框与GT的IoU,作为软标签。

8.4 分类损失

使用BCE Loss(多标签):

Lcls=BCE(p^,p)\mathcal{L}_{cls} = \text{BCE}(\hat{p}, p)Lcls=BCE(p^,p)

8.5 平衡权重

不同尺度的损失使用不同权重:

尺度 权重
P3 (80×80) 4.0
P4 (40×40) 1.0
P5 (20×20) 0.4

小目标尺度权重更高,以平衡样本数量。

9. 数据增强

9.1 Mosaic

def load_mosaic(self, index):
    """4图Mosaic增强"""
    labels4 = []
    s = self.img_size

    # 随机中心点
    yc, xc = [int(random.uniform(s*0.5, s*1.5)) for _ in range(2)]

    # 随机选择3张其他图像
    indices = [index] + random.choices(range(len(self)), k=3)

    for i, idx in enumerate(indices):
        img, labels = self.load_image(idx)
        h, w = img.shape[:2]

        # 放置位置
        if i == 0:  # 左上
            x1a, y1a, x2a, y2a = max(xc-w, 0), max(yc-h, 0), xc, yc
            x1b, y1b, x2b, y2b = w-(x2a-x1a), h-(y2a-y1a), w, h
        # ... 其他位置类似

        img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]
        labels4.append(adjusted_labels)

    return img4, np.concatenate(labels4, 0)

9.2 MixUp

def mixup(img1, labels1, img2, labels2, alpha=0.5):
    """MixUp增强"""
    r = np.random.beta(alpha, alpha)
    img = (img1 * r + img2 * (1 - r)).astype(np.uint8)
    labels = np.concatenate([labels1, labels2], 0)
    return img, labels

9.3 Copy-Paste

def copy_paste(img, labels, segments, p=0.5):
    """Copy-Paste增强(需要分割标注)"""
    n = len(segments)
    if p and n:
        for j in random.sample(range(n), k=int(n * p)):
            # 随机缩放和翻转
            # 粘贴到新位置
            pass
    return img, labels

9.4 增强策略总结

增强方法 默认概率 作用
Mosaic 1.0 多图融合
MixUp 0.0 图像混合
Copy-Paste 0.0 实例粘贴
HSV增强 1.0 色彩变换
翻转 0.5 水平翻转
缩放 0.5 随机缩放
平移 0.1 随机平移

10. 训练策略

10.1 学习率调度

使用余弦退火(Cosine Annealing):

ηt=ηmin+12(ηmax−ηmin)(1+cos⁡(tTπ))\eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})(1 + \cos(\frac{t}{T}\pi))ηt=ηmin+21(ηmaxηmin)(1+cos(Ttπ))

10.2 Warmup

前3个epoch使用线性warmup:

# 学习率warmup
if ni < nw:
    xi = [0, nw]
    for j, x in enumerate(optimizer.param_groups):
        x['lr'] = np.interp(ni, xi, [warmup_bias_lr if j == 2 else 0.0, x['initial_lr'] * lf(epoch)])
        if 'momentum' in x:
            x['momentum'] = np.interp(ni, xi, [warmup_momentum, momentum])

10.3 EMA(指数移动平均)

class ModelEMA:
    def __init__(self, model, decay=0.9999):
        self.ema = deepcopy(model).eval()
        self.decay = decay

    def update(self, model):
        with torch.no_grad():
            for ema_p, p in zip(self.ema.parameters(), model.parameters()):
                ema_p.data.mul_(self.decay).add_(p.data, alpha=1 - self.decay)

10.4 训练超参数

# hyp.scratch-low.yaml
lr0: 0.01        # 初始学习率
lrf: 0.01        # 最终学习率 = lr0 * lrf
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 0.05        # 边界框损失权重
cls: 0.5         # 分类损失权重
obj: 1.0         # 目标损失权重
anchor_t: 4.0    # Anchor匹配阈值

11. 推理与部署

11.1 推理流程

from ultralytics import YOLO

# 加载模型
model = YOLO('yolov5s.pt')

# 推理
results = model('image.jpg')

# 获取结果
for result in results:
    boxes = result.boxes.xyxy      # 边界框
    scores = result.boxes.conf      # 置信度
    classes = result.boxes.cls      # 类别

11.2 模型导出

# 导出ONNX
python export.py --weights yolov5s.pt --include onnx

# 导出TensorRT
python export.py --weights yolov5s.pt --include engine --device 0

# 导出CoreML
python export.py --weights yolov5s.pt --include coreml

# 导出TFLite
python export.py --weights yolov5s.pt --include tflite

11.3 支持的导出格式

格式 用途
PyTorch 训练和研究
ONNX 跨平台部署
TensorRT NVIDIA GPU加速
CoreML iOS/macOS部署
TFLite Android/嵌入式
OpenVINO Intel硬件
PaddlePaddle 百度生态

12. 完整配置文件

12.1 模型配置 (yolov5s.yaml)

# YOLOv5s
nc: 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

backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],      # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],        # 1-P2/4
   [-1, 3, C3, [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]],          # 9
  ]

head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],
   [-1, 3, C3, [512, False]],

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

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

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

   [[17, 20, 23], 1, Detect, [nc, anchors]],
  ]

13. 实验结果

13.1 COCO val2017

模型 尺寸 mAP@0.5 mAP@0.5:0.95 速度(V100)
YOLOv5n 640 45.7 28.0 0.6ms
YOLOv5s 640 56.8 37.4 1.0ms
YOLOv5m 640 64.1 45.4 2.2ms
YOLOv5l 640 67.3 49.0 3.8ms
YOLOv5x 640 68.9 50.7 6.3ms

13.2 与其他模型对比

模型 参数量 mAP FPS
YOLOv4 64M 43.5 38
YOLOv5l 47M 49.0 50
EfficientDet-D2 8M 43.0 26

14. 总结

YOLOv5的成功在于:

方面 特点
架构 CSP + SPPF + PANet的成熟组合
工程 完善的训练、评估、部署流程
易用 简洁API,丰富文档
社区 持续更新,活跃维护
部署 多格式导出,跨平台支持

YOLOv5虽然没有论文,但其工程化实现为目标检测的实际应用树立了标杆。


参考资源

  • 官方仓库:https://github.com/ultralytics/yolov5
  • 官方文档:https://docs.ultralytics.com/yolov5/
  • 预训练模型:https://github.com/ultralytics/yolov5/releases

上一篇:【YOLO系列06】YOLOv4详解——Bag of Freebies与Bag of Specials
下一篇:【YOLO系列08】YOLOv6详解——美团的工业级检测器

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐