
1. 从 DETR 到 Deformable DETR这个项目究竟在解决什么问题Deformable DETR 是我这两年做检测落地时回头率最高的一个结构。它属于 Transformers 在视觉检测方向的一条重要分支——把注意力机制从一视同仁地看全图改成每个查询只在少数关键位置上采样。这个改动听起来很小但它把 DETR 系列从论文里很美、工程上很难用拉到了真的能上线的水平。如果你在做目标检测尤其是小目标多、场景杂、标注格式不统一的业务工业质检、遥感、病理切片、密集货架Deformable DETR 值得你花两三天时间认真跑通一遍。它最大的吸引力在于端到端不需要锚框、不需要 NMS 后处理、不需要手工调一堆先验参数输入图像直接出框。而过去端到端方案迟迟难以落地核心卡点就是收敛太慢、小目标太差这两个问题恰好是 Deformable DETR 的主要贡献点。这篇文章我会按为什么这么设计—原理怎么算—环境怎么搭—训练怎么跑—坑怎么排的顺序完整走一遍。所有配置参数我会说明取值逻辑涉及计算的地方我把账算给你看涉及版本差异的地方我会标注清楚避免你照着老教程踩新版本的坑。1.1 DETR 的优雅之处与它的三个硬伤先回顾一下 DETR 的思路因为不理解它的问题就看不懂 Deformable DETR 的取舍。DETR 的做法是CNN 主干抽出一张特征图拉平后送入 Transformer 编码器做全局自注意力再准备一组数量固定的可学习查询向量让解码器把查询映射成检测结果最后用匈牙利算法做二分图匹配把预测框和真实框一一对应起来算 loss。这个设计最漂亮的地方是集合预测的思想。传统检测器要生成成千上万个候选框再用 NMS 去掉重复本质上是在做去重后处理。DETR 直接假设有 N 个查询、N 个结果用匹配算法强制每个真实目标对应唯一一个查询重复框天然不会被产生所以 NMS 这个环节被彻底拿掉了。这在工程上是巨大的简化——NMS 的阈值在不同数据集上几乎都要重新调而 NMS 本身在密集场景下还经常误杀。但硬伤也很明显而且都是工程上致命的第一是收敛慢。原版 DETR 在 COCO 上要训练 500 个 epoch 才能达到可用精度而当时的主流检测器 36 到 72 个 epoch 就够了。500 epoch 意味着单次实验要跑好几天调参迭代的成本高到无法接受。根因在于全局注意力对每个查询都要扫描全图所有位置初始化阶段注意力分布接近均匀模型要花很多轮才能学会该看哪里。第二是小目标差。DETR 只在主干网络最深的一层特征图通常是 stride 32 的 C5上做注意力那个分辨率下小目标可能只剩几个像素早就被下采样糊掉了。论文里 DETR 的 AP_S 只有 20 出头而同期的 Faster R-CNN 靠 FPN 能做到 27 以上。第三是显存和计算量。全局注意力的复杂度是 token 数量的平方。一张 800×1333 的图C5 特征约 25×42 个位置大概一千个 token注意力矩阵就是一百万级别如果想把高分辨率特征也塞进去改善小目标token 数翻四倍计算量直接翻十六倍显存撑不住。这三个问题不是调参能解决的是结构层面的限制所以才有了 Deformable DETR。1.2 可变形注意力把稠密全局换成稀疏采样Deformable DETR 的核心改动只有一句话每个查询不再和所有位置交互而是只在少数几个位置上采样。这些位置不是固定的网格而是根据查询内容动态预测出来的偏移点。这里的灵感来自可变形卷积Deformable Convolution。可变形卷积让卷积核的采样点位置可以偏移从而适应目标形变Deformable DETR 把这个思想搬到了注意力上——不再用固定的、稠密的注意力图而是用采样点位置采样点权重来近似注意力。为什么这样就能解决问题我打个比方。原版 DETR 的注意力像是一个人在图书馆里为了找一句话把每一本书都翻一遍效率极低。可变形注意力像是先根据手头的线索判断这句话大概在第三排书架第二层然后只去那个位置附近翻几页。判断可能不完全准但可以多找几个候选位置K 个采样点并且给它们不同的权重最终效果反而更好成本还低了一个数量级。这个设计同时解决了三个硬伤采样点在每个注意力头、每个特征层上独立预测天然支持多尺度可以同时在 C3/C4/C5/C6 上采样小目标在高分辨率层上有采样点因为稀疏高分辨率特征也能负担得起小目标精度直接受益因为查询从一开始就有明确的关注区域收敛速度大幅加快50 个 epoch 就能达到 DETR 500 epoch 的水平。1.3 和其他主流检测路线的横向对比我整理了一张对照表方便你判断自己的场景要不要选它。表中数值是论文报告值实际复现会因训练细节有波动仅作量级参考。方案是否需 NMS是否需锚框收敛速度epoch小目标表现部署友好度适合场景Faster R-CNN FPN需要需要36 左右好高生态成熟通用场景追求稳定YOLO 系列需要需要300 左右早期中等极高实时推理、边缘设备原版 DETR不需要不需要500差中等大目标为主、研究验证Deformable DETR不需要不需要50好中等小目标密集、标注规范DINO / DAB-DETR 等后续不需要不需要12 到 36很好中等追求最高精度的竞赛/研究选型上我的经验是如果你的场景里小目标占比超过三成且推理实时性要求不高比如离线批处理、服务端推理Deformable DETR 系列性价比很高。如果要求端侧实时那还是老老实实上 YOLO 系。如果精度优先且算力充足可以直接考虑 DINO 这类后续工作它把 Deformable DETR 的训练策略又往前推了一截。注意Deformable DETR 的端到端特性意味着推理后处理代码极简但代价是训练成本高、调参敏感度大。如果你的团队数据量少于两千张建议先做迁移学习而不是从头训。2. 核心机制拆解可变形注意力到底怎么算这一节我想把公式讲透因为很多同学跑通了代码却说不清里面的张量在干什么一旦需要改结构或者排查异常就无从下手。可变形注意力的计算流程其实只有四步理解之后你看源码会顺畅很多。2.1 采样点、偏移量与注意力权重先看公式这是整个模块的定义DeformAttn(z_q, p_q, x) Σ_m W_m [ Σ_k A_mqk · W_m · x(p_q Δp_mqk) ]逐个符号解释。z_q是查询特征p_q是查询对应的参考点坐标归一化到 [-1, 1]x是输入特征图。m遍历注意力头k遍历采样点。W_m是把特征投影到第 m 个头的线性层W_m是把各头结果融合回原维度的线性层。A_mqk是第 m 头第 k 个采样点的注意力权重Δp_mqk是相对于参考点的偏移量。关键在于Δp_mqk和A_mqk都不是固定值而是由查询特征z_q经过一层线性变换直接预测出来的。这就是可变形的来源——采样位置随内容动态变化。工程实现上有几个细节值得专门记一下偏移量的初始值应该接近零也就是训练刚开始时采样点基本落在参考点上随着训练逐步学会偏移。代码里偏移由nn.Linear输出后通常会经过一次归一化缩放MMDetection 的实现里用的是offset_normalizer取值是各层特征图的长宽减一目的是把预测的相对偏移映射到实际像素坐标。注意力权重的归一化维度容易搞错。它不是对每个特征层单独 softmax而是把所有特征层、所有采样点的权重放在一起做 softmax。也就是说M×K×L个权重M 为头数K 为每头采样点数L 为特征层数竞争归一模型会自动决定主要看哪一层的哪个点。这一点在改代码时务必注意我见过有人误改成了按层归一精度掉了两个点多。采样点是浮点坐标需要用双线性插值取值。这也是为什么必须写自定义 CUDA 算子反向传播时相邻采样点会共享同一个特征位置梯度需要累加用普通 PyTorch 操作做会有性能和精度问题。2.2 多尺度特征与多头采样的组合方式多尺度是这个模块的另一半。主干网络输出 C3、C4、C5 三层再加一层从 C5 卷出来的 C6stride 分别是 8、16、32、64。每层特征先过一层 1×1 卷积统一到 256 维再送上可变形注意力。以论文默认配置为例注意力头数 M8每头采样点数 K4特征层数 L4。那么每个查询在每个特征层上要采样 M×K32 个点四层加起来是 128 个位置。相比之下原版 DETR 在 C5 上做全局注意力一张常规输入图大概要覆盖上千个位置。128 对 1000这个差距就是收敛速度和显存占用的来源。编码器和解码器用的都是同一个可变形注意力模块但参考点的来源不同。解码器的参考点来自查询本身或者在迭代框回归模式下来自上一层预测出的框中心编码器的参考点则来自特征图上的网格位置并且会按有效区域比例缩放避免 padding 区域被采样到。另外编码器的自注意力输出会加上位置编码。Deformable DETR 用的是正弦位置编码并且在两阶段版本里引入了temperature参数默认 20来调节位置编码的尺度。这个小参数容易被忽略但它对两阶段变体的稳定性有实际影响调参时不要随手改。2.3 复杂度与显存的账算清楚再动手我习惯在动手前把复杂度算一遍这样对显存和训练时间心里有数。假设输入 800×1333四层特征的分辨率大约是 100×167、50×84、25×42、13×21总 token 数约 16700 4200 1050 273加起来两万出头。模块每个查询关联的位置数计算复杂度量级备注DETR 编码器全局自注意力约 1000仅 C5O((HW)²)加高分辨率层会平方级爆炸Deformable 注意力128M8×K4×L4O(HW·M·K·L)对 token 数近似线性解码器交叉注意力300 个查询 × 128与 token 数无关这就是能加多层的底气从表里能看出来Deformable 注意力的复杂度对特征图大小是近似线性的所以你可以放心把高分辨率层加进来。加一层 C3stride 8token 数增加约 1.4 万但计算量只线性增长而小目标精度会有肉眼可见的提升。我实测在工业质检的小缺陷数据集上加上 C3 后 AP_S 能涨三到五个点。显存方面训练时的瓶颈通常不在注意力本身而在主干网络的高分辨率激活值和 batch 大小。以 ResNet-50 为例单卡 24G 显存、batch size 为 2、输入短边 800基本是安全的如果想开 batch 4要么降分辨率要么开启梯度检查点代价是训练速度慢三成左右。提示不要一上来就盲目堆特征层。层数从 4 加到 5 时编码器自注意力的参考点计算和显存都会涨先确认你的收益来自小目标而不是噪声。3. 环境搭建与数据准备把训练真正跑起来原理讲完进入动手环节。我按依赖装对—数据摆对—配置看对三步来写每一步都会说明为什么这么做以及版本不对会出现什么症状。3.1 依赖版本与自定义 CUDA 算子编译Deformable DETR 最容易被卡住的地方就是自定义算子的编译。因为它包含前向和反向的 CUDA 核必须和你的 PyTorch、CUDA 版本严格对应否则会出现编译报错或者能编译但运行报 undefined symbol。版本选择的经验是PyTorch 用官方预编译版本对应 CUDA 版本比如 PyTorch 1.13 配 CUDA 11.7或者 PyTorch 2.x 配 CUDA 11.8/12.1。系统里的nvcc版本必须和 PyTorch 自带的 CUDA 版本一致这一点最容易被忽略——很多人 pip 装的 PyTorch 带的是 11.7但系统装的 CUDA toolkit 是 12.1编译时就会因为 ABI 不匹配出问题。编译命令如下# 进入自定义算子的目录以官方 pytorch 实现为例 cd ./models/ops # 指定目标架构可以显著缩短编译时间并避免兼容问题 # 比如 A100 是 8.03090 是 8.6V100 是 7.0具体查官方算力表 export TORCH_CUDA_ARCH_LIST8.0 export CUDA_HOME/usr/local/cuda-11.7 python setup.py build_ext --inplace # 官方自带正确性校验务必跑通再做后续操作 python test.pytest.py会对比自定义算子和 PyTorch 纯实现的前向、反向结果误差在容忍范围内才算通过。这一步省不掉我见过好几次编译成功但反向梯度算错的情况直接训下去 loss 就是不降非常折腾。如果你用 MMDetection 生态它会通过扩展的方式自动编译。用可编辑模式安装编译日志会打在终端里注意看有没有 warning 级别的兼容提示pip install -v -e .注意编译报错后不要直接重试先把build/目录和*.so清掉。残留的中间产物会导致改了配置但报错信息一模一样这种灵异现象。3.2 数据集组织与标注格式转换Deformable DETR 官方实现默认吃 COCO 格式。如果你手上是 VOC 的 XML、LabelImg 的 txt 或者自研平台导出的 json统一转成 COCO 是最省事的路径。COCO 的核心结构就三块images记录图片名、宽高、idannotations记录每个框的image_id、category_id、bboxcategories记录类别 id 和名称。这里有两个容易翻车的地方。第一bbox格式是[x, y, width, height]原点在左上角而不是[x1, y1, x2, y2]。我第一次转数据时搞混了训练出来的框全部偏移loss 看着在降但可视化合不上查了大半天。第二category_id必须从 1 开始连续编号不能从 0 开始。因为背景在匹配时会被特殊处理很多实现里 0 被隐式占用了。如果你的类别是 0 到 9转换时统一加 1。一个最小可用的转换脚本大概长这样import json import os from PIL import Image def voc_to_coco(anno_list, img_dir, class_names, out_path): coco { images: [], annotations: [], categories: [{id: i 1, name: n} for i, n in enumerate(class_names)] } ann_id 1 name2id {n: i 1 for i, n in enumerate(class_names)} for img_id, (img_name, boxes) in enumerate(anno_list, start1): w, h Image.open(os.path.join(img_dir, img_name)).size coco[images].append( {id: img_id, file_name: img_name, width: w, height: h} ) for x1, y1, x2, y2, cls in boxes: # 关键COCO 用 xywh且需要裁剪到图像范围内 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 - x1 1 or y2 - y1 1: continue # 过滤掉退化的框 coco[annotations].append({ id: ann_id, image_id: img_id, category_id: name2id[cls], bbox: [x1, y1, x2 - x1, y2 - y1], area: (x2 - x1) * (y2 - y1), iscrowd: 0 }) ann_id 1 with open(out_path, w) as f: json.dump(coco, f)写完记得做一次自检随机抽十张图按标注画框存图看一眼。这一步花五分钟能省掉后面几小时的困惑。另外统计一下每类的框数量如果某类少于 50 个训练时基本学不出来考虑合并类别或者补充数据。3.3 配置文件里那些真正重要的参数配置文件看起来参数很多但真正影响结果的就那么十来个。我把它们列出来并说明取值逻辑。# 取自官方配置的关键片段数值为论文默认设置 num_queries 300 # 查询数量 num_feature_levels 4 # 使用 C3/C4/C5/C6 四层 enc_layers 6 # 编码器层数 dec_layers 6 # 解码器层数 hidden_dim 256 # 特征维度 nheads 8 # 注意力头数 enc_n_points 4 # 编码器每头采样点数 dec_n_points 4 # 解码器每头采样点数 dim_feedforward 1024 # 前馈层维度 dropout 0.1 # 损失权重 set_cost_class 2 # 匹配时分类代价 set_cost_bbox 5 # 匹配时 L1 代价 set_cost_giou 2 # 匹配时 GIoU 代价 cls_loss_coef 2 bbox_loss_coef 5 giou_loss_coef 2 focal_alpha 0.25 # 两阶段版本里 focal loss 的 alpha # 优化器 lr_backbone 1e-5 # 主干学习率必须比主体小一个量级 lr 1e-4 # Transformer 部分学习率 weight_decay 1e-4 clip_max_norm 0.1 # 梯度裁剪 lr_drop 40 # 第 40 epoch 衰减 epochs 50num_queries从 DETR 的 100 提到 300 是有原因的。查询数必须大于单图最大目标数否则会出现目标数超过查询数导致漏检。但也不是越大越好查询数翻倍会让解码器计算量和显存同步上升而且多余查询需要靠 loss 压制成本最低的匹配是匹配到背景会轻微拖慢收敛。400 到 500 是个常见上限除非你的场景单图目标数确实很多。lr_backbone比lr小十倍是因为主干是预训练权重学习率太大会把预训练特征破坏掉也就是常说的灾难性遗忘。我见过有人为了加速收敛把主干学习率也设成 1e-4结果前几个 epoch 精度直接崩掉还以为是数据问题。clip_max_norm 0.1这个值偏小是针对注意力机制梯度波动大的特点。如果你的 loss 曲线偶尔有尖刺不要急着改学习率先确认裁剪是否生效。4. 训练、评估与推理的完整实操流程环境、数据、配置都准备好之后进入真正的训练环节。这部分我按时间顺序写包含耗时估算和中间观察点。4.1 启动训练与资源估算单机八卡的启动命令python -m torch.distributed.launch --nproc_per_node8 --use_env main.py \ --config_file configs/r50_deformable_detr.sh \ --output_dir ./exps/r50_deformable_detr如果你只有单卡把nproc_per_node改成 1同时把 batch size 从 2 降到 1 或者用梯度累积凑等效批量。这里要说明官方配置是每卡 batch size 为 2、八卡合计 16。批量太小会让匹配算法匈牙利匹配的结果不稳定因为每张图单独匹配批量只影响梯度平滑度。所以单卡训练时建议用梯度累积累积步数设成 8等效批量 16这时学习率可以保持不变。耗时上ResNet-50 版本、50 epoch、COCO 全集、八卡 V100大概一到一个半天。换成 A100 能压到八小时左右。如果你只想验证流程是否跑通强烈建议先用 COCO 的一个子集比如 5000 张图跑 5 个 epoch看 loss 是否正常下降、评估脚本是否能跑通再上全量。评估命令python -m torch.distributed.launch --nproc_per_node8 --use_env main.py \ --config_file configs/r50_deformable_detr.sh \ --eval --resume ./exps/r50_deformable_detr/checkpoint.pth4.2 训练过程怎么观察loss 曲线怎么看跑起来之后不要就等着盯三个东西总 loss 的趋势、分类 loss 和回归 loss 的相对关系、以及学习率衰减前后的变化。正常的曲线长这样前 3 到 5 个 epoch loss 下降很快从十几降到个位数5 到 30 epoch 缓慢下降中间会有平台期第 40 epoch 学习率衰减一次loss 会有一个台阶式下探最后 10 个 epoch 基本平稳。如果在第 10 个 epoch 之后 loss 就纹丝不动大概率是学习率太小或者匹配出问题了。分类 loss 和回归 loss 的比例也要看。如果分类 loss 一直是回归 loss 的十倍以上说明模型在纠结这是什么而不是在哪里可能是类别不平衡严重可以考虑调整 focal loss 的 alpha 或者损失权重。反之如果回归 loss 占主导通常是标注框噪声大。一个实用的判断技巧把num_queries匹配到背景的比例打印出来。如果超过九成查询都匹配到背景说明查询数严重过剩可以适当减少。这个比例在训练稳定后通常落在七到九成之间比较正常。提示Deformable DETR 的收敛速度和数据量强相关。数据量小于三千张时建议把 epoch 降到 30 并调小学习率否则容易过拟合典型表现是训练 loss 还在降但验证 AP 已经开始掉了。4.3 推理与可视化验证推理代码很简洁因为是端到端不需要 NMS。核心流程是预处理图像、前向、取分类分数超过阈值的预测、把归一化坐标还原到原图尺寸。import torch from PIL import Image import torchvision.transforms as T # 预处理短边缩放到 800长边不超过 1333与训练保持一致 transform T.Compose([ T.Resize(800), T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) model.eval() img Image.open(test.jpg).convert(RGB) w, h img.size x transform(img).unsqueeze(0) with torch.no_grad(): out model(x) # logits: [1, num_queries, num_classes 1]最后一维是背景 probs out[pred_logits].softmax(-1)[0, :, :-1] keep probs.max(-1).values 0.5 boxes out[pred_boxes][0, keep] # 归一化的 cxcywh 还原到原图坐标 scale torch.tensor([w, h, w, h]) boxes boxes * scale cx, cy, bw, bh boxes.unbind(-1) xyxy torch.stack([cx - bw / 2, cy - bh / 2, cx bw / 2, cy bh / 2], dim-1)可视化时我建议做两件事。一是把阈值从 0.5 降到 0.1 再看一遍观察有没有大量低分框堆积在同一位置——如果有通常是训练不充分或者查询数过多二是把预测框和真值框画在同一张图上对比重点看小目标的偏移方向偏右上或偏左下往往提示坐标格式转换时有半个像素的系统偏差。4.4 迭代框回归与两阶段变体该怎么选官方给了三个递进的变体很多人不知道该用哪个。我的建议是看你的需求基础版多尺度可变形注意力50 epoch配置最简单复现风险最低。如果你第一次上手从这里开始。加迭代框回归版解码器每一层都基于上一层预测的框中心重新采样逐层细化。这个改动能涨一到两个点实现上要注意一点参考点在层间传递时梯度需要 detach否则反向传播会跨层回传训练会不稳定甚至发散。这个细节在论文里提了一句但代码里非常关键。两阶段版先用编码器输出加一个小检测头生成候选区域用 focal loss 监督再把候选框的中心作为解码器的初始参考点。相当于给解码器一个高质量的起点精度再涨零点几个点代价是训练多一点点开销推理也几乎不变。如果你的场景目标比较密集两阶段版对小目标和遮挡目标的改善更明显。我实际项目的选择顺序是先跑基础版建立基线再加迭代框回归看提升幅度最后试两阶段。不要一上来就上两阶段因为一旦出问题排查路径会长很多。5. 常见问题排查与调优经验这部分是我踩坑最集中的地方也是我认为最有价值的部分。官方文档不会告诉你这些因为它们只在特定环境组合下才会出现。5.1 问题速查表现象可能原因处理方式自定义算子编译失败nvcc 版本与 PyTorch CUDA 版本不一致统一两者版本清 build 目录重编编译通过但运行报 undefined symbol编译时的 PyTorch ABI 与运行环境不一致在同一虚拟环境内完成编译和运行loss 从头到尾是 NaN学习率过大或梯度爆炸确认梯度裁剪生效主干学习率保持 1e-5loss 下降但 AP 极低坐标格式错误或类别 id 从 0 开始检查 COCO 的 xywh 与 category_id 起始值AP 比论文低很多数据增强、输入尺度、BN 设置不一致逐项对齐官方配置冻结主干 BN小目标 AP_S 明显偏低未使用高分辨率特征层加入 C3 层并适当提高输入分辨率显存溢出分辨率过大、查询数过多、层数过多降 batch启用梯度检查点减少特征层推理结果出现重复框误加了 NMS 或阈值过低端到端模型无需 NMS提高置信度阈值训练后期精度回退过拟合加数据、增强、降低 epoch 或加 dropout多卡训练速度没有提升数据加载成为瓶颈增加 dataloader workers使用预读取5.2 小目标与密集场景的针对性调优小目标差是很多人换到 Deformable DETR 的主要动机但如果只是默认配置跑一遍提升可能没有预期大。我总结了几条实际有效的做法。第一确认 C3 层真的被用上了。有些配置里num_feature_levels设成 3用的是 C4/C5/C6那就丢掉了高分辨率信息。小目标场景一定要用 4 层并且检查主干输出的 stride 是否真的是 8。第二输入分辨率不要压得太狠。如果显存允许把短边从 800 提到 1000 甚至 1200小目标提升立竿见影。代价是显存和耗时线性上升需要权衡。第三数据增强里慎用大幅随机缩放和裁剪。小目标本来就小再缩一次就没了。我一般把随机缩放的幅度控制在 0.8 到 1.25 之间不用 0.5 到 2.0 那种大范围。第四也是容易被忽略的一点采样点数可以适当增加。从 4 提到 8每个查询能覆盖更多位置对小目标有帮助。但这会让计算量翻倍属于有预算就上的选项。密集场景下的另一个技巧是调整匹配代价里的分类权重。当目标密集时相邻目标容易在匹配阶段混淆适当提高 GIoU 代价的权重比如从 2 提到 3能让匹配更倾向位置准确的一对一关系减少漏检。5.3 迁移到自定义数据集的真实经验从 COCO 迁到自己的数据有几件事我每次都做效果很实在。第一件事是先冻结主干训练几个 epoch再解冻微调。虽然官方配置里主干学习率已经很小但对于和自然图像差异大的数据比如 X 光、显微镜图像一开始就更新主干仍然会破坏特征。我会先把主干的学习率设为 0 跑 3 个 epoch让检测头先拟合再恢复到 1e-5。第二件事是检查类别 id 的连续性。前面提过但值得再强调一次。自定义数据集里如果类别 id 有跳号某些实现会直接报错某些则会静默地多出一堆空类别表现为某些类永远检不出来。第三件事是关于评估指标的诚实性。COCO 的 mAP 在类别少、样本不均衡的场景下会失真。比如你只有两类其中一类占了九成样本那么 mAP 会被大类主导。我习惯额外算一遍每类的 AP 和混淆矩阵这两个指标对业务方更有说服力。第四件事是混合训练。如果自己的数据只有几千张把 COCO 按一定比例混进来一起训精度通常比纯自训高而且收敛更稳。混合比例我一般用自研数据为主、COCO 子集占三分之一左右采样时按类别做均衡。6. 一些后续可扩展的方向跑通之后如果还想继续往下走有几个方向是自然延伸的。一个是把可变形注意力的采样点可视化出来看每个查询到底关注了图像的哪些区域这对排查为什么这个目标漏检非常直观也能发现标注错误。做法是把sampling_offsets和参考点相加还原到原图坐标画点我每次调优都会做这一步。另一个方向是蒸馏和剪枝。Deformable DETR 的编码器六层、解码器六层推理速度在服务端够用但不算快。把编码器层数降到三层、查询数降到 200精度掉一个点左右推理速度能快四成很多线上场景这个交换是划算的。我做过一次在保证业务指标不降的前提下把单帧耗时压到了原来的一半多靠的就是层数和查询数的联合裁剪再加上把输入短边从 1000 降到 850。还有一条路是换更强的查询设计比如后续工作里引入的去噪训练和更好的参考点初始化策略。这部分在 Deformable DETR 的基础上改动不算大收益却很明显值得花时间读一读再动手改。我个人在实际操作中的体会是检测模型的精度提升往往不来自某个花哨的模块而来自训练策略的细节打磨——匹配代价怎么设、参考点怎么初始化、标签分配怎么稳定这些看起来不起眼的地方累积起来常常比换主干网络更管用。