
DETR是我在Transformer大规模进入视觉领域之后第一个认真从头读到尾、又从零实现过关键模块的目标检测工作。在那之前我做了不少基于Faster R-CNN和YOLO系列的落地项目习惯了锚框、NMS、候选区域这些概念第一次读DETR论文的反应和大多数人一样这玩意儿真的能收敛没有锚框和NMS靠一个Transformer做集合预测竟然能在COCO上直接和Faster R-CNN打平后来我把DETR跑通又在自己的数据集上调过、踩过坑才慢慢理解它为什么是目标检测走向端到端的转折点。这篇博文我想把DETR从思想到实现完整拆一遍重点讲清楚那些论文里一句话带过、但实际操作时决定成败的细节。1. 从非极大值抑制到集合预测DETR要打破的旧范式1.1 检测的本质是集合预测但旧方法一直在绕路在做目标检测的时候输出层究竟在干什么理解这一点是读懂DETR的前提。传统检测器不论YOLO、SSD还是Faster R-CNN其实都做了一件事在图像上铺大量的候选位置。以锚框为例一张图会产生几万甚至十几万个锚框每个锚框负责预测这个框里有没有物体、是什么类别、和真实框偏移多少。但一张图里目标通常只有几个、几十个这就导致绝大多数锚框是负样本。正负样本极度不均衡于是有了Focal Loss很多锚框高度重叠于是有了NMS非极大值抑制来去重。检测器的后处理链路本质上都是在给先预测一大堆再删掉多余项这个策略擦屁股。从数学视角看目标检测的输出应该是一个无序的集合集合中每个元素是一组二元组类别框坐标这个集合的大小等于图像中目标的个数。所谓端到端就是希望网络直接输出这个集合训练时计算预测集和真实集之间的距离。但集合是无序的怎么定义两个集合的距离这就是DETR最核心的创新点把检测建模成二分图匹配问题用匈牙利算法找到预测和真值之间的最优匹配然后计算匹配上的预测和真值之间的损失。整个过程没有锚框、没有候选区域、没有NMS也不需要人为设计正负样本分配策略这正是端到端的含义。1.2 为什么以前没人用Transformer做检测既然集合预测这么好为什么早期没人用Transformer实现端到端检测一个直观原因是注意力机制的归纳偏置问题。CNN自带局部性、平移等变性等强烈先验非常契合图像的局部纹理特征提取。而Transformer在NLP领域是处理序列的把它平移到视觉上需要解决计算量爆炸问题——图像是二维的直接把每个像素当作序列的一个token分辨率稍微一高自注意力的计算复杂度就是O(N²)根本吃不消。DETR的聪明之处是只让Transformer处理CNN骨干网络输出的低分辨率特征图比如步长为32的特征图COCO输入尺寸800x600时特征图也就25x19左右token数量大约几百个Transformer完全可以承受。另一个原因在于query机制。Transformer解码器天然适合给定固定数量的查询输出对应数量的预测结果这个范式。DETR把解码器的输入query固定为100个可学习的向量强迫解码器学会用这100个query去表示图的各个区域里是否存在物体。DETR的伟大不在于把Transformer搬到了检测上而是用一个统一的集合损失函数优雅地解决了之前的检测器最丑陋的部分——正负样本分配和后处理去重。2. 网络结构逐层拆解CNN骨干到Transformer解码器之间的数据流DETR的网络结构可以分为四个部分CNN骨干网络、Transformer编码器、Transformer解码器、检测头。很多人看DETR结构图时觉得不难但真正实现起来才发现特征图从CNN出来后怎么进编码器、位置编码怎么加、query如何与图像特征交互、输出怎么变成框的坐标每一步都有讲究。2.1 骨干网络不只是提特征还要做通道压缩骨干网络用的是ResNet-50或者ResNet-101但需要去掉最后的平均池化和全连接层。实际使用时输入图像经过归一化、缩放到短边至少800像素、长边最多1333像素然后送入ResNet得到下采样32倍的特征图。比如输入是800x600经过ResNet-50的第四阶段C5输出就是25x19通道数为2048。这个特征图会先经过一个1x1卷积把通道数压缩成256再进入Transformer编码器主要是为了控制Transformer的计算量。论文里选ResNet-50而不是更深的网络原因很实际Transformer本身的参数和计算量已经不小了编码器在COCO上要跑36个epoch骨干网络太深会导致训练时间成倍上升收益却有限。这里有个细节值得注意为什么要取下采样32倍的特征图而不是16倍或者8倍下采样倍数越高特征图越小每个token对应的原图感受野就越大。对于一个目标检测任务来说如果特征图分辨率太高自注意力的计算量会以平方速度上涨而如果太低小目标的信息会严重丢失。DETR选择32倍下采样是平衡计算量和召回率的结果。这也是DETR小目标检测差的一个重要原因后面我会专门讲。2.2 编码器序列到序列的全局上下文建模特征图经过1x1卷积压缩通道后会从形状(B, 256, H, W)展平为(B, HxW, 256)再与位置编码相加送入Transformer编码器。编码器部分和NLP中的标准Transformer编码器几乎一样由多个重复的Encoder Layer组成每个Layer包含一个多头自注意力模块和一个前馈网络两者都使用残差连接和层归一化。不同点在于位置编码的注入方式DETR没有像NLP那样把位置编码直接加到token embedding上后只做一次而是将空间位置编码加到每个Encoder Layer的Query和Key上Value不变。为什么要加在Query和Key上因为自注意力计算的是Query和Key之间的相似度如果希望注意力分数反映哪些位置之间相关性高这些位置信息必须在计算相似度时参与进来否则一个像素和远距离另一个像素如果特征相似注意力就会分散。而Value是真正要聚合的信息加上位置编码反而会污染内容特征。这个细节直接抄自Transformer在NLP里的相对位置编码思想但DETR用了一个更简洁的实现——正弦位置编码。正弦位置编码是这样生成的对于特征图的每个位置(x, y)对应一个256维的向量维度下标从0到127是x方向的正弦编码128到255是y方向的正弦编码。这种编码和NLP里的正弦位置编码一脉相承好处是不同位置的编码向量有一定区分度且能泛化到未见过的大尺寸特征图上。不过在实际工程中越来越多的人用可学习的绝对位置编码效果和正弦编码差不多但更灵活。2.3 解码器object queries如何逐步挑出目标解码器是DETR里最反直觉的部分。它的输入不再是图像的某个局部特征而是100个可学习的向量论文称之为object queries。这100个向量初始值是完全相同的不对是随机初始化、相互独立的。它们和图像特征没有直接关系纯粹是100个要预测什么东西的占位符。解码器的结构也是标准Transformer解码器但和NLP解码器有个重要区别没有掩码机制也不需要自回归。在NLP里解码器生成序列时必须一个一个token地输出当前token只能看到之前的token所以需要masked self-attention。DETR的输出是一个无序集合100个query在每层解码器里都互相可见彼此通过自注意力交换信息避免重复预测同一个目标。每个Decoder Layer的操作顺序是object queries先经过一个自注意力层互相商量各自负责图像里的哪个区域然后作为Query与编码器输出的图像特征做交叉注意力从图像中提取对应区域的特征最后经过前馈网络输出更新后的特征表示。经过6层解码器100个query各自携带的语义信息已经从随机初始化变成图像中某个区域的特征聚合成的高层语义。最后送入检测头线性层预测类别另一个三层MLP预测框坐标中心点坐标、宽高四个值。2.4 检测头和损失计算从类别分数到完整框输出的链路检测头很简单一个线性层把256维特征映射成类别分数——注意这个类别分数的维度是91其中90个是COCO类别外加一个特殊的背景类别也就是None类。另一个三层MLP隐藏维度256输出4个数值归一化在0到1之间分别是中心点x、中心点y、宽w、高h的偏移量相对于图像宽高的比例。至于DETR为什么用MLP回归框而不是线性层经验上的解释是框回归是非线性映射3层MLP的表达能力比单层线性层强不少实验也证明MLP比线性层有大约1个点的AP提升。这里有一个许多初学者容易忽略的点100个query并不是每张图都会输出100个框只有预测类别分数达到阈值的query才算有效输出。在推理时网络的100个输出中很多预测为背景类别直接丢掉即可根本不需要NMS。这正是DETR能去掉NMS的原因不是靠后处理过滤重叠框而是靠训练时集合匹配的惩罚机制让模型自己学会不重复预测同一个目标。3. object queries是什么理解DETR绕不开的灵魂概念3.1 从区域建议到可学习的查询object queries大概是DETR里最难向别人解释清楚的概念。我第一次给别人讲DETR对方问了一句这100个query到底代表了什么我当时的回答是你可以理解为100个有没有物体在哪里的问题。这个比喻后来我发现非常有效。如果把检测看作一个看图说话的过程那么每个object query就是一句话的问题图像里有没有一个猫如果有框在哪解码器的交叉注意力层就是让这个问题和图像特征做交互从图像里找到回答这个问题的证据。但100个query是热身学习的不是提前预设第3个query负责找猫、第5个query负责找车。训练结束后每个query会自动分化出自己的分工有的倾向预测大目标有的倾向预测小目标有的倾向关注图像的左上区域。论文里可视化了一些query的注意力图你会发现不同的query呈现出不同的空间偏好和尺寸偏好。3.2 query的插槽分配机制一个更准确的类比是插槽slot。DETR的解码器就像一个固定大小的插槽集合每个插槽独立地去图像里认领一个目标。训练时集合匹配会让每个插槽尽量匹配一个真值框一旦某个插槽对应了真值框它受到的监督信号就是输出这个框的类别和坐标没有匹配到真值的插槽就输出背景。于是模型学会了让插槽之间通过自注意力互斥——如果一个插槽已经确认了某个目标另一个插槽就不应该再去预测同一个目标这就是为什么推理时不需要NMS也能保证不重复预测。在实际实现中object queries的初始值是用nn.Embedding(100, 256)定义的属于模型参数随训练更新。我觉得它更像一组提问模板训练前是随机噪声训练后被塑造成了100种有区分度的提问方式每一类提问方式擅长发现某种位置、某种尺度、某种类别的目标。值得注意的是query本身和类别没有固定对应关系同一个query在不同图像里可能预测不同的类别它关注的更多是目标的几何属性和上下文关系。3.3 query数量如何选择100够吗DETR论文把query数默认设为100理由是COCO数据集一张图最多出现约100个目标设置100可以覆盖绝大多数场景。实际调参时如果目标类别少、密集程度低比如工业质检场景一张图最多出现几个缺陷30到50个query就够如果做密集小目标检测比如细胞计数、遥感图像目标检测100个可能不够需要增大到300甚至900。但要提醒的是query数不是越大越好。query数增大后解码器自注意力的计算量平方上升训练时集合匹配的匈牙利算法计算量也上升而且大量query有可能导致模型更难收敛。我自己的经验是先用默认100跑通再可视化一下每个query匹配到的真值框分布根据实际密集程度动态调整不要一上来就无脑加。4. 二分图匹配与集合损失DETR端到端训练的灵魂4.1 为什么NLP的损失函数不能直接搬到检测上有了100个预测输出真值可能是5个框怎么计算损失最朴素的想法是逐一配对第一个预测和第一个真值比、第二个和第二个比多余的预测算背景。但这个做法会导致一个严重问题模型不知道哪个预测对应哪个真值即使某个预测已经接近某个真值因为编号错位损失还是会很大。预测和真值之间没有固定对应关系而检测的输出是集合天然无序这决定了我们不能直接套用逐元素损失。DETR的思路是先通过匈牙利算法求出损失最低的匹配组合再把匹配关系固定下来计算每个匹配对的损失。这个匹配结果在每一轮训练迭代中都不同比如第一个query在上一轮匹配到了真值框A这一轮可能匹配到了真值框B都没关系只要能最小化整体匹配损失。所以集合预测损失是一个两步走的过程第一步找最优匹配第二步计算匹配下的损失。4.2 匈牙利匹配的代价计算细节匈牙利算法解决的是一个组合优化问题给定成本矩阵C其中C(i, j)表示第i个预测和第j个真值之间的匹配代价求一个一一映射使总代价最小。DETR中匹配代价的计算公式为C(i, j) -λ_cls * p_hat_i(c_j) λ_l1 * ||b_i - b_j||₁ λ_giou * L_giou(b_i, b_j)p_hat_i(c_j)是第i个预测在第j个真值类别上的概率预测得越准这个代价越负b_i是预测框b_j是真值框后面的两个损失衡量框的回归误差。注意这里的匹配代价和最终的训练损失是两个不同量匹配代价不需要梯度回传只负责找到最优配对消耗代价确定后训练损失中才会用sigmoid交叉熵损失、L1损失和GIOU损失来更新网络参数。一个很有意思的细节DETR在匹配代价里用了p_hat_i(c_j)的负对数概率但训练损失里用的是sigmoid交叉熵。这意味着匹配更倾向于类别预测置信度高、框回归误差小的配对而训练则用更稳定的损失函数优化网络。两类参数λ_cls, λ_l1, λ_giou论文里分别设为1、5、2。项目实操时如果你发现自己数据集上类别不均衡可以适当加大λ_cls让匹配更偏向分类准确有助于缓解不均衡问题。还有一个细节容易踩坑真值框需要填充到100个填充项用特殊的无物体类别表示为背景匹配时这些填充槽位只参与背景分类代价不参与框回归代价。也就是说匹配代价矩阵是(100, num_truths (100 - num_truths))多出来的是背景槽任何预测和背景槽匹配都只计算分类代价希望模型倾向于预测对这些槽位输出背景。4.3 为什么推理时不需要NMS训练时的互斥惩罚传统检测器的NMS解决的是多个预测框对应同一个目标的问题。DETR通过训练时的集合匹配从机制上消除了这个问题的根源。我们可以这样理解训练时如果两个query都预测了同一个真值框匈牙利匹配只会把其中一个匹配给这个真值框另一个会被匹配为背景也就是说重复预测的那个query会承担背景分类的巨额损失梯度信号会强力打压这种行为。经过多轮训练解码器的query自注意力学会了相互抑制一个目标往往只会分配到一个query负责而其他query对它的响应趋近于零。这就带来了一个工程上的转变测试时不需要复杂的NMS后处理只需要把类别分数低于阈值的预测过滤掉剩下的直接输出。但实际使用中DETR还是可能出现少数的重复预测尤其是遮挡场景、密集场景。所以现在的主流做法是保留一个阈值很低的NMS作为兜底比如IoU阈值设为0.7几乎不会误删正确框但能兜住极端情况下的重复预测。这个经验在Deformable DETR里依然适用。5. 训练DETR必须知道的几个坑收敛慢背后的原因与对策5.1 为什么需要500个epoch注意力从“均匀分布”到“稀疏分布”的漫长旅程DETR最被诟病的缺点就是收敛慢在COCO上要训练500个epoch才能达到44左右的AP而Faster R-CNN的常规训练量是12个epoch。为什么会这样我的理解是Transformer编码器的注意力初始状态几乎是均匀分布的每个token都会注意到整张图的所有其他token。而目标检测需要的注意力是稀疏的——每个位置应该只关注同一目标和其他相关上下文的几个关键位置。从均匀分布到稀疏分布这个转变是DETR训练过程中最耗时的一部分。相比之下CNN的局部感受野自带稀疏性先验不需要花大量epoch去学习该看哪里。辅助损失是缓解收敛慢的一个重要手段。在每层解码器后面加一个FFN和分类头监督每一层解码器都直接预测目标这样梯度可以更直接地从深层传回浅层帮助每一层的解码器更快学会如何匹配目标。加了辅助损失后训练会明显更稳定收敛速度也能提升。实现细节上每层辅助预测的损失会以相同权重加到总损失上最后一层不设辅助损失避免重复计算。另一个对策是dropout。Transformer对过拟合非常敏感尤其是数据量不大的时候。实际使用中Transformer的dropout设为0.1比较稳妥骨干网络的dropout建议直接设为0——骨干网络是在ImageNet上预训练过的再给它加dropout会影响已经学好的特征提取能力。5.2 学习率和骨干网络冻结策略DETR使用的优化器是AdamW初始学习率设为1e-4骨干网络的学习率设为1e-5因为骨干是预训练好的用较小学习率做微调即可。batch size默认是16COCO上8卡分布式训练每卡batch 2。实际训练时如果你只有单卡且只能支持batch 2到4学习率要按比例缩小大概batch减半学习率减半这是Transformer类模型的通病学习率对batch size非常敏感。这里我建议的一个实用策略训练早期冻结骨干网络。刚训练时解码器的注意力还很混乱如果骨干网络的参数也在大幅度更新整个模型会非常不稳定。我在自研数据集上测试过前10个epoch冻结骨干只训练Transformer部分之后解冻骨干并用10倍小的学习率微调稳定性和最终精度都有提升。这个方法尤其适合数据集只有几千张的小规模场景。5.3 数据增强策略对端到端模型的影响DETR论文用的数据增强是标准的随机裁剪、随机水平翻转、多尺度训练。这里有一个容易忽视的细节随机裁剪如果直接把某个目标裁掉一半对于普通检测器来说还可以靠锚框硬扛但对DETR这种集合预测模型来说被切掉的目标会让学习信号变得混乱——这个目标到底算存在还是不存在所以实际工程上很多实现会使用带物体性检查的随机裁剪——先判断裁剪区域内是否含有完整目标如果没有就重新裁剪或者放弃这次裁剪保证裁剪后的图里目标尽量完整。这个策略在YOLOv5之后的版本中也出现过在DETR上同样有效。多尺度训练对于小目标改善很大。DETR的编码器端到端结构没有特征金字塔多尺度信息的获取完全依赖自注意力如果训练时输入尺寸固定模型很难泛化到不同尺度的目标上。我在训练时把短边随机缩放范围设为480到960长边限制在1333以内模型小目标AP和不常见宽高比目标上的表现有明显提升。6. DETR在自有数据集上的实测精度、速度、显存占用全记录6.1 训练自定义数据集需要改动哪些模块如果你想在自己的数据集上训练DETR需要改动的地方比想象中少数据加载器换成自己的格式类别数改成自己的类别数object queries数量根据你要预测的最大目标数调整。其他结构可以完全不动因为DETR是一个和数据集无关的通用检测框架。类别数决定最终分类头的维度这里有一个和Faster R-CNN系列不一样的地方DETR分类头的输出维度是num_classes 1多出来的维度是无物体类别而Faster R-CNN在训练时通过RPN和采样器区分前景背景最后做二分类。这个差异反映出两种损失哲学的不同Faster R-CNN靠正负样本比例人工调节训练信号DETR靠背景类别消化掉没有匹配到任何真值的query。数据集标注格式方面如果你的标注是COCO格式的JSON那非常省事如果是VOC格式的XML需要转换成COCO格式因为DETR官方代码和大多数第三方实现都直接依赖COCO数据集的接口。目标数量上限对query数量的影响我在前面已经讲到这里再强调一次如果数据集里出现超过query数量的目标多余目标在训练时无法匹配到任何query就等于告诉模型这个目标不存在后果是模型永远学不会一张图里出现大量目标时应该输出多少框。6.2 显存占用一个容易被低估的问题显存占用是DETR普及路上的一个实际障碍。在2080Ti 11GB显存上输入短边800、batch size为1时ResNet-50加DETR的显存占用大约在7到9GB勉强能跑。如果你用Deformable DETR的多尺度版本显存占用会进一步上升。实际训练时我推荐使用梯度累积batch size设为1、梯度累积16步等效于batch size 16显存压力小很多收敛效果和真实大batch接近。推理阶段的显存占用相对友好DETR前向计算全程只需要O(HW N²)量级的显存N是query数比训练时的小很多。模型参数量的体量大约在41MResNet-50骨干左右其中Transformer编解码器部分约占17MONNX导出后FP32模型大约160MB量化到FP16后可以在边缘设备上实时运行但CPU推理会明显偏慢因为Transformer的自注意力对矩阵运算耗时较为敏感。6.3 小目标检测差的根因与应对DETR在COCO上的小目标APAPS比大目标APAPL低了将近20个百分点这个差距在端到端检测器里是比较明显的。根因有两点一是骨干网络下采样32倍小目标的特征在空间维度上几乎被抹掉了二是Transformer自注意力是全局的计算复杂度高如果编码器用高分辨率特征图训练和推理开销让人难以接受。目前最有效的应对方案是用Deformable DETR风格的多尺度特征融合。Deformable DETR让编码器直接在ResNet的C3、C4、C5三个层级的特征图上计算可变形注意力每个query只采样少量关键点既保留了多尺度信息又把计算量降到可控范围。如果你不想换模型也可以在DETR里加一个简单的FPN把C3、C4、C5的特征融合后送入编码器代价是Transformer的序列长度变成四倍左右显存和训练时间都需要重新评估。7. 从DETR到Deformable DETR一个实操者视角的改进路线梳理7.1 收敛速度和性能对DETR的全面反思DETR的贡献是开创性的但它的不足在落地过程中会被放得很大。50%以上的COCO训练时间花了500个epoch相比之下Deformable DETR在50个epoch内就能达到接近的效果。Deformable DETR的改进点可以用一句话概括把DETR中所有全局注意力替换成可变形注意力——每个query不是看全图所有位置而是只关注若干采样点这些采样点的位置是网络自己学习的。可变形注意力的好处有三个一是计算量从O(N²)降到O(NK)K是采样点数量通常设为4到8显存和时间开销显著下降二是天然适合多尺度特征可以直接在多个特征层上采样增强了处理小目标的能力三是收敛更快因为注意力初始就有了空间的稀疏先验不需要从均匀分布慢慢学到稀疏分布。7.2 把DETR改造成Deformable的工程要点从工程角度T的改造涉及几个具体的模块替换编码器中每个Transformer Layer里的自注意力模块换成Deformable Attention模块这个模块除了输入特征之外还需要多尺度特征图的空间位置和归一化坐标解码器中的交叉注意力模块同样换成Deformable Attention让每个object query从多尺度特征上直接采样。而解码器第一层那个自注意力模块可以保留普通注意力因为100个query之间的交互计算量很小效果也更稳定。这里有一个值得注意的细节Deformable DETR在解码器的每个交叉注意力部分增加了掩码过滤和遮挡感知之类的trick实际github实现版本很多选型时要关注你的数据分布。如果目标尺寸相对均匀原始Deformable DETR就够用如果大小跨度很大建议选择带iterative bounding box refinement和two-stage机制的版本。前者让每层解码器都输出一组框下一层基于上一层的框做精细化后者让模型先生成候选区域再用解码器做sparse attention两种改动在小目标上的收益都很明显。7.3 除了Deformable还有哪些值得关注的改进方向DETR的改进家族近年已经非常庞杂从工程实用角度我梳理出三个有代表性的方向一是收敛速度优化除了Deformable系列Conditional DETR通过让query在解码器里动态生成条件位置编码来降低训练难度收敛速度比DETR快很多二是查询机制改进比如DAB-DETR把object queries从纯可学习向量改成可学习的锚点坐标和宽度高度让每个query初始化时就带框的形状先验三是训练策略优化比如DN-DETR用去噪训练的方式把一些带噪声的真值框作为额外query让模型去重建显著加快了训练收敛速度。如果你在做工程项目、时间紧、GPU资源有限我个人的建议是直接在Deformable DETR基础上开始不要从原生DETR改。原生DETR更适合学习和理解检测器的范式变革落地时它的收敛成本和显存消耗对中小团队并不友好。8. 实操记录用Pytorch复现DETR时我遇到的几个绊脚石8.1 位置编码的广播机制一个debug了一整晚的错误第一个绊脚石是位置编码的维度。DETR分发特征图时输入的张量维度是(B, C, H, W)展平后变成(B, HxW, C)。做cross attention时作为Key的图像特征和作为Query的object queries维度上差得很多。位置编码的形状是(B, C, H, W)需要展平成(B, HxW, C)但如果直接把位置编码加到图像特征上要确保它是加到序列维度的特征上而不是误加到batch或通道维。我在第一次实现时把位置编码的H和W维度顺序搞混了结果训练曲线完全不动debug了好几个小时才发现是位置编码广播错了。建议实现时把生成位置编码和展平/还原封装成独立函数并且在编码器和解码器里都用同一套函数避免维度不一致。还要加一句assert位置编码的shape和特征图的shape必须完全一致宁可报错也不要静默广播成错误的结果。8.2 匈牙利匹配在torch中的向量化实现难点与加速技巧匈牙利算法的朴素实现是逐行扫描的循环次数高达100乘真值数。对于COCO这种真值平均7个左右的数据每个batch要执行16次每次循环300行速度勉强能接受。但如果数据密集比如一张图50个真值朴素实现就会让训练速度明显下降。加速方案是把代价矩阵计算向量化先把100个query的预测和真值框分别扩展为(100, num_truths)的矩阵一次性计算出全部代价再用scipy.optimize.linear_sum_assignment求解。实测这种方式比逐对计算快一个数量级。如果你完全不想依赖scipy也可以用torch自带的cuda实现但要注意匈牙利算法是序列决策过程cuda加速收益有限scipy在CPU上求解已经足够快瓶颈通常在代价矩阵计算而非匹配本身。8.3 类别均衡与长尾分布训练DETR时损失震动的常见原因目标检测数据集普遍长尾分布头类样本多、尾类样本少。DETR的集合匹配让每个query在匹配时倾向于匹配代价更低的类别也就是说对于样本少的尾类模型可能认为预测成背景或头类会更安全。这会导致两个现象训练损失下降正常但尾类AP几乎为0或者训练中期损失突然上升因为模型对某个难分类别的置信度突然下降导致匹配关系大规模重组训练震荡。缓解办法有几种我的经验是先把匹配代价里的分类系数从1调到2.5让匹配更重视分类质量而不是让框回归主导匹配然后调整真值数量长尾数据集里很多目标真值框本身标注质量就不高L1损失对标注框的偏移非常敏感一个标歪的框会严重拉偏回归梯度。最后是用EMA指数移动平均保存模型参数防止在尾类上过拟合导致震荡。9. 部署DETR到生产环境的真实体验与建议9.1 ONNX导出时最常见的兼容性问题DETR部署时最麻烦的是Transformer多头注意力里的reshape和permute操作ONNX导出时容易遇到算子不支持的问题。我用的Pytorch版本比较新1.13以上配合ONNX opset 13可以基本解决但仍有几个坑一是多尺度位置编码中的interpolate操作在opset 11以下不支持二是匈牙利匹配在推理阶段不需要导出前必须把匹配逻辑从forward里剥离开三是一些实现里为了训练方便在解码器里保留了dropout导出前要设为eval模式并设置torch.no_grad。建议是先跑一遍torch.onnx.export然后用onnxruntime或TensorRT的onnx解析器做一次前向对比Pytorch和部署框架的输出是否一致最大误差控制在1e-3以下。因为DETR的输出是集合即使很小的数值误差也可能导致某个目标的类别分数从阈值以上掉到阈值以下。9.2 TensorRT加速与精度损失DETR在TensorRT上能做FP16推理速度大约是Pytorch的2到4倍。但注意FP16下Transformer的softmax和层归一化对精度非常敏感某些层需要用FP32保留这就要做层级别精度控制。我的做法是先全FP16跑一遍看目标AP掉多少如果掉得明显就把层归一化层和softmax层强制用FP32通常这样精度损失可以控制在0.5个AP以内。还有一种做法是把DETR的encoder部分全部保留FP32decoder部分用FP16这样精度损失更小。因为编码器处理的是高维特征上的自注意力数值范围波动大FP16很容易溢出解码器的query特征是低维的对数值误差的容忍度更高。具体哪些层用FP16建议通过实际精度测试确定不要盲信默认配置。9.3 在边缘设备上的取舍模型压缩和蒸馏思路如果部署目标明确是边缘设备我建议认真考虑一下DETR是否值得。DETR的FP32模型大约160MB比同等精度的YOLOv8大不少在树莓派这类设备上推理速度很难让人满意。如果非要用DETR可以考虑三个方向一是蒸馏用大模型DETR当teacher把Transformer层输出的特征蒸馏给小模型二是减少encoder层数实测encoder从6层减到3层AP大约下降1到2个点但速度提升非常明显三是把backbone从ResNet换轻量化网络比如MobileNetV3通道数调小但需要重新预训练和调参成本不低。综合来看DETR类模型更适合算力充足的服务器端场景边缘设备还是YOLO系更实际。10. 写在最后DETR给我带来的认知转变做完DETR的实验和工程落地我对目标检测这个任务的理解发生了本质变化检测的问题核心在于表示和匹配而不是在于怎么设计更强的手工先验。锚框、RPN、NMS都是为了让模型更好优化而设计的外部辅助结构DETR证明了当模型有足够的表达能力和训练时间时这些手工模块可以被一个统一的集合学习框架替代。这个思路后来也影响了我在其他视觉任务上的设计——只要你把任务重构成一个集合预测问题Transformer解码器那套query机制就可以迁移过去。比如实例分割领域的Mask2Former、全景分割领域的PANet走的都是这个路子。如果你现在正准备学习DETR我的建议是别只看论文结构图去把代码跑通然后手动改几个核心模块比如把query数量从100改成10看看模型的预测行为会怎么变把位置编码去掉看看训练loss的变化趋势把匈牙利匹配换成固定匹配看看最终AP会出现什么样的断崖。这些实验做完你对端到端检测的理解会比读十篇论文都深。