把目标检测走到2020年前后,主流方案基本被anchor和NMS统治。Faster R-CNN、YOLO、SSD这几位,性能虽然越刷越高,但套路还是老套路:先密集铺anchor,再对正负样本做匹配,最后用NMS把重复框压下去。整个流程里,手工设计的组件一个接一个,训练时一堆超参数要调。直到Facebook AI那篇DETR(Detection Transformer)发出来,整个圈子才意识到,检测还能有另一种打开方式。这篇论文后来拿了ECCV 2020的Best Paper,标题叫《End-to-End Object Detection with Transformers》,核心思想用一句话就能说清:把目标检测当成一个集合预测问题,用Transformer去掉anchor、RPN、NMS这些手工组件。这篇文章主要面向两类人:一类是已经跑过YOLO或Faster R-CNN、想深入理解DETR原理的深度学习者;另一类是准备在自有数据集上复现、微调或部署DETR的工程师。读完你至少能弄明白三件事:DETR为什么能端到端、它的损失函数为什么绕不开匈牙利匹配、以及它到底有哪些绕不过去的短板。
1. 检测范式换了赛道:从“滑窗猜”到“直接报数”
1.1 传统检测器为什么越做越重
在DETR之前,主流检测器大致分两条路线。一阶段代表如YOLO和SSD,直接在主特征图上做密集预测,每个位置铺若干个预设的anchor框,模型要学的是“这些框里面有没有物体、物体是什么类别、框要往哪个方向调”。二阶段代表如Faster R-CNN,先用RPN提一堆候选框,再逐框做ROI Pooling和分类回归。无论哪条路线,都离不开anchor先验框的设计,离不开对正负样本的筛选策略,更离不开推理阶段的NMS去重。
这些环节单独拿出来都说得通,但串在一起就暴露出一个问题:整个pipeline像一个手工拼装的大机器,组件之间耦合很强。anchor的尺度和比例要针对数据集调,正负样本的IoU阈值会影响训练,NMS阈值稍微动一下,mAP就可能出现明显波动。做检测的工程师,很多时间其实不是在调模型,而是在调这些后处理细节。
DETR的出发点就是要打破这种局面。它把检测结果直接看成一组集合,模型每次输出固定数量的预测,通过二分图匹配让预测和真值一一对应。于是anchor没了、RPN没了、NMS也没了,所有的检测逻辑都被收进了一个Transformer里,真正做到了端到端。
1.2 DETR整体流程快速过一遍
DETR的完整结构可以切成三块:CNN backbone负责提特征,Transformer encoder负责全局建模,Transformer decoder配合object queries负责查物体。
具体流程是这样的:输入一张图,经过backbone得到特征图,默认用ResNet-50,输出通道2048,空间分辨率是原图的1/32。接着用一个1x1卷积把通道降到256,这一步是为了匹配Transformer的embedding维度。然后把特征图展平成序列,每个空间位置就是一个token,同时把每个token的空间位置编码加进去,送进encoder。encoder输出的是经过全局建模的特征表示,作者把它叫作memory。decoder这边,输入是一组可学习的object queries,默认100个,每个query都会通过cross-attention去memory里“查询”对应的物体信息。最后,每个query接一个FFN输出预测,包括类别和归一化的bounding box坐标。
整个模型训练完,你给一张图,它就固定输出100个预测。图片里可能有几个物体,剩下的预测都是“背景”。用什么方式让100个预测和真实物体一一对上?答案是匈牙利算法做二分图匹配,这个在第三节细讲。
2. 核心模块拆解:每个组件都在解决什么问题
2.1 Backbone与位置编码:给序列补上空间信息
DETR的backbone其实没有太多新东西,选ResNet-50是因为它在ImageNet上pretrain效果好、结构成熟。有一个细节值得注意:DETR只用backbone最后一层特征图,没有像FPN那样做多尺度融合。所以在输入分辨率800x1066的情况下,送给Transformer的特征图大概是25x34=850个token。空间信息被压缩到很低的分辨率,这一点后面会解释为什么小目标检测差。
位置编码在这套结构里非常关键。Transformer本身是置换等变的,它根本不关心token之间的相对位置。图片的特征图如果被打平成序列而不加位置信息,模型就不知道哪个token在上哪个token在下。DETR用的是空间位置编码,对x和y方向分别做不同频率的正弦编码,然后加到每个token的特征上。它不是学出来的,是固定公式生成的,好处是能泛化到不同的输入分辨率。
这里有个实操容易踩的坑:如果你自己实现或后来用DAB-DETR这类变体,位置编码的维度、加在query还是key上、以及是否在每层attention前重加,都会直接影响最终效果。原始DETR的做法是在encoder和decoder每一层的输入上都加位置编码,不是只在最前面加一次。
2.2 Encoder与全局建模:换个角度理解注意力
Transformer encoder在DETR里承担的任务,是把backbone输出的局部特征变成有全局上下文信息的特征。CNN的卷积核感受野是有限的,高层特征虽然能看到更大的区域,但要建模两个距离很远物体之间的关系,靠堆卷积始终不自然。self-attention不一样,它让每个token都能直接看到序列里的所有token,一次建模全局依赖。
从计算量来看,self-attention复杂度是O(N^2),N是token数量。850个token的注意力矩阵是850x850,这个规模对GPU来说压力不大。真正的问题不在于计算量,而在于收敛速度。DETR论文里明确说过,encoder的全局注意力需要大量训练才能学会“该关注哪里”,这也是它500个epochs才能收敛的重要原因之一。
那为什么还要用encoder?因为在目标检测里,一个物体是否成立,往往取决于它和其他物体的关系。比如图片里有一辆车,你是通过“它下面有路面、周围有行道树”来确认的。全局建模能让模型对遮挡、重叠、语义歧义更鲁棒。DETR的实验也显示,去掉encoder或者减少encoder层数,AP会明显下降。
2.3 Decoder与object queries:用“提问”代替“滑窗”
Decoder是DETR里最有意思的部分。object queries初始是100个可学习的向量,每个向量256维。它们不依赖输入图片,训练之初随机初始化,然后在训练中被逐步塑造成“不同类型查询器”,有的query负责查询大目标、有的负责小目标、有的专门关注图片左上角区域。
每一层decoder的cross-attention中,query会与encoder输出的memory做注意力运算,相当于每个query主动向特征图提问:“这里有没有我负责的那种物体?”decoder一共有6层,每层都把前一层输出送去FFN做一轮预测,并参与损失计算,这就是auxiliary loss,辅助收敛。
有个容易混淆的点:object queries为什么是100个?这个数字代表模型最多预测100个物体,不是网络结构必须用100。如果你在自有数据集上训练,单张图里目标可能远超100个,就要加大query数量。反过来,如果目标数量少,减到50或30也能跑,但一般不推荐贸然改动,因为匹配数量会影响匈牙利匹配的分布。
2.4 FFN与输出头:让每个query给出最终预测
Decoder输出一个query向量后,后面接的FFN其实是一个很小的多层感知机,负责把向量映射成预测结果。预测分两部分:一部分是类别logits,维度等于类别数加1(背景类);另一部分是box坐标,输出4个值,分别是中心点cx、cy、宽度w、高度h,全部做了归一化,范围在0到1之间。
这里的box预测没有用anchor回归,也没有边界框偏移约束,直接回归绝对坐标。之所以能这么做,是因为二分图匹配已经解决了预测和真值的配对问题,每个query稳稳对应一个真值,回归任务相对干净。但如果box坐标不归一化,不同尺寸的物体回归难度差异会很大。所以DETR在损失里同时用L1损失和GIoU损失,前者保证坐标精度,后者让框的重合度更好优化,这一块在下一节展开。
3. 二分图匹配与损失函数:端到端的关键一步
3.1 为什么必须用匈牙利匹配
既然模型输出的是100个无序预测,真值可能只有几个,怎么让模型知道哪个预测对应哪个真值?如果沿用传统检测器的做法,按IoU阈值匹配,那又回到了人工设计规则的老路。DETR的办法是把预测和真值当成两个集合做二分图匹配,找到一个最优的一一对应关系。
数学上就是匈牙利算法解决的问题。给定一个代价矩阵,矩阵第i行第j列表示第i个预测与第j个真值之间的匹配代价,算法会找出一个代价最小的匹配方案。重点在于,这个匹配是在每个训练batch内动态计算的,当前模型的预测结果决定了每个query学哪个物体,而不是一开始就固定。这种动态匹配让训练相当灵活,但也带来了一个弊端:匹配结果不稳定,训练前期预测很烂,匹配也乱,模型容易陷入次优解,这是DETR收敛慢的另一个原因。
3.2 匹配代价和训练损失怎么设计
匹配代价决定了匈牙利匹配的“偏好”。DETR的匹配代价由三部分构成:分类负log概率、box的L1距离、box的GIoU负值。
实现时有一个细节:匹配用的是“分类负log概率”,而不是交叉熵loss,因为匹配阶段只关心预测置信度排序,不希望额外引入不必要的梯度。匹配完成之后,真正的训练损失才在匹配好的配对上进行。训练损失同样由分类损失、L1损失和GIoU损失组成,但分类用的是标准交叉熵,背景类要单独设置权重。DETR实验里把背景类的权重调低到0.1,这个值直接影响前景背景的平衡。
有一个问题是L1损失对框的尺寸敏感。同样的绝对误差,大框和小框的L1数值差异很大。DETR的做法是box坐标归一化,用相对于图片尺寸的比例来表示,减小尺度影响。但即便归一化,L1对微小偏移和大偏移的惩罚是线性的,优化GIoU能缓解这部分问题。GIoU的取值在-1到1之间,它考虑了框之间的重叠区域、闭合区域和空白区域,比单纯IoU更能提供可优化的梯度信号。
匹配代价和训练损失为什么保持同构?因为如果匹配阶段和训练阶段的标准不一致,模型会学偏。比如匹配时用IoU,训练时用L1,那匹配找出来的最优配对可能在训练损失下并不是最好的,整个优化方向就乱了。
3.3 训练超参和收敛问题
DETR论文里的训练配置,值得所有复现者拿小本本记下来。默认ResNet-50骨干,embedding维度256,encoder和decoder各6层,8个注意力头,object queries数量100,训练500个epochs。优化器用的AdamW,初始学习率1e-4,backbone部分单独用1e-5,weight decay设为1e-4。数据增强包括随机裁剪、随机缩放,这一点和传统检测器差异不大。
最大的槽点是收敛速度。Faster R-CNN在COCO上训练12个epochs就能达到不错效果,DETR需要500个epochs才能到42.0 AP(ResNet-50),中间整整差了一个数量级。作者在论文里用16张V100训练了大概3天时间,这对个人开发者来说门槛相当高。
如果你自己复现,我的建议是:初期实验用5到10个epochs看loss下降趋势,不要一上来就冲500 epochs。另外dropout设为0.1、grad clip设为0.1这两个trick能有效稳定训练,论文里没有强调,但很多复现实验都验证了它们的重要性。
3.4 推理阶段:NMS被彻底拿掉
DETR推理时,直接取100个预测里置信度高于阈值的框,不需要NMS。为什么不需要?因为object queries经过训练后,同一个物体大概率只有一个query能激活。即便有两个query同时预测到同一个物体,匈牙利匹配的训练目标会让它们分化。这是set prediction范式的自然结果。
没有NMS带来两个直接好处:一是推理pipeline更短,二是不会有NMS阈值超参需要调。但严格来说,DETR输出偶尔还是会出现少量重复框,不过重复框的置信度通常差别很大,过滤阈值就能解决大部分。实际工程里,如果你追求极致精度,也可以在输出端加一个轻量的NMS兜底,但这不是必须的。
4. 实践中的坑与改进方向:哪些问题必须正视
4.1 DETR收敛慢到底卡在哪
很多人复现DETR第一反应是:我的loss怎么下降得这么慢?这是正常的,DETR收敛慢是结构性的,不是训练细节问题。根本原因有两个。一个是全局注意力在训练初期不知道关注哪里,self-attention需要大量样本才能建立起空间对应关系。另一个是匈牙利匹配的动态性,预测差导致匹配差,匹配差导致监督信号弱,形成恶性循环。匹配关系每步都在变,模型很难稳定学到一致的映射。
传统检测器用anchor或RPN先把候选位置限制在很小的范围,模型只需要做局部精修。DETR把“找物体”这件事完全交给注意力机制,自由度更高,但学习难度也更大。所以不能用训练YOLO的思维去训DETR,必须给它足够的训练预算。
缓解收敛慢的几种常见做法:加auxiliary loss(每个decoder层都算损失)、用AdamW而不是SGD、预热学习率、dropout和grad clip配合使用。这些都能让训练更快稳定下来,但不会从根本上解决慢的问题。
4.2 小目标检测差,问题出在特征分辨率
DETR在COCO上大中目标的AP还不错,小目标AP明显偏低。原因其实很直白:它只用backbone最后一层1/32特征图,小目标在这个分辨率下可能只占一两个像素,信息已经丢失,Transformer再强大也无法凭空恢复。再加上self-attention是全局的,小目标的注意力容易被大目标稀释,两个因素叠加,小目标性能自然拉胯。
如果你的任务主要是小目标检测,直接上原版DETR大概率会很难受。要么换成Deformable DETR这类带多尺度特征的设计,要么在输入分辨率上做文章,比如测试时用更高分辨率。原版DETR在输入尺寸升高时性能会有提升,但计算量和显存消耗也会跟着涨,需要自己权衡。
4.3 改进方案怎么选:从Deformable DETR到DINO
Deformable DETR是2021年提出来的改进版,解决两个痛点:收敛慢和多尺度。它借鉴了可变形卷积的思想,注意力只在每个query的参考点附近采样少量点,而不是全图计算。这种稀疏注意力把计算复杂度从O(N^2)降到了O(N*K),K是采样点数,也让多尺度特征融合变得可负担。在COCO上,Deformable DETR用10个epochs就能达到DETR用500个epochs的效果,收敛速度快了将近50倍。
DAB-DETR则是把object queries从“隐向量”改成了“可学习的anchor box”,每个query自带位置和尺寸信息,让decoder的查询目标更明确。DN-DETR引入去噪训练,把带噪声的真值框喂给decoder学重建,大大缩短收敛时间。DINO综合了DAB和DN的思路,在检测精度上连续霸榜多年。到了RT-DETR,百度把实时性也解决了,用混合编码器和不确定性最小化,让Transformer检测器也能跑到实时水平。
如果你想在工程里用DETR系列,我的建议是:直接跑原版DETR理解原理,但落地优先考虑Deformable DETR或DINO。原版DETR更多是学术价值和范式的意义,实用层面后面这些变体才是真正能上线的版本。
4.4 部署DETR系列的几个注意点
部署Transformer检测器,和部署YOLO这种CNN模型有不少差异,容易踩坑。第一个坑是动态shape。推理时如果输入尺寸可变,Transformer的序列长度会变,ONNX导出时最好固定输入尺寸,或者用多档分辨率预先优化。第二个坑是位置编码。DETR用正弦位置编码,导出时要把位置编码的计算保留在模型图内,不要在外部拼凑。第三个坑是后处理。虽然不需要NMS,但100个预测的过滤和框的解码在CPU上实现也要注意效率,如果是TensorRT部署,可以把过滤逻辑写在plugin里,避免频繁的GPU-CPU数据传输。
内存方面,Transformer的attention中间变量在推理时占内存不小,尤其batch size大的时候。实测中,用TensorRT部署Deformable DETR,FP16精度下精度损失很小,速度比PyTorch原生推理快3到5倍,算是一个比较稳的部署方案。
5. 在自有数据集上训练DETR:一个可参考的流程
5.1 数据准备与num_classes修改
DETR官方代码支持COCO格式数据集,你自己训练最省事的路径是把手头的数据转成COCO标注格式,再用官方dataloader读取。如果你用Detectron2做后端,把数据集注册成自定义name,改掉category数就行。如果数据格式不是COCO,你需要自己写一个dataset类,返回image和对应的boxes、labels。
关键修改点就是model头部的num_classes,以及object queries数量是否匹配你的数据。经典COCO是80类,你的任务如果是比如5类,model里class_head的线性层参数会变,这个直接改配置文件即可。object queries数量一般保持100就好,除非你确认单张图里可能出现超过100个目标,那就加大到150或200,代价是推理时多算一些背景预测。
5.2 关键超参数设置参考
我自己训过自定义数据的DETR,一个比较稳的配置是这样的:输入分辨率用800x1333,batch size和显存挂钩,8卡或单卡能放多少放多少,但不建议单卡batch小于2,否则BN统计都不好算。学习率用1e-4,backbone部分降为1e-5,预训练权重一定得加载,否则收敛更慢。训练轮数按数据量来,小数据集(几千张)用50到100个epochs,大数据集可以到300个epochs以上。如果你的数据量大,且用的是Deformable DETR,训练轮数可以大幅缩减到50个epochs左右。
数据增强方面,随机裁剪、随机缩放这些增强都能用。特别提醒一点:如果你的数据集图片尺寸差异很大,建议用固定短边缩放到统一尺寸,不要直接resize,否则物体形变会被模型学进去。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 解决建议 |
|---|---|---|
| 训练loss不降 | 学习率过高或过低,加载预训练权重失败 | 确认backbone预训练加载,lr设为1e-4附近,内部层1e-5 |
| 输出全是背景 | object queries过少,或者分类loss权重失衡 | 增大queries数量,检查背景类权重是否设置过低 |
| 收敛极慢,几十epoch还没起色 | 没有auxiliary loss,或优化器用错 | 开启auxiliary loss,换AdamW,设grad clip 0.1 |
| 小目标基本检不到 | 特征图分辨率低、多尺度缺失 | 换Deformable DETR,或提高输入分辨率 |
| 推理时ONNX导出报错 | 位置编码或动态序列长度 | 固定输入尺寸,把位置编码包含在图内计算 |
| 同一目标多个重复框 | 置信度阈值过低,或query训练分化不足 | 提高阈值,必要时输出端加轻量NMS兜底 |
6. 我的个人体会
DETR最让我佩服的地方,不是它精度刷得有多高,而是它把检测问题重新定义成了一个纯粹的学习问题。它告诉我们,那些年我们精心设计的anchor、NMS,其实不是目标检测的必需组件,只是当时模型能力不足时用来弥补结构缺陷的拐杖。Transformer凭借强大的建模能力,直接把拐杖扔了。
当然,原版DETR的问题也是实实在在的。我自己用单卡V100复现的时候,光是在COCO上跑完500个epochs就不现实,更别提调参了。所以我的建议是,如果你刚接触DETR,先跑通一个小的子集看看效果,理解它的loss和匹配机制,再决定是否上完整训练。如果你想直接落地,一定要研究Deformable DETR和DINO这些变体,它们解决了原始DETR最难用的那几个痛点。
最后分享一个小技巧:分析DETR中间attention图,是理解它在学什么的最好方式。把decoder不同层的cross-attention可视化出来,你会看到query从最初的均匀分布,慢慢收敛到某个物体附近,这个过程非常直观地展示了“集合预测”是如何做到的。用好这个可视化方法,你对DETR的理解会比单纯读论文深得多。