1. 内容整体设计与思路拆解1.1 传统目标检测的痛点Anchor、NMS与手工设计我第一次认真读DETR论文是2019年左右。当时目标检测这个领域其实已经有非常成熟的方案了Faster R-CNN系列、YOLO系列、SSD系列跑起来都能看到不错的指标但这些方案有一个共性的问题模型里塞了大量“手工设计”的组件。比如Anchor的尺寸比例怎么设RPN的IOU阈值取多少NMS的抑制阈值怎么调特征金字塔每一层负责多大尺度的目标这些问题在每一套新数据集、新场景下都要重新调一遍工程师心里其实没什么底反正就是试。DETR做的第一件颠覆性的事就是把这些组件全部拆掉。它把目标检测重新定义成一个“集合预测”问题模型一次性输出一组预测结果每一条预测包含目标的类别和包围框然后直接和真值做二分图匹配来计算损失。这个思路说出来其实很朴素但要做到“没有Anchor、没有NMS、不需要手工先验”难点在于怎么设计一个网络结构能让模型直接学会“哪些预测对应哪些目标”。当时Transformer在自然语言处理领域已经把序列建模玩得风生水起自注意力机制天然具备建模全局依赖的能力。DETR的作者就做了一个直觉上的迁移如果把图像特征看成一组序列让Transformer去建模特征点之间的关系那是不是就能让模型自己学会“这个位置和那个位置属于同一个物体”1.2 DETR到底解决了一个什么问题DETR全称是Detection Transformer核心价值可以概括成一句话用一套统一的Transformer架构把目标检测做成真正端到端的训练与推理流程。这里的“端到端”不是营销话术而是指从图像输入到预测结果输出中间的“目标候选生成”“特征对齐”“去重后处理”全部被替换成了可微分的网络层。换个角度理解以前的检测器把任务拆成好几个子问题比如先生成候选框、再逐框分类和回归、最后用NMS合并重复框。每一步之间的信息传递是割裂的而且很多操作没法直接求梯度比如NMS里“保留哪个框”这个决策是离散的。DETR把这套流水线压缩成了一个“特征编码—对象查询—集合匹配”的整体所有参数可以一次性端到端训练推理时也不需要做NMS当然实际用的时候为了保险还是有人会做一下简单的去重但原理上已经完全不需要了。这篇文章适合谁读如果你刚接触Transformer在视觉领域的应用想弄明白DETR的完整结构而不只是停留在“跑个demo”的层面如果你想在自己的数据集上复现或改进DETR但又不知道从哪下手或者你只是对“为什么Transformer能做检测”这个问题感兴趣那这篇内容应该能帮到你。我会从结构设计的思路一路讲到训练里踩过的坑尽量把我实际用下来觉得值得注意的东西都说清楚。2. DETR的核心结构三大模块与一条主线2.1 整体流程能分成哪几块DETR的结构从宏观上可以切成三段。第一段是CNN骨干网络通常用ResNet-50或ResNet-101负责把输入图像降采样成分辨率较低的特征图这一步和经典检测器的Backbone没有本质区别唯一要注意的是DETR一般会去掉ResNet最后一个Stage的步长让输出特征图分辨率保持在32倍降采样这样能保留更多空间细节。第二段是Transformer的编码器和解码器。编码器接收的是“展平后的图像特征序列”自注意力模块会在整张图的特征之间做交互相当于让每一个空间位置都“看到”全图的上下文。解码器稍微复杂一点它不直接读取特征图而是输入一组可学习的“对象查询”向量通过交叉注意力机制从编码器输出的特征里“查询”出每个目标的特征再逐步细化。第三段是检测头。解码器输出的每个对象查询向量会被送入一个FFN前馈网络预测出目标的类别分布和包围框坐标。类别分支输出的是最多一个类别或者“空”这个背景类框分支则输出归一化后的中心点坐标和宽高。这里有一个很关键的设计为什么解码器能用“固定数量”的对象查询因为DETR假设“一张图里最多同时出现N个目标”N是超参论文默认设100。就算图里只有两三个目标模型也会输出100条预测但多余的预测都会通过“背景类”被抑制掉。这种设计在生产环境里其实挺方便因为输出的张量形状是固定的部署时对显存和算力的预估更简单。2.2 “集合预测”这一层思路比结构本身更难懂第一次看DETR结构的人通常会有一个疑惑编码器输出的是一组特征解码器查询的是一组可学习向量模型怎么知道哪个查询对应哪个目标答案就是“二分图匹配”机制。训练阶段我们会把模型的100条预测和真值框做一个最优匹配。这个匹配问题的代价函数包含三部分类别预测是否和真值类别一致、预测框和真值框的L1距离、预测框和真值框的GIoU距离。用匈牙利算法找到“让整体代价最小”的匹配方案然后只对匹配成功的预测计算损失没有匹配上的预测全部按背景类计算损失。这个机制有点像排队领东西有100个人预测桌上摆了若干个印了名字的礼物真值每个人只能拿一个但礼物和名字之间不是一一对应的我们需要找出一种分配方式让所有人的满意度总和最高。匈牙利算法解决的就是这个分配问题。为什么不能简单地把第i个预测强行对应第i个真值因为Trm解码器输出顺序没有几何先验第1个查询不一定对应左上角的物体模型内部又没有谁来保证输出顺序。如果不做匹配而是按位置强行对齐训练就崩了。这算是DETR设计里最精妙、也最容易被人忽略的部分。2.3 训练与推理时的行为差异训练阶段DETR的损失是“匹配后”的损失先用匈牙利算法做一次匹配再计算匹配上的预测与真值之间的距离损失。这个设计意味着即使模型一开始预测得很差只要匹配关系建立了梯度信号依然能沿着对应的路径回流到正确的位置。推理阶段就简单多了不需要做匹配直接把解码器输出经过FFN得到类别和框然后按置信度过滤掉“空”类预测剩下的就是检测结果。如果还有少数重叠的框常规做法是直接按置信度取TopK或者做一个非常宽松的NMS。我在工程落地时还是会用一个低IOU阈值的NMS省得被下游逻辑抱怨“怎么同一个目标出两个框”但这不是算法必需纯属工程洁癖。3. 关键模块拆细编码器、解码器与位置编码的配合3.1 编码器怎么把图像特征变成序列图像输入DETR后会先经过CNN骨干假设输入是800×800的图像经过ResNet的降采样后特征图大概是25×25×2048。接下来为了进Transformer需要把这个特征图压扁成序列25×25625个token每个token的特征维度是2048。Transformer编码器内部首先会用卷积1×1把特征维度从2048压缩到d_model论文用256这样能省下不少显存和计算量。然后会加上一个空间位置编码再被送入一个标准的Transformer编码器层多头自注意力FFN中间有残差连接和LayerNorm。编码器可以叠加多层论文默认是6层。这里最值得说道的是位置编码。图像特征被压成序列后如果没有任何位置信息自注意力做全局交互时“左上角”和“右下角”在模型看来没有任何区别。这显然不行因为目标检测本质上是“在哪里”的任务。DETR用的位置编码是空间版的三角函数编码分别给x轴和y轴生成一套正弦/余弦编码然后和特征逐元素相加。注意DETR的位置编码和输入特征是加法关系不是拼接关系这一点和Transformer原始论文里的做法一致。3.2 对象查询到底是什么可学习的“先验”很多人把对象查询理解成“anchor的替代品”这个类比其实挺到位的。Anchor是手动定义的、固定尺寸比例的候选框对象查询则是一组可学习的嵌入向量它们在训练过程中会逐渐变成“某种语义信息的触发器”。具体来说100个对象查询里有的查询可能慢慢学会“专门负责中等尺寸、位于画面中央的目标”有的是“负责大尺寸且偏左下区域的目标”。模型并没有显式地给每个查询规定分工这种专业化完全是从数据里自发涌现出来的。你可以在训练后把每个查询对应的注意力图可视化出来会很清晰地看到“固定位置、固定尺寸”的偏好。解码器内部做的是这样一件事每个对象查询先通过自注意力互相通信避免多个查询重复指向同一个目标这是DETR不需要NMS的底层原因之一然后通过交叉注意力从编码器的特征序列里提取目标相关的信息。每一层做完后都会更新查询向量经过6层解码器层的迭代最后的输出向量就包含了足够丰富的类别和位置信息足以供给FFN做最终预测。3.3 解码器自注意力如何替代NMS“免NMS”这个特性很大程度要归功于解码器第一层里的自注意力。在标准Transformer解码器里自注意力让不同token之间互相“看到”彼此放到对象查询这个场景里就是100个查询会互相协商知道“我已经选中那个物体了你别跟我抢”。这个协商机制不是强制性的它不像NMS那样有明确的“保留置信度高的、删除重合度高的”规则而是靠数据驱动学出来的。两个查询如果确实同时锁定了目标经过自注意力后它们输出的特征会有强相关性模型在训练时通过二分匹配会让“分数更匹配”的那个查询拿正样本另一个查询被对应到背景类这样梯度信号就会迫使另一个查询学会“换一个目标去关注”。训练久了这100个查询的“分工”会逐渐稳定下来。这也是为什么DETR有时候会出现“漏检”当目标数量非常接近100时查询之间协商不充分互相谦让过头了就没人输出那个框。3.4 FFN检测头与输出格式解码器最后一层的输出会分别进入类别分支和框分支。类别分支是一个线性层Softmax输出每个查询在C1个类别上的概率C个目标类别加1个背景类。框分支是一个3层MLP输出4个数值归一化的中心坐标cx, cy和归一化的宽高w, h。这里有一点容易踩坑框分支的输出范围没有做激活函数限制也就是说模型理论上可能输出负的宽度或高度。实际训练中因为匹配了正确的真值框大多数预测不会出现这种离谱情况但推理时如果看到“宽高为负数”的检测框通常是模型还没收敛好加长训练轮次即可。如果想要更稳也可以在输出后手动做一次绝对值操作但这只能算工程侧保险不解决模型本身的问题。4. 实操过程准备数据、训练调参与推理部署4.1 数据标注与格式转换在DETR上做真实验收第一步是准备COCO格式的标注数据。我一般用在线标注标注完导出成COCO JSON格式然后写一个脚本把标注文件转换成DETR训练需要的格式。DETR的官方代码里直接支持COCO数据集所以你只要把标注改成COCO格式就行包括info/licenses/images/annotations/categories这五个关键字段。标注的时候有几个细节特别关键。一是包围框的坐标要存成[x, y, width, height]的绝对像素值而且不允许出现负值二是Duplicate标注一定要清理干净如果同一张图上同一个目标存在两个框框训练时匈牙利匹配会无所适从目标可能会被两个查询同时对应上导致训练信号混乱三是类别ID从1开始编号0留给背景类官方代码里读categories时它会把0留给“空”如果你从0开始编号后面推理时会发现类别输出整体错了一位。4.2 训练超参数我从论文里抄的作业和改动官方默认配置大致是ResNet-50骨干Batch Size 64训练300个epoch等效于COCO train2017上跑大约110个epoch初始学习率1e-4在200个epoch时乘以0.1。优化器用AdamWweight decay 1e-4。骨干网络加载ImageNet预训练权重Transformer部分用Xavier初始化。实际复现时我建议根据自己的设备去降Batch Size但学习率要按比例调整。我自己试过Batch Size从64降到16的时候学习率最好也降到2.5e-5左右否则模型会训得特别浪损失经常跳到NaN。不过还有一个更稳的做法是保留官方学习率但用累积梯度把等效Batch Size撑回至少32这样与原始配置偏离不大收敛性也更可控。DETR最大的短板就是收敛慢300个epoch在8张V100上要跑两三天单卡用户基本吃不消。所以如果不是做论文复现我更推荐用官方代码里自带的“150 epoch”版本或“50 epoch”简化配置配合更多的数据增强。我自己在业务项目上用过50 epoch那个版本效果虽然比300 epoch的差一点但交付周期短很多。4.3 数据增强策略与Loss设计DETR对数据增强是极度依赖的。官方训练时用到的增强包括随机缩放短边480到800之间随机取、随机裁剪、水平翻转。这些增强消融实验影响很大特别是随机缩放它能给模型提供丰富的尺度多样性让Transformer学到的“对象查询分工”更稳定。如果不做增强模型很容易在小目标上直接摆烂。损失函数这块官方代码用的是L1损失和GIoU损失的线性组合。公式是这样的loss λ1 * L1(box_pred, box_gt) λ2 * GIoU(box_pred, box_gt)默认系数λ15λ22。这里L1损失负责让预测框的中心点“贴近”真值框GIoU损失则负责让预测框的“形状和覆盖范围”接近真值框。两个损失配合使用效果比单独用任何一个都要稳。还有一个小技巧是auxiliary loss。DETR解码器的每一层都会输出预测训练时会给每一层都算一次损失并把这6层的损失加在一起回传。这个设计能显著缓解深层次解码器的梯度消失问题也加速收敛。这个技巧后来被很多DETR变体继承比如Deformable DETR和DN-DETR都用了auxiliary loss。所以如果你用官方代码训练默认就带上了这个机制不用额外操心。4.4 推理端到端流程从模型输出到业务结果推理时输入图像同样会先被预处理到固定尺寸长边不超过1333短边不小于800然后经过骨干、编码器、解码器最后拿到100个输出预测。接下来按置信度阈值过滤掉背景类和低置信度的框比如阈值设0.7如果业务场景只关心TopK个目标再对置信度排序后取前K个即可。这里分享一个实际工程经验DETR的框坐标输出头是用归一化中心坐标宽高格式表示的测试时需要转成像素坐标才能传给下游。转换公式很简单x cx * widthy cy * heightw w_norm * widthh h_norm * height其中width和height是原图宽高。如果输入图像被等比缩放填充过还要注意做反向映射。我第一次上线DETR服务的时候就因为在坐标还原上多算了Padding偏移线上结果在图像边界处总差几个像素排查了半天才找到问题。5. 常见问题与排查技巧实录5.1 训练不收敛或损失震荡很大怎么办DETR的训练窗口比较长前几个epoch损失下降慢是正常的但如果20个epoch后损失还在原地徘徊就要优先检查这几个点。一是学习率是否过大。我自己的经验是Batch Size缩小时学习率不跟着缩是初学者最容易犯的错误这会导致优化过程在损失曲面边缘反复横跳。二是骨干网络的预训练权重是否被冻结或错误初始化。DETR的骨干如果从头训练收敛速度会比加载ImageNet预训练慢很多很多至少在COCO这种数据量上最好不要尝试。三检查是否用了auxiliary loss如果自己写了简化版DETR而没有加auxiliary loss解码器深层的梯度信号会很弱也有可能导致训练卡住。5.2 大目标还行小目标检不出来DETR原版在小目标检测上的效果不算好这是一个结构性的问题CNN骨干输出特征图是32倍降采样一个小目标在原图上可能只占20×20像素降采样后连一个特征点都占不满Transformer再能干也没有足够的特征信息可用。所以如果业务场景里小目标多原版DETR基本不适用要做改进。最直接的办法是换用Deformable DETR它有类似FPN的多尺度特征机制会在不同分辨率的特征图上做可变形注意力效果会好很多。另一个思路是提高输入分辨率让特征图保留更多细节但显存消耗会同步上升。实在不行还可以在骨干部分引入FPN结构把输出特征图分辨率从32倍降到16倍甚至8倍再送入Transformer编码器。5.3 显存占用高训练跑不起来DETR的显存开销主要来自Transformer编码器的全局自注意力以及解码器6层堆叠的交叉注意力。800×800的输入编码器自注意力的计算复杂度是O(N²)N25×25625这个数量级不算特别爆炸但Batch Size一上去显存还是会有点疼。我的经验是先用梯度累积跑通小Batch比如Batch Size2、累积32步等效Batch Size64先看看模型能不能收敛再逐步提高Batch Size。如果显存确实很紧张可以考虑用混合精度训练DETR对FP16的容忍度还不错只要在Loss缩放上稍微注意一下。另外把编码器层数从6层减到3层是显存和精度之间比较划算的折中方案。5.4 为什么会出现同一目标多次重复框虽然DETR的设计目标是避免NMS但训练不够充分或者查询数量设得太少时偶尔还是会输出几个高度重叠的框。我排查下来最常见的原因是目标数量接近查询数量上限100个查询不够用导致模型只能把两个查询分配到同一个目标上输出了两个大同小异的框。解决办法有两种。一是增加对象查询数量比如改成300或1000这能明显缓解重复框问题但推理速度会变慢二是在推理端加一个宽松的NMSIOU阈值设0.8以上只合并那些高度重叠的框。我实际项目里两种方法都用了查询数量300加上非常宽松的NMS效果最稳既不会丢框也不会重复框。6. 从DETR到Deformable DETR与其他变体6.1 原版DETR的痛点推动了后续演进原版DETR奠定了“目标检测集合预测”这个范式但它有两个很突出的短板一个是收敛太慢另一个就是上面提到的小目标效果差。这两个短板的根源都在于“全局自注意力”每层每个query都要去看全图的特征计算成本高而且要学很久才知道哪些位置是有价值的。Deformable DETR是针对性最强的改进版本。它把标准自注意力换成了可变形注意力每个query不再是全图扫描而是通过一个轻量网络预测出一组采样点的偏移然后只在这几个采样点上做注意力。这样计算复杂度从O(N²)降到了O(N·K)其中K是采样点数量一般默认只有4到8个同时还能通过多尺度特征来提升小目标检测能力一举两得。我在自己的项目上尝试过把Deformable DETR的多尺度模块单独抽出来做特征增强效果也很明显。6.2 后续变体“降噪训练”与“去模糊”DETR这条线后来还有几个值得一提的演进。DN-DETR引入了一个“降噪训练”的机制训练时给真值框加一些随机扰动让对象查询去重建完整框这相当于给模型提供了额外的学习信号收敛速度比原版快很多。DINO则像是在DN-DETR基础上又加入了对比学习和更好的初始化直接刷新了COCO上的性能记录。另一个方向是Conditional DETR它把对象查询拆成“内容查询”和“位置查询”两部分让交叉注意力在“看什么”和“看哪里”上各司其职。这个改动看似不大但标准注意力的计算结构改变了解码器的训练效率提升明显。6.3 从DETR到更广泛的视觉Transformer应用DETR的影响早就超出目标检测本身了。它证明了一个思路Transformer结构可以直接作用于视觉特征并且替代掉大量手工设计的任务专用模块。后来的SAMSegment Anything Model、CLIP的Text Encoder等大模型虽然从任务到结构都跟DETR差别很大但底层“图像特征序列化自注意力全局建模”的思路其实都能从DETR里看到影子。如果想把DETR迁移到自己的场景我的建议是先跑通官方代码把骨干、编码器、解码器、匹配、损失这几个部分的代码逐一读一遍不要急着改。只有真正理解了“匈牙利匹配为什么能替代手工匹配”“位置编码为什么是加法不是拼接”这些细节才能在后续换骨干、换Loss、换推理策略时心里有底。按我个人的实际经验DETR类模型在一个新场景里最怕的不是精度不够而是“训练不上来”。它不像YOLO那样调几个训练技巧就能很快看到效果需要更多耐心去等它收敛。但一旦训练稳定了推理时那种“干干净净直接输出结果”的体验确实比传统检测器舒服得多。如果你也在折腾DETR建议先拿小数据集把整个训练流程跑通再逐步放大这样会少掉很多头发。