YOLOX 详解
YOLOX: Exceeding YOLO Series in 2021
论文: YOLOX: Exceeding YOLO Series in 2021 (2021)
作者: Zheng Ge, Songtao Liu, Feng Wang, Zeming Li, Jian Sun (Megvii / 旷视)
代码: Megvii/yolox
一句话总结:YOLOX = YOLOv5 结构 + FCOS 检测头 + OTA 标签分配。 它把 YOLO 系列从 anchor-based 推进到 anchor-free,引入解耦头与 SimOTA 动态标签分配,在 COCO 上以同等推理速度超越 YOLOv5。
一、概述
YOLOX(2021,旷视)是 YOLO 系列的一个重要转折点。它不是堆叠 trick,而是把检测范式从 anchor-based 切换到 anchor-free,并解决了随之而来的训练稳定性问题(靠解耦头 + SimOTA)。
核心改进三点:
- Anchor-free:去掉所有预定义 anchor,每个网格点直接预测 1 个框(类似 FCOS);
- Decoupled Head(解耦头):分类与回归分支分离(YOLOv3-v5 是耦合头);
- SimOTA 标签分配:基于最优传输(OT)的动态标签分配,替代传统的静态 IoU 阈值分配。
简单理解:
YOLOX = YOLOv5(5.0) 的结构 + FCOS 的检测头 + OTA 的标签分配。整体网络(backbone/neck)沿用 YOLOv5,关键改动在检测头与训练时的标签分配策略。
二、与 YOLO v1-v4 的区别 ⭐
这是理解 YOLOX 最直接的角度:它相对前几代改了什么、为什么改。
2.1 总览对比表
| 维度 | YOLOv1 (2016) | YOLOv2 (2017) | YOLOv3 (2018) | YOLOv4 (2020) | YOLOX (2021) |
|---|---|---|---|---|---|
| Anchor | ❌ 无(直接回归) | ✅ 5 个(K-means) | ✅ 9 个(3尺度×3) | ✅ 9 个 | ❌ 无(anchor-free) |
| 检测头 | 耦合(FC) | 耦合 | 耦合 | 耦合 | 解耦(cls/reg 分离) |
| 标签分配 | 网格中心点 | IoU 阈值 + 中心 | IoU 0.5/0.4 + ignore | IoU 阈值 | SimOTA(动态,最优传输) |
| 每位置预测框 | 2 | 5 | 3 | 3 | 1 |
| Backbone | Darknet-24 | Darknet-19 | Darknet-53 | CSPDarknet | CSPDarknet |
| Neck | 无 | Passthrough | FPN | PANet | PAFPN |
| 激活函数 | LeakyReLU | LeakyReLU | LeakyReLU | Mish | SiLU |
| 分类损失 | SSE | SSE | BCE | BCE | BCE |
| 回归损失 | SSE | SSE | SSE | CIoU | IOU loss (1-IoU²) |
| 数据增强 | 无 | 无 | 无 | Mosaic | Mosaic + MixUp |
2.2 关键区别详解
① Anchor-free:去掉 anchor 这个”脚手架”
- v2-v4 都是 anchor-based:每个网格点预设 3-5 个 anchor(不同尺寸/宽高比),预测相对 anchor 的偏移。anchor 的尺寸/数量/宽高比是敏感超参,换数据集要重新聚类(K-means)。
- YOLOX 改为 anchor-free:每个网格点只预测 1 个框,直接回归 $(x, y, w, h)$(无 anchor 偏移)。好处:① 去掉 anchor 超参,泛化更简单;② 减少 NMS 输入量(框数减少 3-9 倍)。
- 这与 FCOS 思路一致,但 YOLOX 仍保留 objectness 分支(FCOS 用 centerness)。
② Decoupled Head:分类与回归解耦
- v3-v4 耦合头:cls、reg、obj 共享同一组卷积,最后用 1×1 卷积切出不同通道。
- YOLOX 解耦头:特征图先经 1×1 stem 降维到 256,再分两个独立分支(各自 2 层 3×3 卷积),cls 分支预测类别、reg 分支预测框+objectness。
- 为什么解耦:作者实验发现,anchor-free + OTA 训练时,耦合头收敛慢且易冲突(cls 和 reg 的梯度互相干扰);解耦后训练稳定、收敛快。代价是多一点计算量。
③ SimOTA:动态标签分配
- v1:物体中心落在哪个网格,那个网格负责(最简单)。
- v2-v4:静态 IoU 阈值–IoU>0.5 为正、<0.4 为负、中间 ignore(v3)。每个 GT 的正样本数固定且依赖人为阈值。
- YOLOX SimOTA:把标签分配建模为最优传输问题,每个 GT 动态分配 k 个正样本(k 由预测质量决定:高质量预测的 GT 多分几个正样本,低质量的少分)。详见第五章。
④ 数据增强与训练策略
- Mosaic + MixUp:v4 用 Mosaic,YOLOX 额外加 MixUp(改进了实现,类似 copy-paste)。
- 最后 15 epoch 关闭 Mosaic/MixUp:Mosaic 会截断 bbox、偏离真实分布,最后阶段关掉增强让模型在真实分布上精修(涨点明显)。
⑤ 激活函数:SiLU(= Swish)
- v3 用 LeakyReLU,v4 用 Mish,YOLOX 用 SiLU($x \cdot \sigma(x)$)。SiLU 在硬件上比 Mish 快,效果相近。
2.3 YOLOX 与 YOLOv1 的渊源:anchor-free 的回归
一个重要的观察:YOLOX 在 anchor-free 这件事上,本质是回到了 YOLOv1 的思路。
1 | YOLOv1 (2016): anchor-free,每个网格直接预测框 |
v1 的 anchor-free 失败不是因为思路错,而是配套没跟上。YOLOX 补齐了:
| v1 的短板 | YOLOX 怎么补 |
|---|---|
| 只有 1 层 7×7,无多尺度 | FPN 3 级(80×80 / 40×40 / 20×20) |
| 每 GT 只 1 个正样本(中心网格) | SimOTA 动态分配 ~3-5 个 |
| SSE 损失,对大小框不公 | IOU loss + BCE |
| 无数据增强 | Mosaic + MixUp |
| 耦合头 | 解耦头 |
一句话:YOLOX = YOLOv1 的 anchor-free 思路 + v3 的 FPN + SimOTA + 解耦头。它是”v1 的回归”,带着 v2-v5 积累的技术进步。
三、网络架构

1 | 输入图像 |
- Backbone:CSPDarknet,同 YOLOv4/v5,取 $C_3, C_4, C_5$ 三级特征;
- Neck:PAFPN(路径聚合 FPN),自顶向下 + 自底向上双向融合,比单向上行的 FPN 更强;
- Head:解耦头,3 个尺度(stride 8/16/32),每个网格点预测 1 个框(anchor-free)。
四、Decoupled Head(解耦头)
4.1 结构

neck 传进来的特征图先经 1×1 stem 降维到 256,再分两个并行分支:
- 分类分支:2×(3×3 Conv + SiLU)→ 1×1 Conv → 输出
num_classes个类别分数; - 回归分支:2×(3×3 Conv + SiLU)→ 1×1 Conv → 输出 4(框坐标)+ 1(objectness)。
4.2 为什么解耦

作者在 YOLOv3 上做实验:耦合头 vs 解耦头。结果显示解耦头收敛更快、最终精度更高,尤其在引入 anchor-free 和 OTA 训练时,耦合头训练不稳定。代价是解耦头多了几个卷积层、计算量略增,但对整体速度影响不大。
4.3 Anchor-free 到底怎么做的 ⭐
这一节对比 anchor-based(YOLOv3/v4/v5)与 anchor-free(YOLOX),把”怎么就 anchor-free 了”讲透。
4.3.1 先搞懂:anchor 是什么、为什么有它
Anchor(先验框) 是预先定义在网格点上的”参考框”,有固定的宽高。以 YOLOv3 为例,每个网格点放 3 个 anchor(通过 K-means 聚类 COCO 数据集得到),比如 P3 层的 anchor 是 (10×13), (16×30), (33×23)。
为什么要有 anchor:网络不直接预测框的绝对大小,而是预测相对 anchor 的偏移量。anchor 提供一个”起始猜测”,网络只需微调–这比从零学大小容易收敛。
YOLOv3 的解码公式(anchor-based):
1 | bx = (sigmoid(tx) × 2 - 0.5 + grid_x) × stride # 中心点 |
关键:anchor_w 是预设的固定值(如 10、16、33…),网络只学 tw(缩放比的对数)。框的大小 = anchor 大小 × exp(tw)。anchor 越接近真实物体大小,网络越好学。
4.3.2 YOLOX 怎么去掉 anchor
YOLOX 的做法很简单但很关键:把 anchor 去掉,每个网格点只预测 1 个框,直接从网格点回归绝对坐标。
YOLOX 的解码公式(anchor-free,源码 get_output_and_grid):
1 | # 源码 yolo_head.py: get_output_and_grid() |
翻译成公式:
1 | bx = (tx + grid_x) × stride # 中心点 (无 sigmoid,raw output + grid) |
4.3.3 两者放一起对比
| YOLOv3(anchor-based) | YOLOX(anchor-free) | |
|---|---|---|
| 每网格点预测数 | 3 个(3 个 anchor 各一个) | 1 个(无 anchor) |
| 中心点解码 | (sigmoid(tx)×2-0.5 + grid) × stride |
(tx + grid) × stride |
| 宽高解码 | anchor_w × exp(tw) ★有 anchor |
exp(tw) × stride ★无 anchor |
| 宽高的”起点” | anchor 的宽高(预设固定值) | stride(仅尺度因子,非框先验) |
| 网络学什么 | 相对 anchor 的缩放比 tw |
绝对大小 tw(exp(tw)×stride 即真实像素宽) |
| 需要 anchor 超参? | ✅ 需要(尺寸/比例/数量,K-means 聚类) | ❌ 不需要 |
| 总预测框数 | (80²+40²+20²)×3 = 25,200 | (80²+40²+20²)×1 = 8,400(少 3 倍) |
核心区别一句话:v3 的
bw = anchor_w × exp(tw)里 anchor_w 是预设的”拐杖”;YOLOX 的bw = exp(tw) × stride里没有 anchor_w,网络直接学绝对大小。去掉 anchor_w 这一项,就是 anchor-free。
4.3.4 为什么去掉 anchor 还能work
你可能会问:没有 anchor 提供”起始猜测”,网络怎么知道框该多大?
- FPN 已提供多尺度:P3(stride=8)检测小物体(8
64px),P5(stride=32)检测大物体(128512px)。不同层的 stride 天然覆盖不同尺度,替代了多组 anchor 尺寸的作用; - exp(tw) × stride 可以表示任意大小:P3 上 exp(tw)≈1 → 8px,exp(tw)≈4 → 32px;P5 上 exp(tw)≈5 → 160px,exp(tw)≈10 → 320px。范围足够覆盖;
- 网络有足够容量学习:从数据中学”这个位置的物体大概多大”,不需要人为预设。代价是前期收敛稍慢,但最终精度不输甚至超越 anchor-based。
4.3.5 源码关键:那个 “1”
源码 get_output_and_grid 中有一行决定 anchor-free 的关键:
1 | # YOLOX: view 里的 "1" = 每个网格点 1 个预测(anchor-free) |
这个 1 意味着:特征图的每个位置只产生 1 组 (tx, ty, tw, th, obj, cls...),不拆成 3 份给 3 个 anchor。从”3 个 anchor 各预测 1 个框”变成”1 个位置直接预测 1 个框”。
4.3.6 预测(推理)时的完整流程
1 | ① 输入图像 (640×640×3) |
源码 decode_outputs(推理解码):
1 | def decode_outputs(self, outputs, dtype): |
训练与推理的解码公式完全一致,区别仅在于:训练时还需要用解码结果做 SimOTA 标签分配 + 算 loss;推理时解码后直接过滤 + NMS。
4.4 源码(解耦头定义)
1 | class YOLOXHead(nn.Module): |
注意:objectness 从 reg 分支出(不是 cls 分支),因为 obj 与定位质量更相关。
五、OTA:标签分配的最优传输视角 ⭐⭐
这是 YOLOX 最核心的创新,也是你最想搞懂的部分。
先搞清一个常见困惑:8,400 个框与 OTA 的关系
“每个网格点只预测 1 个框”不等于“总共只有 1 个框”。一张 640×640 的图有 8,400 个网格点(80²+40²+20²),每个点出 1 个框 = 8,400 个框。
一张训练图可能只有 3 个 GT,但网络吐了 8,400 个框。OTA 的任务就是:从这 8,400 个框里挑出哪些是正样本(负责预测 GT)、哪些是负样本(背景)。
1 | 8,400 个预测框 |
- 正样本(~12 个):学”往 GT 靠”(回归 + 分类 loss);
- 负样本(~8,345 个):学”别出框”(objectness loss,target=0)。
| 训练 | 推理 | |
|---|---|---|
| 网络输出 | 8,400 个框 | 8,400 个框 |
| 有 GT? | ✅ 有(标注) | ❌ 没有 |
| OTA 用? | ✅ 挑正样本算 loss | ❌ 不用 |
| 出结果方式 | OTA + loss + 反向传播 | 过滤 obj > 阈值 + NMS |
anchor-free 和 OTA 不矛盾:anchor-free 决定”每个位置出几个框”(1 个,不是 3 个);OTA 决定”这 8,400 个框里哪些是正样本”。两件事。
5.1 标签分配问题
什么是标签分配:训练时,对每个网格点(anchor point)判定它是正样本(负责预测某个 GT)还是负样本(背景),并分配学习目标。
静态分配的问题(v2-v4 的 IoU 阈值法):

- 模糊框(ambiguity):同一个网格点可能与多个 GT 都有较高 IoU,静态分配只按规则选一个,可能选错,给其他 GT 带来有害梯度;
- 静态阈值(如 0.5)不考虑 GT 的大小/形状/遮挡,不同 GT 合适的正样本数其实不同;
- 没有全局视角:每个 GT 独立分配,不考虑”这个 anchor 给哪个 GT 最优”。
5.2 OTA:最优传输
OTA(Optimal Transport Assignment) 把标签分配建模为最优传输(Optimal Transport)问题:
- GT = supplier(供应方):提供 label,每个 GT 需要把 label”运输”给若干 anchor;
- Anchor = demander(需求方):需要 label 去学习;
- 目标:找到一个映射(哪些 anchor 负责哪个 GT),使总运输成本(cost)最低。
cost 的定义(第 $i$ 个 anchor 与第 $j$ 个前景 GT):
$$
\text{cost}{ij} = \underbrace{L{\text{cls}}(\hat{c}i, c_j)}{\text{分类损失}} + \lambda \underbrace{L_{\text{reg}}(\hat{b}i, b_j)}{\text{回归损失(IoU)}} + \underbrace{\text{center_loss}}_{\text{是否在中心区域}}
$$
- 不在 GT 中心区域内的 anchor,cost 乘一个极大系数(如 $10^6$),迫使分配倾向于中心区域内的 anchor;

OTA 的算法步骤:
- 计算 cost 矩阵:所有 GT × 所有 anchor 的成本;
- 计算每个 GT 的动态 k:取与该 GT IoU 前 q(q=10)的 anchor,对这 10 个 IoU 求和取整作为 k(高质量 GT 多分配、低质量 GT 少分配);
- 用 Sinkhorn-Knopp 算法求解最优传输计划(最小化总 cost 的全局最优分配)。
OTA 的关键洞察:① 用全局信息找全局最优(不是每个 GT 独立贪心);② 动态 k–一个 GT 该分几个正样本取决于预测质量,而非固定阈值。
5.3 SimOTA:OTA 的简化(YOLOX 实际用的)
OTA 效果好但训练成本高:Sinkhorn-Knopp 迭代算法在 300 epoch 训练中增加约 25% 训练时间。YOLOX 提出 SimOTA,用简单手工规则替代 Sinkhorn-Knopp:
SimOTA 与 OTA 的唯一区别:第三步不用 Sinkhorn-Knopp,改为直接按 cost 选 top-k。

SimOTA 步骤(对应源码 simota_matching):
- 计算 cost 矩阵(同 OTA):
cost = cls_loss + 3.0 × iou_loss + 1e6 × (不在中心区域) - 动态 k:
dynamic_k = sum(top-10 IoU),即每个 GT 取与它 IoU 最大的 10 个 anchor 的 IoU 之和作为正样本数; - 直接选 top-k:对每个 GT,从候选 anchor 中选 cost 最小的
dynamic_k个作为正样本; - 冲突处理:若一个 anchor 被多个 GT 选中,分配给 cost 最小的那个 GT。

SimOTA 的核心:保留 OTA 的”动态 k + cost 最小”思想,但去掉昂贵的 Sinkhorn-Knopp 迭代,用直接的 top-k 选取近似全局最优。效果接近 OTA,训练时间大幅缩短。
5.4 源码(SimOTA 实现)
源码在 yolox/models/yolo_head.py 的 YOLOXHead 中,核心是 get_assignments + simota_matching:
1 |
|
1 | def simota_matching(self, cost, pair_wise_ious, gt_classes, num_gt, fg_mask): |
关键代码点:
dynamic_ks = topk_ious.sum(1):动态 k 的计算(top-10 IoU 求和);cost = cls_loss + 3.0 * iou_loss + 1e6 * (~in_center):成本 = 分类 + 回归 + 中心约束;- 冲突处理:
torch.min(cost, dim=0)选最小 cost 的 GT。
5.5 SimOTA 的 loss
标签分配完成后,损失在 get_losses 中计算:
1 | loss_iou = IOUloss(bbox_preds[fg_mask], reg_targets).sum() / num_fg # 1 - IoU² |
- cls_target 是 soft label:
one_hot(class) × pred_iou,即分类目标乘以匹配的 IoU(预测越准,分类目标越接近 1); - IOUloss = 1 - IoU²(不是 CIoU);
reg_weight = 5.0:回归损失权重最高。
六、其他改进
6.1 数据增强
- Mosaic + MixUp:Mosaic(4 图拼接)+ MixUp(改进版,类似 copy-paste 效果);
- 最后 15 epoch 关闭:Mosaic 会截断 bbox、偏离真实分布,最后阶段关掉让模型在真实分布上精修。作者解释:Mosaic 改变了数据分布,最后关闭相当于用真实分布 fine-tune。
6.2 SiLU 激活函数
$$
\text{SiLU}(x) = x \cdot \sigma(x)
$$
- 比 LeakyReLU(v3)平滑、有上界抑制;
- 比 Mish(v4)计算更快,效果相近;
- YOLOX 全网用 SiLU。
6.3 Bias 初始化(focal loss trick)
1 | def initialize_biases(self, prior_prob=0.01): |
cls 和 obj 的 bias 初始化为 $-\log((1-0.01)/0.01)$,使初始输出概率约 0.01,防止训练初期大量 False Positive 导致 loss 爆炸(同 RetinaNet 的 focal loss 初始化)。
6.4 End-to-End(可选)
YOLOX 提供一个 end-to-end 模式,用 [OTA 的后续工作] 去掉 NMS,实现真正端到端。但默认仍用 NMS。
七、性能
| 模型 | 输入 | AP (COCO) | FPS |
|---|---|---|---|
| YOLOX-Nano | 416 | 25.8 | - |
| YOLOX-Tiny | 416 | 32.8 | - |
| YOLOX-S | 640 | 40.5 | - |
| YOLOX-M | 640 | 46.9 | - |
| YOLOX-L | 640 | 49.7 | - |
| YOLOX-X | 640 | 51.5 | - |
| YOLOv5-S (对比) | 640 | ~37 | - |
YOLOX-S(40.5 AP)显著超越 YOLOv5-S(~37 AP),同速度档。YOLOX-X 达 51.5 AP,接近两阶段检测器水平。
八、总结
YOLOX 的核心贡献不是新结构,而是范式切换:把 YOLO 从 anchor-based 推进到 anchor-free,并解决了随之而来的两个关键问题:
- 解耦头:让 anchor-free + OTA 训练稳定收敛(耦合头会冲突);
- SimOTA:用最优传输的思路动态分配正样本,替代静态 IoU 阈值,解决模糊框分配问题。
理解 YOLOX 的关键三件事:
- Anchor-free:每位置预测 1 框,去掉 anchor 超参(vs v2-v4 的 3-9 个 anchor);
- Decoupled Head:cls/reg 分离,训练更稳(vs v3-v4 耦合头);
- SimOTA:动态 k + cost 最小的标签分配(vs v1-v4 的静态规则),近似 OTA 全局最优但更快。
YOLOX 之后,YOLOv6/v7/v8 普遍跟进 anchor-free + 解耦头 + 动态分配的范式,YOLOX 是这个范式切换的关键节点。
相关链接
- 📝 [[20.notes/感知算法/2D检测/单阶段/YOLO/YOLOv3 详解|YOLOv3 详解]] - 对比基准(耦合头/anchor/静态分配)
- 📝 [[20.notes/感知算法/2D检测/单阶段/YOLO/YOLOv4 详解|YOLOv4 详解]] - 对比基准(CSPDarknet/Mosaic)
- 📝 [[20.notes/感知算法/2D检测/单阶段/RetinaNet/RetinaNet 详解|RetinaNet 详解]] - bias 初始化 trick 同源
- 📝 [[20.notes/感知算法/2D检测/单阶段/FCOS/FCOS 详解|FCOS 详解]] - anchor-free 先驱,YOLOX 检测头借鉴
- 📋 论文原文: arxiv.org/abs/2107.08430
- 📋 官方代码: Megvii/yolox
- 📦 源码参考:
deeplearning/detect/YOLOX/yolox/models/yolo_head.py(解耦头 + SimOTA 实现)
剪藏来源(10.clippings/感知算法/2D检测/单阶段/YOLOX/): - 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX 詳細解讀]] - 詳細解讀
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX——SimOTA图文详解]]
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX深度解析]] - 深度解析
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX深度解析(二)-simOTA详解]] - simOTA 详解
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX解析]] - 解析
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/YOLOX:2021年超越YOLOv5的先进检测技术-CSDN博客]] - CSDN 详解
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/yolox论文研读]] - 论文研读
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/【YOLOX 论文+源码解读】YOLOX Exceeding YOLO Series in 2021_yolox论文-CSDN博客]] - 论文+源码解读
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/【精华】从YOLOX详解模型优化思路及涨点技巧]] - 优化思路及涨点技巧
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/【论文阅读】YOLOX:Exceeding YOLO Series in 2021 - welcome to x-jeff blog]] - 论文阅读
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/最详细最易懂的【YOLOX原理篇】]] - 原理篇
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/深入浅出Yolo系列之Yolox核心基础完整讲解]] - 与 YOLOv1-v5 对比
- 📋 [[10.clippings/感知算法/2D检测/单阶段/YOLOX/解读YOLOX(OTA]] - OTA/SimOTA 概念详解