【YOLO系列06】YOLOv4详解——Bag of Freebies与Bag of Specials

本文是YOLO系列博客的第六篇,深入解析YOLOv4的网络架构,包括CSPDarknet53骨干网络、SPP、PAN特征融合、Mish激活函数、Mosaic数据增强等大量技术改进。

1. 引言

2020年4月,Alexey Bochkovskiy等人发布了YOLOv4,这是Joseph Redmon退出后首个正式的YOLO版本。YOLOv4系统性地总结和整合了近年来目标检测领域的各种技术,在COCO数据集上达到了43.5% AP(65.7% AP50),同时保持实时推理速度。

论文信息

  • 标题:YOLOv4: Optimal Speed and Accuracy of Object Detection
  • 作者:Alexey Bochkovskiy, Chien-Yao Wang, Hong-Yuan Mark Liao
  • 发表:arXiv 2020
  • 代码:https://github.com/AlexeyAB/darknet

2. 设计理念

2.1 Bag of Freebies (BoF)

定义:只增加训练成本、不增加推理成本的技术

类别 技术
数据增强 Mosaic、CutMix、MixUp、Random Erasing
正则化 DropBlock、Label Smoothing
损失函数 CIoU Loss、Focal Loss
其他 类别标签平滑、余弦退火学习率

2.2 Bag of Specials (BoS)

定义:增加少量推理成本、但显著提升性能的技术

类别 技术
注意力机制 SE、CBAM、SAM
特征融合 SPP、ASPP、BiFPN、PAN
激活函数 Mish、Swish
后处理 DIoU-NMS、Soft-NMS

2.3 YOLOv4技术选型

经过大量实验,YOLOv4选择的最优组合:

Bag of Freebies

Mosaic

自对抗训练

CutMix

Label Smoothing

检测头

YOLOv3头

CIoU Loss

DIoU-NMS

颈部网络

SPP

PAN

骨干网络

CSPDarknet53

Mish激活

Dropblock

3. CSPDarknet53骨干网络

3.1 CSP(Cross Stage Partial)结构

CSPNet的核心思想:将特征图分成两部分,一部分经过卷积,另一部分直接拼接

CSP Block

输入

Split

Part 1

Part 2

Dense Block

Concat

Transition

输出

优势

  • 减少计算量
  • 降低内存占用
  • 保持梯度流通畅
  • 提升特征复用

3.2 CSP残差块实现

class CSPBlock(nn.Module):
    def __init__(self, in_channels, out_channels, num_blocks):
        super().__init__()
        hidden = out_channels // 2

        # 主分支
        self.conv1 = ConvBNMish(in_channels, hidden, 1)
        self.blocks = nn.Sequential(*[
            DarknetBlock(hidden) for _ in range(num_blocks)
        ])
        self.conv2 = ConvBNMish(hidden, hidden, 1)

        # 跳跃分支
        self.conv3 = ConvBNMish(in_channels, hidden, 1)

        # 融合
        self.conv4 = ConvBNMish(hidden * 2, out_channels, 1)

    def forward(self, x):
        # 主分支
        x1 = self.conv1(x)
        x1 = self.blocks(x1)
        x1 = self.conv2(x1)

        # 跳跃分支
        x2 = self.conv3(x)

        # 融合
        x = torch.cat([x1, x2], dim=1)
        return self.conv4(x)

3.3 CSPDarknet53结构

阶段 输入通道 输出通道 残差块数 输出尺寸
Stem 3 32 - 608×608
Stage1 32 64 1 304×304
Stage2 64 128 2 152×152
Stage3 128 256 8 76×76
Stage4 256 512 8 38×38
Stage5 512 1024 4 19×19

3.4 与Darknet53对比

指标 Darknet53 CSPDarknet53
参数量 41.6M 27.6M
FLOPs 18.7B 13.1B
Top-1 77.2% 77.2%
速度 基准 快23%

参数减少34%,速度提升23%,精度不变!

4. Mish激活函数

4.1 定义

Mish(x)=x⋅tanh⁡(softplus(x))=x⋅tanh⁡(ln⁡(1+ex))\text{Mish}(x) = x \cdot \tanh(\text{softplus}(x)) = x \cdot \tanh(\ln(1 + e^x))Mish(x)=xtanh(softplus(x))=xtanh(ln(1+ex))

4.2 导数

dMishdx=exωδ2\frac{d\text{Mish}}{dx} = \frac{e^x \omega}{\delta^2}dxdMish=δ2exω

其中 ω=4(x+1)+4e2x+e3x+ex(4x+6)\omega = 4(x+1) + 4e^{2x} + e^{3x} + e^x(4x+6)ω=4(x+1)+4e2x+e3x+ex(4x+6)δ=2ex+e2x+2\delta = 2e^x + e^{2x} + 2δ=2ex+e2x+2

4.3 与其他激活函数对比

激活函数 公式 特点
ReLU max⁡(0,x)\max(0, x)max(0,x) 简单高效,有死区
Leaky ReLU max⁡(0.1x,x)\max(0.1x, x)max(0.1x,x) 缓解死区
Swish x⋅σ(x)x \cdot \sigma(x)xσ(x) 平滑,自门控
Mish x⋅tanh⁡(softplus(x))x \cdot \tanh(\text{softplus}(x))xtanh(softplus(x)) 更平滑,无上界

4.4 实现

class Mish(nn.Module):
    def forward(self, x):
        return x * torch.tanh(F.softplus(x))

# 或使用PyTorch内置
# F.mish(x)  # PyTorch 1.9+

5. SPP(Spatial Pyramid Pooling)

5.1 设计动机

增大感受野,聚合多尺度上下文信息。

5.2 结构

输入特征

MaxPool 5×5

MaxPool 9×9

MaxPool 13×13

Identity

Concat

Conv 1×1

5.3 实现

class SPP(nn.Module):
    def __init__(self, in_channels, out_channels, pool_sizes=[5, 9, 13]):
        super().__init__()
        self.conv1 = ConvBNMish(in_channels, in_channels // 2, 1)
        self.pools = nn.ModuleList([
            nn.MaxPool2d(k, stride=1, padding=k//2)
            for k in pool_sizes
        ])
        self.conv2 = ConvBNMish(in_channels // 2 * (len(pool_sizes) + 1), out_channels, 1)

    def forward(self, x):
        x = self.conv1(x)
        features = [x] + [pool(x) for pool in self.pools]
        x = torch.cat(features, dim=1)
        return self.conv2(x)

6. PAN(Path Aggregation Network)

6.1 与FPN对比

结构 特点
FPN 自顶向下,单向传递
PAN 双向传递,增强底层特征

6.2 PAN结构

自底向上 PAN

自顶向下 FPN

P5 19×19

上采样

+

C4

P4 38×38

上采样

+

C3

P3 76×76

下采样

+

N4 38×38

下采样

+

N5 19×19

6.3 YOLOv4中的PAN变体

YOLOv4将FPN中的加法操作改为拼接(Concat)

class PANet(nn.Module):
    def __init__(self):
        super().__init__()
        # 自顶向下
        self.upsample = nn.Upsample(scale_factor=2, mode='nearest')
        self.conv_up1 = ConvBNMish(1024, 512, 1)
        self.conv_up2 = ConvBNMish(512, 256, 1)

        # 自底向上
        self.downsample1 = ConvBNMish(256, 256, 3, stride=2)
        self.downsample2 = ConvBNMish(512, 512, 3, stride=2)

        # 特征处理
        self.conv_set1 = ConvSet(512, 256)
        self.conv_set2 = ConvSet(1024, 512)
        self.conv_set3 = ConvSet(1024, 512)

    def forward(self, c3, c4, c5):
        # 自顶向下
        p5 = self.conv_up1(c5)
        p5_up = self.upsample(p5)
        p4 = torch.cat([p5_up, c4], dim=1)
        p4 = self.conv_set1(p4)

        p4_up = self.upsample(self.conv_up2(p4))
        p3 = torch.cat([p4_up, c3], dim=1)
        p3 = self.conv_set2(p3)

        # 自底向上
        n3 = p3
        n3_down = self.downsample1(n3)
        n4 = torch.cat([n3_down, p4], dim=1)
        n4 = self.conv_set2(n4)

        n4_down = self.downsample2(n4)
        n5 = torch.cat([n4_down, p5], dim=1)
        n5 = self.conv_set3(n5)

        return n3, n4, n5

7. Mosaic数据增强

7.1 原理

将4张图像拼接成一张进行训练:

输出

Mosaic拼接

输入图像

图像1

图像2

图像3

图像4

四宫格拼接

拼接后图像

7.2 优势

  1. 增加数据多样性:单次训练看到4张图像
  2. 增加小目标:缩小后更多小目标
  3. 减少Batch Size需求:一张图包含多场景
  4. 增强BN统计:更丰富的batch统计

7.3 实现

def mosaic_augment(images, labels, img_size=608):
    """Mosaic数据增强"""
    s = img_size
    # 随机中心点
    xc, yc = [int(random.uniform(s*0.25, s*0.75)) for _ in range(2)]

    mosaic_img = np.zeros((s, s, 3), dtype=np.uint8)
    mosaic_labels = []

    for i, (img, label) in enumerate(zip(images, labels)):
        h, w = img.shape[:2]

        # 确定放置位置
        if i == 0:  # 左上
            x1, y1, x2, y2 = max(xc-w, 0), max(yc-h, 0), xc, yc
        elif i == 1:  # 右上
            x1, y1, x2, y2 = xc, max(yc-h, 0), min(xc+w, s), yc
        elif i == 2:  # 左下
            x1, y1, x2, y2 = max(xc-w, 0), yc, xc, min(yc+h, s)
        else:  # 右下
            x1, y1, x2, y2 = xc, yc, min(xc+w, s), min(yc+h, s)

        # 调整图像和标签
        mosaic_img[y1:y2, x1:x2] = resize_and_crop(img, x2-x1, y2-y1)
        adjusted_labels = adjust_labels(label, x1, y1, x2-x1, y2-y1, s)
        mosaic_labels.extend(adjusted_labels)

    return mosaic_img, mosaic_labels

8. CIoU损失

8.1 IoU系列演进

IoU→GIoU→DIoU→CIoU\text{IoU} \rightarrow \text{GIoU} \rightarrow \text{DIoU} \rightarrow \text{CIoU}IoUGIoUDIoUCIoU

8.2 CIoU定义

CIoU=IoU−ρ2(b,bgt)c2−αv\text{CIoU} = \text{IoU} - \frac{\rho^2(b, b^{gt})}{c^2} - \alpha vCIoU=IoUc2ρ2(b,bgt)αv

其中:

  • ρ(b,bgt)\rho(b, b^{gt})ρ(b,bgt):预测框和GT框中心点距离
  • ccc:最小包围框对角线长度
  • vvv:长宽比一致性

v=4π2(arctan⁡wgthgt−arctan⁡wh)2v = \frac{4}{\pi^2}\left(\arctan\frac{w^{gt}}{h^{gt}} - \arctan\frac{w}{h}\right)^2v=π24(arctanhgtwgtarctanhw)2

α=v(1−IoU)+v\alpha = \frac{v}{(1 - \text{IoU}) + v}α=(1IoU)+vv

8.3 CIoU损失

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

8.4 实现

def ciou_loss(pred_boxes, target_boxes):
    # IoU
    inter = intersection(pred_boxes, target_boxes)
    union = area(pred_boxes) + area(target_boxes) - inter
    iou = inter / union

    # 中心点距离
    pred_center = center(pred_boxes)
    target_center = center(target_boxes)
    rho2 = (pred_center - target_center).pow(2).sum(dim=-1)

    # 最小包围框对角线
    enclose = enclosing_box(pred_boxes, target_boxes)
    c2 = diagonal(enclose).pow(2)

    # 长宽比
    v = (4 / math.pi**2) * (
        torch.atan(target_boxes[..., 2] / target_boxes[..., 3]) -
        torch.atan(pred_boxes[..., 2] / pred_boxes[..., 3])
    ).pow(2)
    alpha = v / (1 - iou + v + 1e-7)

    ciou = iou - rho2 / c2 - alpha * v
    return 1 - ciou

9. 其他技术细节

9.1 DropBlock

与Dropout类似,但丢弃连续区域:

class DropBlock(nn.Module):
    def __init__(self, block_size=7, drop_prob=0.1):
        super().__init__()
        self.block_size = block_size
        self.drop_prob = drop_prob

    def forward(self, x):
        if not self.training:
            return x

        # 计算gamma
        gamma = self.drop_prob / (self.block_size ** 2)
        gamma *= x.shape[2] * x.shape[3] / (
            (x.shape[2] - self.block_size + 1) *
            (x.shape[3] - self.block_size + 1)
        )

        # 生成mask
        mask = (torch.rand_like(x[:, :1]) < gamma).float()
        mask = F.max_pool2d(mask, self.block_size, stride=1,
                          padding=self.block_size//2)
        mask = 1 - mask

        # 应用mask并归一化
        return x * mask * mask.numel() / mask.sum()

9.2 Label Smoothing

ysmooth=(1−ϵ)⋅yonehot+ϵKy_{smooth} = (1 - \epsilon) \cdot y_{onehot} + \frac{\epsilon}{K}ysmooth=(1ϵ)yonehot+Kϵ

其中 ϵ\epsilonϵ 通常取0.1,KKK 是类别数。

9.3 自对抗训练(SAT)

  1. 第一次前向传播,计算损失
  2. 反向传播到输入图像,生成对抗噪声
  3. 将对抗图像作为新的训练样本

9.4 DIoU-NMS

使用DIoU代替IoU进行NMS:

RDIoU=IoU−ρ2(M,Bi)c2R_{DIoU} = \text{IoU} - \frac{\rho^2(M, B_i)}{c^2}RDIoU=IoUc2ρ2(M,Bi)

可以更好地处理相邻但不重叠的框。

10. 完整网络架构

10.1 YOLOv4架构图

输入: 608×608×3
         ↓
CSPDarknet53 (Backbone)
    ├── Stage3 → 76×76×256
    ├── Stage4 → 38×38×512
    └── Stage5 → 19×19×1024
         ↓
SPP模块 → 19×19×512
         ↓
PANet (Neck)
    ├── P3: 76×76×128
    ├── P4: 38×38×256
    └── P5: 19×19×512
         ↓
YOLOv3 Head
    ├── 76×76×255 (小目标)
    ├── 38×38×255 (中目标)
    └── 19×19×255 (大目标)

10.2 输出格式

Output=B×(5+C)=3×(5+80)=255\text{Output} = B \times (5 + C) = 3 \times (5 + 80) = 255Output=B×(5+C)=3×(5+80)=255

11. 训练配置

11.1 超参数

超参数
输入尺寸 608×608
Batch Size 64(8×8 subdivisions)
Momentum 0.949
Weight Decay 0.0005
Learning Rate 0.001
Warmup 1000 iterations
总迭代 500500

11.2 学习率调度

  • 余弦退火(Cosine Annealing)
  • 在第400000和450000次迭代时降低

11.3 数据增强组合

  • Mosaic
  • 随机缩放(0.5~1.5)
  • 色彩抖动
  • 随机翻转
  • CutMix(概率0.5)

12. 实验结果

12.1 COCO test-dev

方法 mAP@0.5 mAP@0.5:0.95 FPS (V100)
YOLOv3 57.9 33.0 20
EfficientDet-D2 55.1 43.0 26
YOLOv4 65.7 43.5 38

12.2 消融实验

配置 mAP@0.5:0.95
Baseline (YOLOv3) 36.8
+ CSP 38.1
+ Mish 38.5
+ SPP 39.2
+ PAN 40.3
+ Mosaic 42.1
+ CIoU 43.0
+ SAT 43.5

13. 总结

YOLOv4通过系统性整合各种技术,实现了显著的性能提升:

类别 采用技术 效果
Backbone CSPDarknet53 + Mish 更强特征提取
Neck SPP + PAN 更好特征融合
Head YOLOv3 Head 多尺度检测
BoF Mosaic, CutMix, SAT 训练增强
BoS CIoU, DIoU-NMS 精度提升

YOLOv4证明了通过合理组合现有技术,可以达到SOTA性能。


参考文献

  1. Bochkovskiy, A., et al. “YOLOv4: Optimal Speed and Accuracy of Object Detection.” arXiv 2020.
  2. Wang, C. Y., et al. “CSPNet: A New Backbone that can Enhance Learning Capability of CNN.” CVPR 2020.
  3. Misra, D. “Mish: A Self Regularized Non-Monotonic Activation Function.” BMVC 2020.
  4. Zheng, Z., et al. “Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression.” AAAI 2020.

上一篇:【YOLO系列05】YOLOv3详解——多尺度预测与Darknet-53
下一篇:【YOLO系列07】YOLOv5详解——PyTorch时代的工程化典范

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐