
我从 2020 年第一次读完 DETR 的论文到真正在自己的数据集上把它跑通中间隔了将近一年。不是因为论文难懂而是被当时各种惯性思维困住了目标检测不用 Anchor 还能怎么做不用 NMS 结果还能看吗然而 DETR 用一套干净的 Transformer 架构把这两个困扰检测界多年的“标配”直接拿掉了。这篇文章我想把这些年对 DETR 的理解、动手复现时的关键细节以及在真实项目里踩过的坑一次性整理出来希望能帮你少走弯路。1. 从 Anchor 到 NMS旧范式里让人头疼的两个“标配”做检测的老玩家对 Anchor 和 NMS 都不会陌生。Anchor 是早期两阶段和一阶段检测器赖以生存的“先验框”机制NMS 则是把重复预测框合并掉的后处理手段。这两个东西几乎出现在所有主流检测框架里直到 DETR 直接把整条链路简化掉。1.1 Anchor 机制为什么这么麻烦Anchor 的核心思路是在特征图的每个位置预设一组不同尺度、不同长宽比的矩形框然后让网络去预测“这个框里有没有目标”“框的偏移量是多少”。听起来不算复杂但实际操作中有一堆暗坑。超参数爆炸一组 Anchor 需要配置 anchor 大小、长宽比、缩放比例、步长等不同数据集、不同目标尺度最优配置完全不同。正负样本分配复杂Anchor 数量往往成千上万但真正负责匹配目标的“正样本”只占极少比例需要精心设计 IoU 阈值和采样策略。计算冗余严重大量 Anchor 在训练和推理时都在被计算但绝大多数是背景框。我在跑 Faster R-CNN 系列时最痛苦的莫过于调 Anchor 参数换一个数据集可能就要重新统计目标分布、重新聚类长宽比。每次看到聚类生成的 anchor 尺寸我都觉得“好像合理又好像哪里不对”因为最终效果只能靠试错验证。1.2 NMS 的“非理性”之处NMS非极大值抑制解决的问题是网络经常对同一个目标输出多个互相重叠的预测框需要保留分数最高的框抑制掉其他重合度高的框。听上去很合理但 NMS 有几个天生的毛病阈值需要人工定NMS 的 IoU 阈值定高了重叠目标会被误删定低了重复框又会残留难以平衡。无法后向传播NMS 是基于贪心策略的不可导操作没法直接参与端到端的训练优化。把“预测”变成了“猜”当两个真实目标靠得很近时NMS 极容易把其中一个框直接抑制掉导致漏检。在行人检测或者密集场景里这种“阈值博弈”我见得太多了。每次和产品经理对齐精度时改 NMS 阈值往往是个抖机灵的操作——那个目标检测结果好像“变好了”但往往只是把漏检风险转移到了别的类型上。可以说NMS 是当时检测管线里最不优雅、最不“深度学习”的一环。2. DETR 的核心拆解集合预测、对象查询与匈牙利匹配是如何协同工作的DETRDetection Transformer首次把检测任务真正建模成“集合预测”输入一张图输出一个固定大小的预测集合每个元素包含目标的类别和归一化边框坐标。这个集合的大小是预设的比如 100 或 300如果图中目标少于预设数就用“无目标类别”填充。2.1 从“回归一堆框”到“预测一组集合”传统检测器输出的框数量是不固定的因为每张图的物体数量不同。Anchor 也好NMS 也好本质上都是在处理这种“数量不固定”带来的匹配问题。DETR 换了个思路不管图里有多少目标模型每次都输出固定数量的预测比如 100 个。多的位置全部预测成“本身没有目标no object”。这种设计让模型不再依赖大量手工设计的先验框而是靠学习到的“查询向量”直接预测目标的类别和位置。每个查询向量会经过 Transformer 的 decoder 层不断“关注”图像特征中的相关信息最后输出对应目标的预测结果。2.2 对象查询DETR 里最容易被误解的“魔法参数”对象查询object queries是 DETR 中最重要的设计之一也是最让初学者困惑的地方。它是 Transformer decoder 输入的一组可学习的位置嵌入learnable embeddings数量固定比如 100 个每个向量在训练过程中会逐渐“分化”形成不同的“查询分工”。打个比方这 100 个查询就像 100 个“探员”每个探员负责在图像里寻找特定类型的区域。有的探员倾向找“大物体”有的倾向找“小物体”有的可能集中在图像中心附近有的会分散到图像边缘。这种分工不是手工指定的而是在训练中自动涌现的。我在可视化对象查询的注意力图后第一次真正理解了它的行为逻辑有的查询确实会形成类似“小目标专查员”的模式有的则稳定聚焦在某个空间区域。这种隐式分工是 DETR 能取代手工 Anchor 的根本原因——匹配逻辑被编码在参数里而不是暴露在配置文件里。2.3 匈牙利匹配一次配对全程可导有了固定大小的预测集合训练时就要把预测集合和真实标注集合做一一匹配。DETR 使用的是匈牙利算法Hungarian Algorithm在二分图匹配中寻找全局最优的配对方案使匹配总代价最小。这里的代价函数很关键。常见形式是cost 分类损失类别预测与真实类别的负对数概率 边框损失L1 损失 GIoU 损失在训练中匈牙利匹配负责找到“每个真实目标由哪个预测框来负责”的最优方案。DETR 训练时的 loss 就是基于这个匹配好的对计算的。值得一提的是这个匹配过程本身也是可微的所以整个模型从输入图像到最终的 loss可以端到端地反向传播更新。这就解决了 NMS 不可导的痛点——匹配、回归、分类在同一个优化目标下进行不需要任何后处理来再看一遍结果。推理时只需要按预测分数由高到低保留前 K 个即可因为没有先验框带来的大量冗余预测不需要 NMS 来兜底。3. 位置编码与 Transformer 输入让模型“看见”位置的关键细节Transformer 本身不具备空间感知能力。要让 Decoder 能够区分图像里不同区域的信息就必须给特征融入位置编码。这个环节看似只是“加个编码”实际上对 DETR 的性能和收敛速度影响非常大。3.1 为什么 DETR 不能直接“看到”位置CNN 本身有局部感受野天然具备一定的位置归纳偏置但 Transformer 的 Self-Attention 是全局的、排列不变的把输入 token 的顺序打乱注意力计算的结果不变。对图像来说这意味着模型不知道“左上角”和“右下角”的区别除非我们显式把位置信息传进去。这也是 DETR 论文和后续工作反复强调“位置编码必须加”的原因。很多第一次上手 Transformer 做视觉任务的同学最常犯的错误就是漏掉位置编码、或者位置编码加错位置导致模型怎么训都收敛不了。3.2 DETR 中的位置编码到底怎么加DETR 使用的是一维位置编码还是二维位置编码论文实现里其实用的是基于宽高的二维位置编码分别对宽度方向和高度方向生成位置编码然后在通道维度拼接。具体做法是对输入图像先用 CNN Backbone如 ResNet提取特征图得到形状为[B, C, H, W]的特征。将特征图展平成序列即H × W个 token每个 token 的维度是 C。对 H 和 W 两个方向分别计算位置编码拼接成与特征维度相同的向量。在 Encoder 每一层的 Self-Attention 中将位置编码加到 query 和 key 上而 value 不加位置编码。为什么 value 不加位置编码Insight 在于value 提供的是“具体内容”位置信息已经在 query 和 key 的匹配环节发挥作用了。如果 value 也加位置信息会造成内容特征和位置信息的混合反而干扰回归头的预测。3.3 训练中的过拟合风险与数据增强策略位置编码让模型更容易“记住”训练集中常见的时空分布模式因此在数据量不足时更容易过拟合。我在用 DETR 训练小数据集比如 5000 多张图的场景时很快发现了过拟合迹象训练 loss 一路降低验证集 mAP 却停滞不动。最后帮上大忙的是大规模随机裁剪和缩放增强。DETR 官方配置里的随机裁剪random crop比例范围很大在 0.5 到 1.5 之间随机调整缩放再配合水平翻转能够有效打乱目标在不同位置的分布。如果你在自定义数据集上训练 DETR数据增强的力度一定要配足否则位置编码的那个“位置记忆”很快就会主导模型行为。4. 让 DETR 真正落地收敛慢、小目标弱、显存占用的实战调参记录理想很丰满现实中 DETR 的坑是真不少。我在最初训练 DETR 时最大的感受是为什么 loss 降得这么慢为什么训练 50 个 epoch 才勉强看到轮廓这一节把我实际踩过的坑和解决方法都记录下来。4.1 收敛慢的根源与对策DETR 相比传统检测器需要更多的训练轮次通常 500 epoch 才能完全收敛论文里是 300 epoch 大 batch。原因主要有两个集合匹配的非平稳性训练初期匈牙利匹配的配对关系频繁“跳变”同一个查询在迭代中可能不断被分配到不同的目标导致梯度更新信号不稳定。Decoder 的交叉注意力需要时间形成分工每个对象查询要经过学习才能“锁定”自己负责的目标区域这个过程在早期非常慢。我的做法和应对技巧供你参考足够大的 batch sizeDETR 对 batch size 很敏感论文使用的是 64 张图以上减少匹配不稳定的情况。显存不够时可以考虑梯度累积。初始学习率不要过大Transformer 模块比较敏感初始学习率建议 1e-4 起预训练 backbone 部分可以给 1e-5 甚至更低。使用 DETR 变体加速收敛比如 Deformable DETR 通过稀疏注意力机制将收敛 epoch 从 500 降到 50。如果项目周期紧推荐直接上变体。4.2 小目标检测弱不是 DETR 的“硬伤”但确实要处理DETR 原版在 COCO 上小目标的 AP 一直偏低原因是多尺度特征利用不足。CNN Backbone 输出的最高层特征图上小目标的语义信息几乎消失殆尽。原版 DETR 的解决方案是只使用单一特征图这确实让模型结构简洁但小目标召回率会打折。我在做遥感目标检测时小目标占比非常高单纯用原版 DETR 的 AP 比 Faster R-CNN 低了 5 个点左右。解决思路主要有三种引入多尺度特征融合模块类似 FPN把浅层高分辨率特征和深层语义特征一起送入 Transformer。使用 Deformable DETR 的多尺度可变形注意力它在每一层 decoder 中都会采样多尺度特征对小目标友好很多。在数据增强中强化小目标比如对图像做 Mosaic 增强、增加小目标的复制粘贴增强让模型有更多机会看到小目标。我在实际项目中试过方案 23 的组合小目标 AP 提升了大概 4 到 5 个点非常见效。4.3 显存占用和推理速度的真实体验Transformer 的全局注意力计算量是 O(n²)n 是 token 数量。对于 800×600 的输入特征图下采样 32 倍后仍有 25×19 约 475 个 token这个计算量本身不算大但如果输入分辨率再高比如 1280×1024token 数量会迅速攀升到 1280 个以上显存压力陡增。我个人的使用经验是训练阶段DETR 在 1080Ti 级别的显卡上很难跑太大的分辨率一般 800 左右是比较稳的。想要更高分辨率要么用梯度累积要么使用可变形注意力的稀疏采样来省显存。推理阶段DETR 比同体量的 CNN 检测器慢一些尤其是在没有 TensorRT 优化时。但好处是完全不需要 NMS 后处理省掉的耗时能冲抵一部分注意力计算的代价。工程加速如果在上线时遇到延迟瓶颈建议先转 ONNX再尝试 TensorRT 的 Transformer 算子优化。DETR 的结构比较规整转 ONNX 时没有太多兼容性问题比带 NMS 的自定义算子舒服多了。4.4 训练自己的数据集时输出头尺寸怎么改DETR 的分类头是一个简单的线性层加 softmax输出维度是num_classes 1多出的 1 代表“无目标”类别。如果你在自定义数据集上从零训练 DETR需要修改两个地方类别数量num_classes按照你的数据标注类别数设置。对象查询数量num_queries要大于单张图像中可能出现的最多目标数量否则目标会被强制舍弃。修改时有一个容易忽略的细节预训练权重中的分类层维度与你新任务不匹配加载权重时会出现尺寸错误。我一般会把这两层单独初始化并冻结 Backbone 的前几层在少量迭代后逐步解冻能有效避免迁移初期的不稳定。5. 代码级复现笔记从模型搭建到训练的完整关键点这一部分分享我在复现 DETR 时整理的代码级要点框架基于 PyTorch代码逻辑提炼自官方实现但做了简化说明方便你理解核心链路。5.1 Backbone 特征提取与序列化import torch import torch.nn as nn from torchvision.models import resnet50 class Backbone(nn.Module): def __init__(self): super().__init__() self.body nn.Sequential(*list(resnet50(pretrainedTrue).children())[:-2]) self.conv nn.Conv2d(2048, 256, 1) # 降维到 d_model256 def forward(self, x): x self.body(x) # [B, 2048, H/32, W/32] x self.conv(x) # [B, 256, H/32, W/32] return x这里我做了两件事去掉了 ResNet 最后的全连接层和平均池化层保留特征图。用 1×1 卷积把通道数从 2048 降到 256这是 Transformer 的d_model维度。5.2 Positional Encoding 的核心实现DETR 使用的是二维可学习或固定的正弦位置编码。下面这段代码来自官方实现的核心逻辑class PositionEmbeddingSine(nn.Module): def __init__(self, num_pos_feats128, temperature10000): super().__init__() self.num_pos_feats num_pos_feats self.temperature temperature def forward(self, mask): # mask: [B, H, W]1 表示有效区域0 表示 padding not_mask ~mask y_embed not_mask.cumsum(1, dtypetorch.float32) x_embed not_mask.cumsum(2, dtypetorch.float32) dim_t torch.arange(self.num_pos_feats, dtypetorch.float32) dim_t self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) pos_x x_embed[:, :, :, None] / dim_t pos_y y_embed[:, :, :, None] / dim_t pos_x torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim4).flatten(3) pos_y torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim4).flatten(3) pos torch.cat((pos_y, pos_x), dim3).permute(0, 3, 1, 2) return pos这个方法朝上往下累计了位置索引cumsum所以即使图像 padding 过位置编码也不会乱掉。计算出的编码会在每个 Transformer Encoder 层加载到注意力机制中。5.3 Transformer Encoder-Decoder 的搭建逻辑DETR 的 Transformer 部分可以直接用 PyTorch 的nn.Transformer来构建。我实际搭建时没有直接用nn.Transformer整体而是拆开 Encoder 和 Decoder 分别实例化方便对每一层做断点调试。from torch.nn import TransformerEncoder, TransformerDecoder from torch.nn import TransformerEncoderLayer, TransformerDecoderLayer encoder_layer TransformerEncoderLayer(d_model256, nhead8, dim_feedforward2048, dropout0.1) decoder_layer TransformerDecoderLayer(d_model256, nhead8, dim_feedforward2048, dropout0.1) encoder TransformerEncoder(encoder_layer, num_layers6) decoder TransformerDecoder(decoder_layer, num_layers6)需要注意这里的dim_feedforward2048是 FFN 中间层宽度Transformer 里的参数量和计算量多半都集中在 FFN如果你显存吃紧可以适当调小到 1024但精度也会有一定下降。5.4 匈牙利匹配与损失计算的实现训练 DETR 最关键的就是匹配和损失函数。匹配代价用到分类和框回归的加权和cost_class -pred_logits.softmax(-1)[..., :-1] cost_bbox torch.cdist(pred_boxes, tgt_boxes, p1) # GIoU 需要自行实现这里示意 cost_giou -generalized_box_iou(pred_boxes, tgt_boxes) C cost_class * 1 cost_bbox * 5 cost_giou * 2 # 用匈牙利算法找最优匹配 from scipy.optimize import linear_sum_assignment row_ind, col_ind linear_sum_assignment(C.detach().cpu().numpy())匹配完成后计算损失时同样要包含三部分分类的交叉熵、回归的 L1 和 GIoU 损失。我建议在项目初期把这三项的权重固定成官方默认值1、5、2不要一开始就去调权重比例因为 DETR 的调参空间主要在数据增强和学习率而不是损失权重。5.5 推理阶段如何绕过 NMS推理时DETR 直接取预测集合中“类别置信度大于等于阈值”的结果并按置信度排序输出。有同学问如果两个查询输出了相似的框要不要做 NMS从论文结论来看DETR 几乎不会产生重复框这是因为匈牙利匹配和查询的“分工机制”在训练中已经天然抑制了重复预测。我在多个数据集上验证过DETR 不加 NMS 的重复框比例远低于传统检测器偶尔出现轻微重叠也都能对应到不同类别的目标或多个实例。所以工程上完全可以把后处理简化到“一个阈值过滤”这比传统检测流程省掉了一个模块也让推理的 latency 更可控。6. DETR 之外的延伸Deformable DETR 与新的检测范式思考DETR 不是终点而是一个新范式的起点。它的后续发展非常快很多改进在工程上更有实用价值。这里我简单梳理几条主线如果你已经理解了原版 DETR接下来的提升方向会非常清晰。6.1 Deformable DETR收敛快、小目标好、显存友好Deformable DETR 的核心改进是把普通 Transformer 中的全局注意力替换为可变形注意力Deformable Attention每个 query 只采样少量关键点默认 4 个的位置并通过学习到的偏移量动态调整采样位置。这样做有三个直接好处计算量与内存大幅下降不需要对所有 token 计算 attention 权重只采样少量点显存占用和 Transformer 层数不再完全绑定。收敛速度提升明显由于采样位置有空间先验模型不需要从零学习“该去哪看”300 epoch 的训练周期可以被压缩到 50 epoch 左右这对实际项目意义重大。天然支持多尺度特征每个 query 可以同时在浅层高分辨率特征和深层语义特征上采样小目标检测效果比原版 DETR 好不少。6.2 DINO、DN-DETR 等方向再往深走DN-DETR 把“去噪训练”引入检测DINO 则在 DETR 家族里进一步刷新了 COCO 精度。它们从不同角度解决了集合预测的不稳定性问题本质上都是想“让匹配过程更加确定性”。如果你不是在做科研而是做工程我的建议是直接使用带有 Deformable Attention 的 DETR 变体做基线或者在 MMDetection 等框架里尝试DINO配置它往往能在一个相对标准的训练设置下得到很强的精度表现。6.3 对目标检测范式的一点思考从 Faster R-CNN 到 DETR目标检测经历了一次范式级别的切换从“手工先验 局部推理”走向“全局推理 集合预测”。Anchor 和 NMS 并不是永远必要的它们只是在一个特定范式下所采用的工具。DETR 用 Transformer 的注意力机制把“在哪里找目标”“用什么形状的框去匹配”这些原本由人工规则解决的问题变成了网络自动学习的隐式行为。对于从业者来说这种范式的意义不仅在于精度提升更重要的是简化了检测系统的组件复杂度不需要再做 Anchor 聚类、不需要调 NMS 阈值、不需要设计复杂的正负样本分配规则。你只需要准备好数据定好参数量模型自己就能学习匹配逻辑这对工业化落地是非常友好的。7. 想用 DETR 做项目这几条个人经验值得牢记最后聊点实战中真正能提升成功率的事情。以下几条经验每一条都来自我在不同项目里反复验证后的心得。7.1 数据集规模不够就别硬上原版 DETRDETR 对数据量的需求比传统检测器更高这一点很多人没注意到。如果你只有几千张图直接用原版 DETR 大概率会得到一个“loss 已经很低但验证集 mAP 很差”的过拟合模型。我踩过最大的坑就是在小数据集上盲目追求“端到端”。后来改用 Deformable DETR 或 DINO并且严格控制数据增强和正则化效果反而好得多。注意通常预训练权重迁移到小数据集时要保留大模型的基本分辨率习惯不能一上来就大幅提高输入尺寸否则前面的 CNN Backbone 会产生严重的分布漂移。7.2 对象查询数量并非越大越好有些同学会觉得“既然查询数量决定了能检测的最大目标数那我直接设 300、500 不就好了”实际效果并非如此。查询数量过大会让匹配空间变大训练更不稳定且大量查询最后可能收敛到重复预测或无效区域白白增加计算量。我的经验是统计训练集中单张图像最多目标数量再上浮 1.2~1.5 倍作为查询数。如果单张最多 28 个目标设 50 就足够了没必要设 100。7.3 评估时别只看 mAP要关注收敛曲线DETR 的训练曲线和传统检测器差异很大前期 mAP 上升非常缓慢给人“模型学不会”的感觉。如果你第 50 个 epoch 时看到 mAP 只有个位数不要急于放弃先看训练 loss 有没有持续下降、验证集上预测框和真实框的重合度是否在提升。我常用的方法是在训练中途定期保存预测结果的可视化图片直观对比第 10 个 epoch 和第 60 个 epoch 的预测质量。如果到后期预测框的位置稳定性明显变好说明模型正在收敛。这一点对 DETR 尤其重要因为它的“有效学习期”比传统检测器靠后。7.4 合理利用现成工具库现在很多框架已经内建了 DETR 及变体比如 MMDetection 的 DETR、Deformable DETR、DINO 等。如果只是做项目而不需要深入源码直接用这些库能省下大量时间。但如果你想真正理解 DETR 的机制建议至少把官方代码里的 HungarianMatcher、Transformer Encoder-Decoder 部分逐行走一遍重点看看_set_item和查询嵌入是怎么交互的。抄一遍永远比读一遍理解更深刻。8. 写在最后DETR 对我做检测项目方式的影响从第一次看到 DETR 的实验结果到现在我最大的感受是目标检测终于从一个“搭积木”的领域变得越来越像“调模型”的领域。不用再去抠 Anchor 的尺寸分布不用再为 NMS 阈值伤脑筋模型本身的结构决定了它能处理很大一部分底层适配问题。当然DETR 也不是没有代价训练成本高、小目标需要额外手段、Transformer 内部的调试比 CNN 更抽象。但它的思路——用统一的、端到端的集合预测取代人工规则堆叠——已经成为当前乃至未来检测器的重要方向。如果你正打算入坑目标检测我建议直接以 DETR 或 Deformable DETR 作为学习主线把 Anchor 和 NMS 当作“历史知识”来了解把更多精力放在理解注意力机制和集合预测的匹配逻辑上。在真实项目里我不建议为了追逐新模型而全盘更换技术栈但 A/B 测试 DETR 类模型与传统检测器对比精度与延迟是一项低成本、高收益的工作。哪怕最后没有上线你也能从它的失败、成功、边界情况中更深刻地理解检测任务本身。