ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DETR端到端目标检测:从Transformer原理到实战踩坑全解析

DETR端到端目标检测:从Transformer原理到实战踩坑全解析 先聊个实在的做目标检测的这些年从Faster R-CNN到YOLO系列大家早就习惯了一件事——想拿到一个像样的检测结果先得准备好一大堆后处理零件锚框设定、NMS阈值调参、正负样本匹配策略。每个环节单独看不复杂但串在一起整个pipeline就变得很脆。而DETRDEtection TRansformer这个工作算是我见过最干脆的一次“掀桌子”它把目标检测直接定义成一个集合预测问题用Transformer架构端到端出结果把锚框、NMS这些东西全删了。这篇文章我会结合我自己训练DETR、调模型、推数据集的经历把它的核心原理、完整实操链路和几个容易踩的坑一次说清楚。不管你是刚接触Transformer目标检测的新手还是想把手头数据迁到DETR上的老兵这篇都值得花十分钟看完。1. DETR提出的背景它到底解决了我做目标检测时的什么痛处1.1 NMS和后处理那段让人头疼的日子先回忆一下传统检测器的标准流程。以Faster R-CNN为例训练时要做锚框分配、RPN提案、ROI Pooling推理完还得做NMS非极大值抑制。NMS这个操作看似简单实际很“玄学”阈值调高了重叠目标的框会被误删调低了同一目标出好几个框。再加上锚框的超参数大小、宽高比直接继承自数据集经验换一个应用场景往往要重新调一轮。我当时做一个工业场景的零件检测项目目标是小尺寸、高密度的螺丝和垫片。用Faster R-CNN那套光是调试锚框尺寸就花了一周。后来换用YOLOv5虽然省心了点但针对小目标的NMS处理仍然不省心。我总在想能不能有一种模型直接让网络自己去理解“这张图里有哪些目标、各自在什么位置”不用我手动设计那么多中间规则1.2 把检测当作直接集合预测的关键思路DETR给出的答案很直接把检测当作集合预测来做。所谓集合预测就是模型输出一个固定大小的预测集合比如100个预测框然后和图像中真实的若干个目标框做匹配。匹配上了就算检出没匹配上的就是背景。整个过程没有锚框没有NMS也不需要任何手工设计的后处理。这个思路之所以在当时让人觉得眼前一亮在于它把检测的pipeline真正变成了“端到端”。你想一下端到端意味着什么意味着从图像像素到最终预测结果之间所有步骤都是可微的误差可以直接从损失函数反向传播到模型参数。而传统检测器中的NMS是不可微的它只存在于推理阶段训练时还要模拟它的行为去设计损失。DETR的另一个关键点是它把目标检测看成了一个“序列生成”问题。图像经过CNN骨干网络提取特征展平成一串特征序列然后交给Transformer的编码器-解码器结构去处理。这跟机器翻译里把一个句子翻译成另一个句子的逻辑很像。解码器一侧输入的不是图像特征而是一组可学习的“目标查询”object queries它们像“空槽位”一样在解码过程中逐渐定位到具体目标上。你可能要问了为什么Transformer在这件事上能行因为Transformer的核心机制是自注意力self-attention它能让每个位置的特征都和其他所有位置的特征交互天然具备全局建模能力。传统卷积要扩大感受野得堆层数、加深网络Transformer一上来就把整个图像的特征都放在一个上下文里处理目标之间的全局关系、目标与背景的关系都能直接被建模到。2. 核心架构逐层拆解从图像输入到目标查询输出2.1 CNN骨干网络与位置编码的配合DETR的输入处理分两步。第一步图像先喂给一个CNN骨干网络论文里用的是ResNet-50或ResNet-101也可换成ResNet-50的变体得到下采样32倍的特征图。假设输入是800x800输出的特征图就是25x25通道数2048。这个特征图其实已经浓缩了图像的语义信息。第二步用1x1卷积把通道数压缩到256左右得到一个形状为25x25x256的紧凑特征图。但这里有个问题Transformer本身是序列模型它不知道特征图里每个位置对应图像上的哪个空间位置。所以需要给每个特征位置加上位置编码positional encoding。DETR用的位置编码是正弦余弦函数生成的绝对位置编码二维空间上分别在x和y方向计算。你可以把它理解成给每个特征网格发了一张“身份证”让Transformer在计算注意力时知道哪个特征来自图的左上方、哪个来自右下方。这一步细节容易被忽略但特别重要。如果没有位置编码Transformer做自注意力时所有位置的特征初始状态没有空间区分度模型很难学会“相邻区域倾向于属于同一个目标”这种空间先验。实测把位置编码去掉训练损失下降明显变慢。2.2 Transformer编码器全局上下文建模的优势编码器部分基本就是标准的Transformer Encoder Block论文默认6层。每一层包含一个多头自注意力子层和一个前馈网络子层层归一化采用Post-LN先加残差再归一化的方式。输入的序列长度是HxW对25x25的特征图来说就是625个token。自注意力在这里的作用是让每个位置的特征感知到整张图的上下文。举个例子一张图上有一辆车和一个行人传统卷积要经过多层堆叠才能让车的位置特征“看到”行人的位置特征而自注意力一步到位任意两个位置之间都建立连接。这种全局交互对消除重复检测非常有帮助因为模型能学到“左上角已经检测到一辆车了那右下角跟它长得很像的区域很可能不是另一辆车”。不过全局注意力也带来一个现实问题计算复杂度是序列长度的平方。如果输入分辨率太大编码器的显存开销会非常可观。这一点我在后面讲训练实操时会展开。2.3 解码器与object queries到底是怎么发挥作用的解码器也默认6层和编码器不同的是每层解码器内部有三个子层自注意力、交叉注意力、前馈网络。交叉注意力负责在解码器输出和编码器输出的图像特征之间建立联系。核心的机制在于object queries。这是一个形状为(100, 256)的可学习参数矩阵相当于100个“目标查询向量”。它们在初始状态下没有任何语义但随着训练进行每个query会逐渐“锁定”到图像中特定位置、特定尺寸、特定类别的目标上。你可以把这些queries理解为100个“小侦探”每个侦探负责去图像里找一类目标有的专找小目标有的专找大目标有的偏向图像中心区域有的偏向边缘。这100个queries经过解码器的交叉注意力机制不断“查询”图像特征最终在最后一层解码器输出后接一个FFN前馈网络预测每个query对应的目标类别和边界框位置。类别预测用一个线性层加softmax得到100个概率分布边界框预测用另一个线性层输出4个数值中心点x、中心点y、宽w、高h并且用sigmoid函数归一化到0到1之间。有一点值得注意DETR的解码器在每一层都引入了辅助损失。也就是说中间层解码器的输出也会被拿来计算损失然后和最终层的损失加在一起。这个设计是为了缓解Transformer训练不稳定的问题让梯度能更直接地传到前面的层。2.4 预测头与二分图匹配的损失设计如果说上面的结构还算常规那DETR的损失设计就属于“点睛之笔”。因为模型输出100个预测框而图像里可能只有几个真实目标框那么怎么计算损失DETR用的是匈牙利匹配算法Hungarian Algorithm在100个预测框和真实目标框之间找到一种最优一对一匹配关系使得总匹配代价最小。匹配代价由三部分组成类别预测的负对数概率、边界框的L1距离、以及GIoU损失。简单来说匈牙利匹配会这样分配每个真实目标框找到与自己最匹配且类别预测正确的预测框剩下的预测框视为背景。匹配完成后损失函数只针对匹配上的预测框计算没匹配上的预测框用“无对象”no object这个类别来监督。这套设计的巧妙之处在于它用一次匹配同时解决了目标分配和去重两个问题。一对一的强约束意味着一个真实目标最多只能匹配一个预测框这天然抑制了重复检测。也正因如此推理阶段完全不需要NMS。我第一次跑通DETR看到最终输出直接就是干净利落的检测框确实有种“终于摆脱麻烦后处理”的轻松感。3. 让DETR跑起来一次训练和推理的完整实操记录3.1 环境准备与依赖版本选择DETR的官方实现托管在GitHubfacebookresearch/detr上基于PyTorch。直接克隆仓库装好torch、torchvision和pycocotools就能跑。我的建议是PyTorch版本用1.7以上太低的版本在autograd机制上对Transformer的支持没那么好。CUDA最好是11.x训练DETR时显存占用不低别省这点。一个很多人会忽略的点DETR的repo里指定了一个submodule需要拉取cocoapi的依赖。如果你直接git clone而不加--recurse-submodules后面跑评估时会遇到pycocotools导入失败的问题。我自己踩过这个坑最后的解决办法是手动把cocoapi/PythonAPI下面的pycocotools目录复制到环境site-packages里或者用pip安装pycocotools。3.2 数据格式与加载用COCO格式训练自己的数据DETR默认使用COCO数据集格式也就是一张图像对应一个JSON标注文件。标注格式长这样{ images: [ {id: 1, file_name: 000001.jpg, width: 640, height: 480} ], annotations: [ {id: 1, image_id: 1, category_id: 1, bbox: [100, 150, 200, 300], area: 60000, iscrowd: 0} ], categories: [ {id: 1, name: screw} ] }这里的bbox格式是[x, y, width, height]注意不是YOLO的归一化中心点格式也不是VOC的[x_min, y_min, x_max, y_max]格式。我在第一次做数据转换时就因为在bbox的格式上搞混导致训练出的模型检测框全部偏移后面排查了很久才意识到是标注格式理解有误。如果要用自己的数据强烈建议先写一个检查脚本随机抽几张图把标注框画在原图上并保存出来确认标注框位置正确后再开始训练。这一步虽然笨但能省掉你后续调试模型时的大量脑细胞。3.3 训练参数、显存占用和收敛观察官方默认的DETR训练配置是batch size 2epochs 300初始学习率1e-4在第200个epoch时降到1e-5。这么长的训练周期肯定劝退不少人。但实际上如果你的数据集不复杂类别不多完全不需要跑到300个epoch。我用一个只有5个类别的工业零件数据集200个epoch左右就已经能看到收敛趋势了。显存占用方面输入尺寸默认是800左右batch size为2时一张RTX 309024GB显存能跑但比较吃紧。如果你只有12GB显存建议把batch size降为1同时把输入图片最长边缩放到600。Transformer的注意力复杂度是平方级别的降低分辨率是控制显存最有效的手段。训练时的损失曲线建议同时关注三个指标总损失、分类损失和边界框损失。DETR收敛有个特点前期分类损失下降很慢看起来像没在学实际上边界框损失已经在悄悄下降了。这是正常现象因为匈牙利匹配算法前期还在做“试配”匹配关系稳定后损失下降才会明显加速。我自己的经验是如果看到分类损失一直卡在某个高位不动优先检查是不是类别数量设置错了比如背景类算进去了没、或者目标数量太少导致大量query被分配到“无对象”类别。理论上一张图里只有1个目标那100个query里头99个都该学成背景如果模型连这个都没学会多半是数据或匹配逻辑有问题。3.4 推理可视化与结果解读官方推理脚本支持两种模式图片文件夹推理和单图可视化。我自己习惯写一个更简单的推理脚本只提取模型输出再手动画框。下面这段代码是核心部分import torch import torchvision.transforms as T from PIL import Image from models import build_model # 加载模型 model, criterion, postprocessors build_model(args) checkpoint torch.load(checkpoint.pth, map_locationcpu) model.load_state_dict(checkpoint[model]) model.eval() # 图像预处理 transform T.Compose([ T.Resize(800), T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) img Image.open(test.jpg).convert(RGB) img_tensor transform(img).unsqueeze(0) with torch.no_grad(): outputs model(img_tensor) probas outputs[pred_logits].softmax(-1)[0, :, :-1] bboxes outputs[pred_boxes][0] # 过滤低置信度预测 keep probas.max(-1).values 0.7 boxes bboxes[keep] labels probas.argmax(-1)[keep] scores probas.max(-1).values[keep]这里有个细节outputs[pred_logits]的形状是(batch, num_queries, num_classes1)因为有一个额外的“无对象”类别所以在做softmax之后我用[:, :, :-1]把最后一类去掉。然后通过置信度阈值过滤掉背景预测剩下的就是最终检测结果。推理阶段比较快一张800x800图片在我的3090上约20毫秒。注意推理时不需要任何NMS步骤原封不动地取模型输出就行。4. 我用DETR时踩过的坑收敛慢、小目标漏检和其他边界问题4.1 DETR为什么收敛慢以及我用的缓解手段DETR被诟病最多的就是收敛速度慢。原论文在COCO上需要500个epoch才能达到较好的性能这对实际项目来说确实是个负担。根本原因在于匈牙利匹配是一种“软”匹配训练早期的匹配结果不稳定同一个query可能在一次迭代中被分配到目标A下一次又被分配到目标B导致梯度方向不一致训练效率自然低下。我实际使用的几个缓解手段按效果排序如下使用Deformable DETR的变体后面会讲到或者用DETR的改进版DINO收敛速度和最终精度都明显优于原版。引入更强的特征提取骨干比如把ResNet-50换成ResNet-101或Swin Transformer的骨干可以在同样epoch数下获得更好的特征表示。适当降低batch size并提高训练轮数。虽然batch size 2会让梯度噪声更大但配合lr scheduler的warmup阶段有时反而能逃离初始的不良匹配局域。4.2 小目标检测偏弱的根因和可选方案小目标检测是DETR的一个明显短板。原因也很直观小目标在特征图上仅占很少的像素点经过下采样32倍之后可能只对应到1到2个特征格点在全局自注意力中容易被其他大目标的特征淹没。此外小目标的边界框L1损失在总损失中占比很小匈牙利匹配时不容易被优先匹配到。这个问题我试过几种解法多尺度特征融合把骨干网络不同stage的特征都取出来拼接后送入编码器。Deformable DETR就是这么干的效果立竿见影小目标的召回率提升明显。使用更高分辨率输入好处直接坏处是显存翻倍。调整损失权重把边界框损失中GIoU损失的权重加大让匹配时更青睐空间位置准确的候选框。4.3 训练不稳定与随机种子、辅助损失的关系DETR对随机种子比较敏感这也是Transformer类模型的通病。同样的数据、同样的超参数用不同种子跑两次最终AP可能差1到2个点。我在论文复现时也发现了这一点。建议训练前固定好所有随机种子包括Python的random、NumPy的np.random和PyTorch的torch.manual_seed并在代码开头设置torch.backends.cudnn.deterministic True和torch.backends.cudnn.benchmark False保证可复现性。辅助损失auxiliary loss是另一个稳定训练的关键。打开官方配置中的aux_lossTrue后每一层解码器都会产生预测并参与损失计算。我发现关闭辅助损失时模型在训练前期的分类分支非常容易陷入局部最小表现为预测全部偏向“背景”类。开启辅助损失后这个问题得到显著改善建议默认保持开启。5. DETR之后从原版到Deformable DETR和DINO的进化5.1 Deformable DETR如何加速收敛并改善小目标Deformable DETR的核心改动是在交叉注意力模块中引入可变形注意力机制。通俗讲每个query不再需要对整张特征图计算注意力权重而是通过学习一组稀疏的采样点默认每个query采样4个点只在这些位置采集特征。这样既降低了计算复杂度也让模型聚焦在少数关键位置。更实际的好处是Deformable DETR将收敛epoch数从500降到了50左右几乎一个数量级的提升。它同时引入了多尺度特征输入把小目标漏检问题一并改善。如果是新项目我基本不建议直接用原版DETRDeformable DETR是更稳妥的起点。5.2 DINO等进阶变体对我们有什么启发DINODETR with Improved deNoising anchOr boxes是DETR系更后期的一个代表思路更复杂一些引入了对比去噪训练、锚框初始化等机制。简单说它在训练时人为给真实目标框添加一些噪声让模型学会“去掉噪声、还原目标”这种去噪预训练任务能极大加速收敛。从实际应用角度如果你要在自己的数据集上很快见效直接找DINO的预训练权重做迁移学习通常比我前面说的从零训练原版DETR效果更好。我在一个自采集的安防数据集上做过对比用原版DETR从零训150个epoch的AP约38用DINO加载COCO预训练权重再微调30个epoch就达到了46差距非常明显。不过DINO的训练配置和代码复杂度比原版DETR高不少调试成本也上去了。对入门学习来说原版DETR仍然是最值得先吃透的起点毕竟理解了基础结构后面的变体不过是“在哪些环节做了哪些改进”的问题。最后分享一个按我个人经验可能对你有用的细节训练DETR系列模型千万不要上来就放弃默认配置。官方repo的800x800输入、batch size 2、300epoch这些数字都是针对COCO这种大而复杂的数据集调出来的对你自己的小数据集未必合适。我的建议是第一周先用小分辨率、小epoch比如输入600、训练50个epoch跑通整体流程确认数据格式和损失曲线正常再逐步放大输入分辨率和训练轮数。如果一上来就追求完整配置很容易在长时间训练后发现某个基础环节出了错白白浪费大量算力。还有一个实用技巧在训练过程中定期保存checkpoint时不要把整个模型状态存下来而是只存model.state_dict()和optimizer.state_dict()。DETR模型本身不算特别大但优化器状态往往占更多空间长期训练下来能省不少磁盘。而且推理时只需要model.state_dict()加载更轻量。DETR的设计哲学我打心底里喜欢它让我看到了目标检测从“手工规则”走向“数据驱动端到端”的清晰方向。虽然它直接落地到复杂业务场景时效率和精度还需要依赖各种改进版本来支撑但它的思想价值远大于那几张可以被新模型刷新的指标表。如果你正准备入门Transformer检测模型从DETR开始把每一层结构、每一个损失函数吃透再去看Deformable DETR、DINO、DN-DETR这些后继者会顺畅得多。
返回列表