写在前面:2015-2018 年是 YOLO 的"创世纪"。Joseph Redmon 用 3 年时间,从一个"无名小卒"变成目标检测领域的"颠覆者"。YOLOv1 第一次让检测跑到了 45FPS,YOLOv3 把单阶段精度推到了 76% mAP。今天我们一起回顾这 3 年,看看单阶段范式是如何诞生的。
YOLOv1-v3 就像漫威的"复仇者联盟 1-3"——奠定了英雄基础,但还没到巅峰。Redmon 是 YOLO 的"斯坦·李"。
目录
3.6 YOLOv2 的"细粒度特征"——Passthrough 层
一、YOLO 诞生前的目标检测江湖
1.1 2015 年前的"两阶段王朝"
graph TB
A["2014: R-CNN"] --> B["2015: Fast R-CNN"]
B --> C["2015: Faster R-CNN"]
C --> D["2016: YOLOv1<br/>⚡ 颠覆者"]
D --> E["2017: YOLOv2"]
E --> F["2018: YOLOv3"]
style A fill:#95E1D3,color:#000
style B fill:#95E1D3,color:#000
style C fill:#95E1D3,color:#000
style D fill:#FF6B6B,color:#fff
style E fill:#FFD93D,color:#000
style F fill:#6BCB77,color:#fff
1.2 两阶段检测的"速度瓶颈"
| 模型 | 年份 | FPS | mAP@50 | 速度瓶颈 |
|---|---|---|---|---|
| R-CNN | 2014 | 0.05 | 66% | 2000 个候选框独立 CNN |
| Fast R-CNN | 2015 | 0.5 | 70% | 仍是两段式 |
| Faster R-CNN | 2015 | 7 | 78% | RPN 串行 + RoI Pooling |
💡 效率技巧:2015 年前,"实时检测"是奢望——所有模型都跑不到 30FPS。
二、YOLOv1:第一次让检测"实时"
2.1 YOLOv1 的核心理念
graph LR
A["传统两阶段"] -->|"先猜后修"| B["2000 个候选框"]
C["YOLOv1 单阶段"] -->|"一次出结果"| D["7×7 网格"]
style A fill:#FF6B6B,color:#fff
style C fill:#6BCB77,color:#fff
2.2 YOLOv1 的"网格划分"
graph TB
A["输入图像 448×448"] --> B["划分 7×7 网格"]
B --> C["共 49 个格子"]
C --> D["每个格子预测"]
D --> D1["2 个边界框"]
D --> D2["20 个类别概率<br/>VOC 数据集"]
D --> D3["1 个置信度"]
D1 --> E["最终: 49×(2×5+20)=1470 维"]
D2 --> E
D3 --> E
style A fill:#4ECDC4,color:#fff
style E fill:#FF6B6B,color:#fff
2.3 YOLOv1 架构(基于 GoogLeNet)
graph TB
A["Input 448×448×3"] --> B["Conv 7×7<br/>64ch, stride 2"]
B --> C["MaxPool 2×2"]
C --> D["Conv 3×3<br/>192ch"]
D --> E["MaxPool"]
E --> F["GoogLeNet Inception ×1"]
F --> G["MaxPool"]
G --> H["GoogLeNet Inception ×3"]
H --> I["MaxPool"]
I --> J["GoogLeNet Inception ×5"]
J --> K["MaxPool"]
K --> L["Conv 3×3<br/>1024ch"]
L --> M["AvgPool"]
M --> N["FC 4096"]
N --> O["FC 1470<br/>(7×7×30)"]
style A fill:#4ECDC4,color:#fff
style O fill:#FF6B6B,color:#fff
2.4 YOLOv1 性能
| 数据集 | 类别数 | mAP | FPS (Titan X) |
|---|---|---|---|
| VOC2007 | 20 | 63.4% | 45 |
| VOC2012 | 20 | 57.9% | 45 |
YOLOv1 的 45FPS 是个什么概念?当时 Faster R-CNN 7FPS——YOLO 快了 6.4 倍。这就好比别人都在骑马,YOLO 突然开上了汽车。
2.5 YOLOv1 的"3 大问题"
graph TD
A["YOLOv1 问题"] --> B["1. 网格粗糙<br/>7×7 = 49 格<br/>小目标难检测"]
A --> C["2. 类别互斥<br/>一个格子只能一类"]
A --> D["3. 定位不准<br/>直接回归误差大"]
style A fill:#FF6B6B,color:#fff
style B fill:#FF6B6B,color:#fff
style C fill:#FF6B6B,color:#fff
style D fill:#FF6B6B,color:#fff
2.6 YOLOv1 损失函数
# YOLOv1 损失函数(多部分组成)
import torch
import torch.nn as nn
class YOLOv1Loss(nn.Module):
def __init__(self, S=7, B=2, lambda_coord=5, lambda_noobj=0.5):
super().__init__()
self.S = S # 网格数
self.B = B # 每格预测框数
self.lambda_coord = lambda_coord
self.lambda_noobj = lambda_noobj
def forward(self, predictions, targets):
# predictions: [B, S, S, (B*5+C)]
# targets: [B, S, S, (B*5+C)]
# 1. 坐标损失(仅正样本)
coord_loss = self.lambda_coord * torch.sum(
(predictions[..., 0:2] - targets[..., 0:2])**2 +
(predictions[..., 2:4] - torch.sqrt(targets[..., 2:4]))**2
)
# 2. 置信度损失
conf_loss_obj = torch.sum(
(predictions[..., 4] - targets[..., 4])**2 * targets[..., 4]
)
conf_loss_noobj = self.lambda_noobj * torch.sum(
(predictions[..., 4] - targets[..., 4])**2 * (1 - targets[..., 4])
)
# 3. 分类损失
cls_loss = torch.sum(
(predictions[..., 10:] - targets[..., 10:])**2 * targets[..., 4].unsqueeze(-1)
)
return coord_loss + conf_loss_obj + conf_loss_noobj + cls_loss
三、YOLOv2(YOLO9000):更强、更快、更包容
3.1 YOLOv2 的"7 大改进"
graph TB
A["YOLOv2 改进"] --> B1["1. BatchNorm<br/>mAP +2%"]
A --> B2["2. Hi-Res Classifier<br/>mAP +4%"]
A --> B3["3. Conv+Anchor<br/>召回率 +7%"]
A --> B4["4. 维度聚类<br/>更优 Anchor"]
A --> B5["5. 细粒度特征<br/>mAP +1%"]
A --> B6["6. 多尺度训练<br/>适应不同输入"]
A --> B7["7. Darknet-19<br/>更快骨干"]
style A fill:#FF6B6B,color:#fff
style B1 fill:#4ECDC4,color:#fff
style B2 fill:#95E1D3,color:#000
style B3 fill:#FFD93D,color:#000
style B4 fill:#6BCB77,color:#fff
style B5 fill:#9D4EDD,color:#fff
style B6 fill:#FFA500,color:#000
style B7 fill:#4ECDC4,color:#fff
3.2 YOLOv2 架构(Darknet-19)
graph TB
A["Input 416×416×3"] --> B["Conv 3×3<br/>32ch"]
B --> C["MaxPool"]
C --> D["Conv 3×3<br/>64ch"]
D --> E["MaxPool"]
E --> F["Conv 3×3<br/>128ch"]
F --> G["Conv 3×3<br/>64ch"]
G --> H["Conv 3×3<br/>128ch"]
H --> I["MaxPool"]
I --> J["×4 Conv blocks<br/>256ch"]
J --> K["×4 Conv blocks<br/>512ch"]
K --> L["×4 Conv blocks<br/>1024ch"]
L --> M["GlobalAvgPool"]
M --> N["输出"]
style A fill:#4ECDC4,color:#fff
style N fill:#FF6B6B,color:#fff
3.3 YOLOv2 性能
| 数据集 | 类别数 | mAP | FPS (Titan X) | 改进 |
|---|---|---|---|---|
| VOC2007 | 20 | 76.8% | 67 | vs v1: +13.4% |
| VOC2012 | 20 | 73.4% | 67 | vs v1: +15.5% |
| COCO | 80 | 21.6% | 40 | 首个 YOLO 多类别 |
3.4 YOLOv2 的"两大黑科技"
graph TD
A["YOLOv2 黑科技"] --> B["1. K-means 聚类 Anchor<br/>代替手工设计"]
A --> C["2. 联合训练<br/>YOLO9000 预测 9000+ 类"]
style A fill:#FF6B6B,color:#fff
style B fill:#4ECDC4,color:#fff
style C fill:#FFD93D,color:#000
YOLO9000 是个"狂妄"的名字——目标是检测 9000 个类别。虽然最终没完全做到,但体现了 Redmon 的雄心。
3.5 K-means 聚类 Anchor 实战
# kmeans_anchors.py
import numpy as np
from sklearn.cluster import KMeans
def kmeans_anchors(boxes, k=5):
"""
对 GT 框做 K-means 聚类,得到最优 Anchor
boxes: [N, 2] (w, h) 归一化的 GT 框
"""
# 1. K-means 聚类(用 IoU 作为距离)
kmeans = KMeans(n_clusters=k, random_state=0)
kmeans.fit(boxes)
anchors = kmeans.cluster_centers_
# 2. 按面积排序
areas = anchors[:, 0] * anchors[:, 1]
sorted_indices = np.argsort(areas)
anchors = anchors[sorted_indices]
return anchors
# 实际使用
# from ultralytics import yolo
# model = YOLO('yolov8.yaml')
# model.kmeans('/path/to/dataset.yaml', k=9) # 自动聚类 9 个 Anchor
3.6 YOLOv2 的"细粒度特征"——Passthrough 层
graph LR
A["26×26×512 浅层"] --> B["Passthrough 层<br/>重组为 13×13×2048"]
B --> C["Concat"]
C --> D["13×13×(1024+2048)"]
D --> E["输出"]
style A fill:#4ECDC4,color:#fff
style E fill:#FF6B6B,color:#fff
关键:把浅层 26×26 重组为 13×13×2048,与深层 13×13 拼接,保留小目标信息。
四、YOLOv3:单阶段检测的成熟之作
4.1 YOLOv3 的"3 大创新"
graph TB
A["YOLOv3 创新"] --> B1["1. Darknet-53<br/>更深骨干"]
A --> B2["2. 多尺度预测<br/>3 个尺度"]
A --> B3["3. 逻辑回归<br/>代替 Softmax"]
style A fill:#FF6B6B,color:#fff
style B1 fill:#4ECDC4,color:#fff
style B2 fill:#95E1D3,color:#000
style B3 fill:#FFD93D,color:#000
4.2 YOLOv3 架构(Darknet-53)
graph TB
A["Input 416×416×3"] --> B["Conv 32<br/>stride 2"]
B --> C["Residual ×1<br/>64ch"]
C --> D["Conv 64<br/>stride 2"]
D --> E["Residual ×2<br/>128ch"]
E --> F["Conv 128<br/>stride 2"]
F --> G["Residual ×8<br/>256ch"]
G --> H["Conv 256<br/>stride 2"]
H --> I["Residual ×8<br/>512ch"]
I --> J["Conv 512<br/>stride 2"]
J --> K["Residual ×4<br/>1024ch"]
K --> L["AvgPool"]
L --> M["FC 1000"]
style A fill:#4ECDC4,color:#fff
style M fill:#FF6B6B,color:#fff
4.3 YOLOv3 的"3 尺度预测"
graph TB
A["Backbone 输出"] --> P3["P3: 52×52<br/>检测小目标"]
A --> P4["P4: 26×26<br/>检测中目标"]
A --> P5["P5: 13×13<br/>检测大目标"]
P3 --> O1["输出 1<br/>85 维/Anchor"]
P4 --> O2["输出 2<br/>85 维/Anchor"]
P5 --> O3["输出 3<br/>85 维/Anchor"]
style A fill:#4ECDC4,color:#fff
style P3 fill:#6BCB77,color:#fff
style P4 fill:#FFD93D,color:#000
style P5 fill:#FF6B6B,color:#fff
4.4 YOLOv3 性能
| 数据集 | mAP@50 | mAP@50:95 | FPS (Titan X) |
|---|---|---|---|
| COCO | 82.3% | 31.0% | 65 |
| VOC2007 | 89% | - | 65 |
💡 效率技巧:YOLOv3 让 YOLO 第一次突破 80% mAP——这是单阶段范式的里程碑。
4.5 YOLOv3 的"逻辑回归"创新
# YOLOv3 不再用 Softmax 做多分类
# 改用:每个类别独立用逻辑回归(二分类)
import torch
import torch.nn as nn
class YOLOv3Head(nn.Module):
"""YOLOv3 风格的检测头(每类别独立 sigmoid)"""
def __init__(self, num_classes=80, num_anchors=3):
super().__init__()
self.nc = num_classes
self.na = num_anchors
# 每锚点输出: 5 (xywh+conf) + nc (类别概率)
self.no = 5 + num_classes
def forward(self, x):
# x: [B, na*(5+nc), H, W]
B, _, H, W = x.shape
# 重塑: [B, na, H, W, 5+nc]
x = x.view(B, self.na, self.no, H, W).permute(0, 1, 3, 4, 2).contiguous()
# 关键:分类用 sigmoid 而非 softmax
x[..., 5:] = x[..., 5:].sigmoid()
return x
为什么改用 Sigmoid?
- Softmax 强制类别互斥(一个物体只能是一类)
- Sigmoid 允许多标签分类(如"既是猫又是动物")
YOLOv3 的"逻辑回归"就像学生选课——Softmax 时代只能选一门,Sigmoid 时代可以多选。这就是 YOLOv3 灵活的关键。
4.6 YOLOv3 的"3 尺度"完整架构
graph TB
A["Input 416×416"] --> B["Darknet-53"]
B --> R1["Route 82 层"]
B --> R2["Route 94 层"]
B --> R3["Route 106 层"]
R3 --> DBL1["DBL ×5"]
DBL1 --> UPS1["上采样"]
UPS1 --> CAT1["+ R2"]
CAT1 --> DBL2["DBL ×5"]
DBL2 --> UPS2["上采样"]
UPS2 --> CAT2["+ R1"]
CAT2 --> DBL3["DBL ×5"]
DBL3 --> Y1["Y1: 13×13<br/>大目标"]
DBL2 --> DBL4["DBL ×5"]
DBL4 --> Y2["Y2: 26×26<br/>中目标"]
DBL1 --> DBL5["DBL ×5"]
DBL5 --> Y3["Y3: 52×52<br/>小目标"]
style A fill:#4ECDC4,color:#fff
style Y1 fill:#FF6B6B,color:#fff
style Y2 fill:#FFD93D,color:#000
style Y3 fill:#6BCB77,color:#fff
五、3 代的"技术师承"关系
5.1 YOLOv1 → v2 → v3 演进图
graph LR
A["YOLOv1<br/>2016"] -->|"+ 7 改进"| B["YOLOv2<br/>2017"]
B -->|"+ 3 创新"| C["YOLOv3<br/>2018"]
A --> A1["45 FPS<br/>63.4% mAP"]
B --> B1["67 FPS<br/>76.8% mAP"]
C --> C1["65 FPS<br/>82.3% mAP"]
style A fill:#FF6B6B,color:#fff
style B fill:#FFD93D,color:#000
style C fill:#6BCB77,color:#fff
5.2 关键改进"传"了多少
| 改进项 | YOLOv1 | YOLOv2 | YOLOv3 |
|---|---|---|---|
| 骨干 | GoogLeNet | Darknet-19 | Darknet-53 |
| Anchor | 无 | K-means 聚类 | 9 个固定 |
| 多尺度 | 无 | 1 尺度 | 3 尺度 |
| 分类 | Softmax | Softmax | Sigmoid |
| 残差 | 无 | 无 | 有 |
5.3 数据对比
graph TB
A["mAP 提升"] --> B["v1: 63.4%"]
A --> C["v2: 76.8% (+13.4%)"]
A --> D["v3: 82.3% (+5.5%)"]
style B fill:#95E1D3,color:#000
style C fill:#FFD93D,color:#000
style D fill:#6BCB77,color:#fff
💡 效率技巧:3 代 YOLO 累计提升 18.9% mAP——这就是技术迭代的力量。
六、Redmon 的"急流勇退"
6.1 2020 年的"告别 Twitter"
graph TB
A["Joseph Redmon"] --> B["2016: YOLOv1 震惊世界"]
A --> C["2017: YOLOv2 巅峰"]
A --> D["2018: YOLOv3 经典"]
A --> E["2020: 宣布退出 CV<br/>出于道德顾虑"]
style A fill:#FF6B6B,color:#fff
style B fill:#4ECDC4,color:#fff
style C fill:#FFD93D,color:#000
style D fill:#6BCB77,color:#fff
style E fill:#9D4EDD,color:#fff
6.2 Redmon 退出的原因
“我看着 YOLO 被用于军事、隐私侵犯,我无法继续。”
6.3 接力棒
graph LR
A["YOLOv3 (Redmon)"] --> B["YOLOv4 (Alexey Bochkovskiy)"]
B --> C["YOLOv5 (Ultralytics)"]
C --> D["YOLOv6-v10 (多家)"]
style A fill:#FF6B6B,color:#fff
style B fill:#FFD93D,color:#000
style C fill:#6BCB77,color:#fff
style D fill:#4ECDC4,color:#fff
Redmon 退出后,YOLO 像"复仇者联盟失去钢铁侠"——但其他英雄接过了大旗。YOLOv4-v10 都来了。
七、避坑指南:复现 YOLOv1-v3 的 5 个真实坑
坑 1:YOLOv1 损失函数权重设置错误
症状:训练时 loss 振荡不收敛。
原因:YOLOv1 损失中 λ_coord=5, λ_noobj=0.5 是关键。
解法:
# 严格按论文设置
lambda_coord = 5.0 # 坐标损失权重(高)
lambda_noobj = 0.5 # 负样本置信度损失权重(低)
坑 2:YOLOv2 Anchor 聚类失败
症状:聚类出来的 Anchor 形状诡异(一个极大一个极小)。
原因:用了欧氏距离而非 IoU 距离。
解法:
# 关键:用 1-IoU 作为距离度量
def iou_distance(box, centroids):
"""box: (w, h), centroids: (k, 2)"""
# 计算 1 - IoU
iou = compute_iou(box, centroids)
return 1 - iou
坑 3:YOLOv3 的 Sigmoid 没生效
症状:训练时分类概率总和不是 1,但应该用 sigmoid。
原因:代码里写成 softmax 或没激活。
解法:
# 关键:分类分支用 sigmoid,不用 softmax
x[..., 5:] = x[..., 5:].sigmoid() # 80 个独立概率
# 而不是 x[..., 5:] = F.softmax(x[..., 5:], dim=-1)
坑 4:多尺度训练时输入尺寸报错
症状:每 10 个 iter 改变输入尺寸,但维度不匹配。
原因:直接修改 input.shape 没用。
解法:
# 多尺度训练的正确姿势
import random
def train_iter():
input_size = random.choice([320, 352, 384, 416, 448, 480, 512, 544, 576, 608])
dataloader = DataLoader(dataset, batch_size=batch_size,
collate_fn=lambda x: collate(x, size=input_size))
return dataloader
坑 5:Darknet-53 训练时梯度爆炸
症状:训练 loss 突然变 NaN。
原因:53 层网络梯度链长,容易爆炸。
解法:
- 用 梯度裁剪:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) - 减小学习率
- 混合精度训练
⚠️ 避坑警告:YOLOv3 至今仍是工业级目标检测的"基础款"——很多工业项目直接用 YOLOv3。
八、总结:YOLOv1-v3 的历史地位
8.1 核心结论
graph TD
A["YOLOv1-v3 贡献"] --> B["1. 单阶段范式<br/>45FPS 开启实时"]
A --> C["2. 端到端训练<br/>一个 Loss 搞定"]
A --> D["3. 开源文化<br/>Darknet 框架"]
A --> E["4. 工业落地<br/>无数项目基石"]
style A fill:#FF6B6B,color:#fff
style B fill:#4ECDC4,color:#fff
style C fill:#95E1D3,color:#000
style D fill:#FFD93D,color:#000
style E fill:#6BCB77,color:#fff
8.2 5 大核心 Takeaway
- YOLOv1 是"破冰者":第一次让检测跑到了 45FPS,开启实时检测时代
- YOLOv2 是"工程化巅峰":7 大改进把 mAP 推到 76.8%,引入 K-means 聚类
- YOLOv3 是"经典款":Darknet-53 + 多尺度 + Sigmoid,至今仍是工业首选
- Redmon 是"急流勇退的孤胆英雄":出于道德考虑退出 CV,留下一段传奇
- YOLOv1-v3 是 YOLO 的"创世纪":后续所有版本都基于这 3 代奠基
8.3 一句话总结
YOLOv1-v3 是单阶段检测的"创世纪"——3 年时间,45FPS → 65FPS,63.4% → 82.3% mAP。 Redmon 用 3 篇论文,颠覆了目标检测 10 年的格局。 致敬这位"急流勇退"的英雄。
📌 文末三件套
【源码获取】
关注此公众号,后台回复「YOLO07」 获取本文全部代码(YOLOv1/v2/v3 损失函数 + Darknet-19/53 + K-means 聚类完整实现)。
【思考题】
Joseph Redmon 在 YOLOv3 之后宣布退出 CV 领域——原因是"被用于军事和隐私侵犯"。开源技术的伦理边界在哪里? 开发者是否应该为代码的最终用途负责?欢迎在评论区讨论。
【系列文章预告】
- ✅ 07 篇:YOLOv1-v3 技术演进详解(本文)
- ⏭️ 08 篇:YOLOv4-v5——CSPNet 时代的工程化巅峰
- ⏭️ 09 篇:YOLOv6-v7——RepVGG 与 ELAN 的推理加速
- ⏭️ 10 篇:YOLOv8-v10——Anchor-Free 与多模态的未来
- ⏭️ 后续 20 篇:行业应用、案例、训练部署避坑
标签:#YOLOv1 #YOLOv2 #YOLOv3 #Redmon #Darknet #单阶段检测 #YOLO创世纪
397




被折叠的 条评论
为什么被折叠?



