ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DETR详解:Transformer如何颠覆目标检测,实现端到端集合预测

DETR详解:Transformer如何颠覆目标检测,实现端到端集合预测 2. 从“DETR”读懂Transformer怎样杀进目标检测如果你平时关注目标检测这块的技术演进那你大概率已经注意到YOLO系列、Faster R-CNN这些老牌选手统治了多年之后2020年突然冒出来一个叫DETR的家伙全称是Detection TransformerFacebook AI团队出品。当时我第一眼看到这个论文标题“End-to-End Object Detection with Transformers”说实话心里是半信半疑的——Transformer做机器翻译、做文本分类我是信的但拿来做目标检测还真敢想。但后来我花了一整个周末把这篇论文啃下来又动手跑了几轮实验不得不承认一个事实DETR的思路确实够颠覆它把目标检测从“一堆手工设计的锚框、NMS后处理”里彻底解放出来第一次让检测任务变成了一个“直接从序列到序列”的问题。这篇文章就围绕DETR把我理解的原理、实现细节、训练经验、踩过的坑一次性讲清楚。这篇内容适合谁看如果你已经跑过几天YOLO或者Faster R-CNN想知道Transformer在视觉任务里怎么玩或者你在准备面试、做毕业设计需要深入理解DETR的来龙去脉——那这篇文章应该能帮你节省不少时间。我会尽量用大白话讲原理但也保留了公式和代码级的细节不同基础的读者可以挑着看。3. 整体设计思路目标检测怎么被改造成一个“集合预测”问题3.1 传统目标检测到底哪里让人觉得“绕”在聊DETR之前最好先达成一个共识传统检测器最大的痛点是什么。以Faster R-CNN为例整个过程大致是先通过骨干网络提特征再用RPN生成一堆候选框接着做RoI Pooling把不同尺寸的区域统一起来最后再接分类头和回归头。YOLO系列虽然省掉了候选框这一步但它基于网格加锚框的设计本质上也还是在“预设一大堆框然后判断每个框里有没有目标”。这套流程的问题很明显锚框尺寸、长宽比、正负样本阈值这些超参数都需要人工调而且训练完之后模型在遇到极端长宽比的物体、或者小目标扎堆的场景时预设锚框往往不够用。NMS去重更是典型的“后处理补丁”因为网络本身没法决定“一个物体到底该由哪个框负责”。我们不妨打个比方传统检测像是一个只能靠“先撒网再收网”的渔夫网眼大小、网的形状都得提前定好DETR的思路则是不撒网了直接派几个“专员”去盯着画面每个人报告“我这边发现了什么目标、在什么位置”。是不是听着简单实际实现起来关键就在怎么让这些“专员”学会分工协作这就轮到Transformer上场了。3.2 DETR的核心思想把检测当作一个集合预测问题DETR把目标检测重新定义成一个“集合预测”问题给定一张图片网络直接输出一个固定大小比如100个的预测集合每个元素包含目标的类别和归一化后的边界框坐标。这100个预测对应到数学上就是100个query查询向量。Transformer解码器拿着这些query去编码器输出的图像特征里“查询”有没有目标、目标在哪里。这听起来跟传统方法的区别只是“少了一些手工组件”但真正的创新在于DETR使用匈牙利匹配算法在训练时让100个预测和图中的真实目标做一对一的二分匹配然后基于匹配结果计算损失。换句话说网络在训练过程中自动学习每个query应该负责哪个目标不需要预先设置锚框的去重和分配逻辑。这样设计的好处是显而易见的推理阶段完全不需要NMS因为每个目标最多只被一个query命中理想情况下而且模型直接输出的是集合天然满足“一个目标一个框”的需求。更重要的是把检测器的结构简化到了极致——输入图像输出集合中间就是个标准的Transformer编码器-解码器。3.3 为什么选Transformer而不是继续堆CNN也许你会问既然是把特征图映射成100个预测那用全连接层直接输出不就行了实际上目标数量是动态的而且目标之间是有空间关系、有语义关联的全连接层学不到这种全局关系中“谁该负责谁”的分配逻辑。Transformer里的自注意力机制恰恰擅长建模元素之间的长程依赖和相互关系。图像特征在送入Transformer之前会被展平成一个序列每个位置就是一个token自注意力让每个token都能看到全局信息。这对目标检测来说是极大的优势因为以前的卷积神经网络受限于感受野要看全局得靠堆叠很多层或者引入FPN这类特征金字塔结构。而Transformer天然就是全局建模第一层就能看到整张图的上下文。DETR选择Transformer的另一个原因是归纳偏置少。CNN默认图像是局部相关的所以用卷积核去扫描但目标检测的某些场景下目标之间的关系比如“一个键盘通常在鼠标旁边”“人手里拿的手机应该和人关联”能提供很强的先验这种关系用自注意力来捕捉比用卷积堆叠自然得多。当然代价也很明显后面会讲到收敛速度慢对小目标不友好这些本质上都跟“全局建模带来的高复杂度”有关。4. 网络结构拆解从骨干网络到Transformer编码器解码器4.1 整体结构一览与特征提取DETR的完整结构可以划分成四块骨干网络Backbone、Transformer编码器、Transformer解码器、预测头FFN。骨干网络一般用ResNet-50或者ResNet-101去掉最后的全连接分类层和平均池化层只保留到Stage 4的卷积输出。假设输入图片是3×800×800经过骨干网络下采样32倍得到的特征图就是2048×25×25。需要注意的是这里的2048是ResNet最后一层输出的通道数DETR会先用一个1×1卷积把通道压缩到256目的是降低Transformer后续计算的自注意力复杂度。然后这个形状为256×25×25的特征图会被展平成序列变成625个长度为256的token。每个token代表原图上某个区域的特征这就是编码器的输入。这个展平操作很关键因为它把二维图像结构“压扁”成了一维序列这也是Transformer处理图像的标准姿势之一。4.2 编码器怎么让图片特征学会“互相沟通”Transformer编码器由多个标准Encoder Layer堆叠而成DETR默认是6层。每一层包含两部分一个多头自注意力模块和一个前馈网络FFN它们各自都有残差连接和Layer Norm。自注意力在这里的作用是让每个空间位置的特征都能聚合全图其他位置的信息。从效果上讲编码器输出的每个位置的特征已经不再是孤立的局部特征而是“结合了全局上下文的局部特征”。比如一个目标只露出很小一部分但周围有很强的场景线索高层注意力就有机会把它“脑补”出来。这与传统CNN靠增大感受野获取全局信息的方式有本质区别——自注意力是动态计算每个位置对当前任务的贡献权重而CNN的卷积核权重是静态共享的。计算复杂度方面假设序列长度是N特征维度是D那么单层自注意力的复杂度是O(N²D)。N625的时候还好但如果输入是大图N可能到几千甚至上万这个平方项的负担就上来了。这也是后来Deformable DETR等改进工作的出发点之一。这里要特别说一个细节编码器里加的是空间位置编码不是文本里那种词嵌入。DETR使用了一种可学习的positional embedding也可以选择正弦余弦编码论文实验显示二者效果差不多它的形状和特征图一样加到每一层编码器的输入里帮助模型区分不同位置。如果没有位置编码自注意力就会把所有token当成无序集合会丢失空间结构信息。4.3 解码器object query的自我进化和目标查询解码器是整个DETR最灵魂的部分。它的输入不是图像特征而是一组可学习的参数维度是100×256这100个向量就是前面反复提到的object queries。你可以把它们理解为100个“待命的目标检测专员”每个query都拥有自己的初始状态训练过程中会不断调整最终学会去图片的不同区域寻找目标。解码器结构也是6层每一层除了自注意力和FFN之外还多了一个交叉注意力模块。自注意力的作用是在100个query之间做通信这非常有意思浏览器想既然每个query可能负责一个目标那它们之间也得学会“你负责左边那个我负责右边那个”避免重复检测。交叉注意力则是让每个query去编码器输出的特征图上“挑选”自己感兴趣的区域。解码器每一层的输出都接到同一个FFN预测头上FFN输出目标类别包括一个特殊的“无目标”类和边界框的归一化坐标。论文里把FFN设计成一个简单的3层感知机中间维度是256激活函数用ReLU。边界框输出是中心点坐标和宽高的归一化值类别输出用softmax。训练时100个query会被强制去匹配图中为数不多的真实目标比如COCO一张图平均只有7个目标剩下的query都会学成“无目标”。这种约束意味着只有少数query最终学到有意义的检测功能其余query的梯度贡献主要来自“预测空也得分低”的约束。4.4 预测头与整体信息流把整个流程串一遍图像输入骨干网络 → 得到下采样32倍的特征图 → 1×1卷积降到256维 → 展平成序列 → 加上位置编码 → 进入编码器 → 输出增强后的特征序列 → 解码器接收这些特征和100个query → 经过6层自注意力交叉注意力 → 输出100个预测结果 → 输入FFN → 得到类别和框。这里有个容易忽略但很重要的点解码器的输出不是只在最后一层用每一层都参与了损失计算。DETR为了帮助训练采用了类似机器翻译里“检查每个解码层输出”的思路每一层都接FFN预测头。这样能让梯度信号更直接地传到浅层缓解Transformer深层的训练难问题。所以说DETR的结构并不复杂每个组件都是现成的Transformer模块但它把经典结构重新翻译成了目标检测的语言。能够用最少的定制化组件实现端到端检测这本身就是一种设计上的克制和自信。5. 匈牙利匹配训练DETR最关键的那一步5.1 为什么需要匈牙利匹配前面的结构说明里提到了训练时要让100个预测和真实目标配对。问题是怎么配对不能简单地把第i个预测框和第i个真实框对比因为预测框的顺序是模型自己定的一上来根本对不上。而且还存在“无目标”query——它预测出来的框没有任何真实目标可以对应。解决思路是把配对当作一个“最小代价二分匹配”问题给定一组预测和一组真实目标计算它们两两之间的匹配代价然后用匈牙利算法求出总代价最小的配对方案。这个算法本身是组合优化里的经典算法也叫Kuhn-Munkres算法能在多项式时间内求出最优匹配。DETR在这里的用法可以理解为先求出一个全局最优的“预测—目标”对应关系再基于这个关系去算损失。这个过程跟传统检测里的“正负样本分配”完全不一样。传统方法是基于IoU的手工规则比如“IoU大于0.5就是正样本”DETR的匹配则是动态计算的每个真实目标只会分配给一个预测而且匹配的标准不仅仅基于IoU还综合考虑了类别预测的置信度。5.2 匹配代价与损失函数的形式化拆解匈牙利匹配的核心是定义匹配代价矩阵。假设图中真实目标有m个预测有100个那么对于第i个真实目标和第j个预测匹配代价定义为L_match(i, j) -1{p_i(c_i) 0} · p_hat_j(c_i) L_box(b_i, b_hat_j)这个公式看起来有点吓人其实拆开就两半前半部分是类别代价后半部分是框回归代价。类别代价如果真实目标的类别不是“无目标”类就用预测模型对该类的预测概率取负值。为什么要取负因为匈牙利算法是求最小代价预测得越准概率越高我们希望代价越小所以加个负号把“高置信度”变成“低代价”。如果一个真实目标对应的类别在预测里压根没被识别出来代价就会很高匹配时就会被放弃。框回归代价用L1损失和GIoU损失的加权和来衡量两个框的差异。论文里系数是λ_l1 5λ_giou 2。L1损失衡量的是中心点坐标和宽高的绝对差异但单独用L1有个问题——对尺寸不同的框不公平大框的绝对误差天然更大。GIoU损失弥补了这个缺陷它衡量的是两个框的几何重叠程度即使不重叠也能提供梯度信号。两者按权重叠加匹配时才能得到更合理的配对。匹配完成之后最终的总损失其实跟匹配代价很像唯一的区别是类别那一项换成了标准的交叉熵损失因为此时我们已经确定了每个预测对应的真实目标是谁不需要再取负概率了直接算交叉熵就行框回归那部分继续用L1GIoU。另外匹配时真实目标的“无目标”类即背景是不参与代价计算的但在损失里背景类要参与否则模型会毫无约束地输出一堆“无目标”预测。5.3 匹配过程的直观理解我用一个例子帮你把整个流程印在脑子里。假设一张图里有3个真实目标一只猫、一只狗、一个人。模型输出100个预测。匈牙利算法会从100个预测中挑出3个分别分配给这3个真实目标同时要求总匹配代价最小。具体挑选的时候算法综合考虑“哪个预测框的位置最接近猫”“预测为猫的概率是不是最高”“框和猫的重叠度怎么样”三个维度一起算。匹配完成后剩下97个预测都是“无目标”类它们在最终损失里的作用就是“预测为背景要正确”。这100个预测的身份分配在每一步训练迭代里都是动态变化的同一个query在第一批数据里可能负责“猫”到第二批数据里可能就变成负责“狗”了。这个特性跟传统检测器里“锚框角色固定”的模式有本质区别。最后想多提一嘴实现匈牙利匹配业界最常用的库是scipy里的linear_sum_assignment函数输入匹配代价矩阵输出行和列的最优配对索引。DETR官方代码就是这么干的而且跑得很快矩阵才100×mm是真实目标数最多也就几十计算量微乎其微。6. 位置编码与Transformer在检测场景中的适配6.1 位置编码的两种选择与我的实测体验前面提到位置编码很关键这里展开一下。DETR官方代码支持两种位置编码一种是固定的正弦余弦编码类似Transformer原文里的方法但扩展到二维另一种是可学习的位置编码。从论文的实验结果看两者在COCO上的最终AP几乎没有明显差别。我自己跑实验时也对比过确实差距在0.1 AP以内基本可以忽略。但有一个细节值得注意位置编码的添加方式。DETR把位置编码同时加到了编码器的输入和每一层自注意力的Q和K上不是V。为什么不是加到V上因为注意力权重是由Q和K算出来的要让注意力“知道”两个token的空间位置关系就必须让位置信息参与Q、K的计算V只是被加权求和的“值”加不加位置信息影响不大。这个设计背后的逻辑理解了之后对后续自己改模型会有帮助。6.2 二维位置编码的生成思路图像是二维结构位置编码也要是二维的。DETR的官方实现里对Height方向的h和Width方向的w分别计算一维的正弦余弦编码然后拼接起来。具体做法是先生成一个shape为(B, 256, H, W)的编码其中前128维对应h方向的位置信息后128维对应w方向的位置信息。注意这里两个方向的特征不是相加是在通道维度上拼接然后通过一个1×1卷积融合同样是256维。我在复现时踩过一个坑一开始直接把两个方向的一维编码在通道维相加效果比拼接差了不少后来查了代码才反应过来。回头想想相加会丢掉“h和w各自独立”的信息维度两个正弦波叠加容易产生歧义拼接保留了完整的二维坐标信息虽然特征数量翻倍但后面接1×1卷积融合效果确实更好。6.3 为什么输入尺寸和编码策略对部署很重要DETR推理时对输入尺寸比较敏感因为Transformer的序列长度随输入分辨率平方增长。同样是800×800和1000×1000后者序列长度从625变成约977自注意力的计算量涨了将近2.5倍。但分辨率太低了又影响小目标检测所以实际使用时要根据硬件平台平衡。另外位置编码是跟分辨率相关的。如果在800×800上训练然后直接换到1333×800这种长边尺度做推理位置编码是可以插值的。但要注意如果训练和推理的分辨率差异过大位置编码的插值可能带来一定性能下降。稳妥的做法是在训练时就加入多尺度训练让模型适应不同的位置编码长度部署时才不会掉链子。7. 训练细节从损失函数到收敛速度的坑7.1 训练超参与收敛速度DETR的训练难度是有名的“难搞”。在COCO数据集上如果不做任何技巧需要大约500轮epoch才能达到比较好的效果而Faster R-CNN大概只需要36轮左右。当时论文公开的最终结果是在COCO val上AP达到42.0单卡V100上训练耗时约3天这还是在8张卡并行的情况下。对普通个人开发者来说复现一次的成本确实不低。问题出在哪大家可以这样理解匈牙利匹配让每个query在一开始几乎随机地跟目标建立对应关系前几轮训练里匹配关系很不稳定模型要花大量时间去“摸索”各个query的工作方向。这个阶段很慢因为梯度信号在“到底谁来负责这个目标”这件事上是模糊的。加速办法有几个一是用预训练权重初始化骨干网络论文里用的是在ImageNet上预训练好的ResNet二是做大规模的随机裁剪增强尤其是随机裁剪到0.5到1.5倍之间的尺度能让模型更快适应不同尺寸的目标三是设置合理的dropout默认0.1和梯度裁剪。即使有了这些手段DETR还是比传统检测器慢得多这也是后来Deformable DETR能大火的原因之一——它把收敛速度提升了一个量级。7.2 数据增强与超参数设置建议根据论文和社区实践我整理了一份可复用的DETR训练配置输入分辨率统一缩放到短边800、长边不超过1333数据增强随机裁剪缩放范围0.5~1.5、随机水平翻转优化器AdamW初始学习率编码器部分1e-4骨干网络部分1e-5权重衰减1e-4学习率调度第200轮和第400轮时衰减10倍Transformer层数编码器6层解码器6层多头注意力头数8损失权重λ_l15λ_giou2Batch Size16单卡4张4卡并行这份配置跑COCO大约300轮能达到AP 40。如果资源有限建议先在VOC或者自己的小数据集上调试把逻辑跑通了再上大任务。7.3 损失函数的一些实现细节实现DETR损失时有两点特别容易出错。第一类别损失要用标准的交叉熵但得益于匈牙利匹配每个预测有了唯一的真实目标所以不需要像RetinaNet那样搞focal loss——端到端匹配本身就缓解了正负样本不平衡的问题。第二背景类的索引在DETR里被特殊处理了类别输出维度是num_classes1多出来的那一类就是“无目标”在匹配过程中背景类不参与代价计算但在最终损失里要参与计算。我还想补充一个训练技巧辅助损失。论文里每个解码器层都接FFN完成分类和回归这样前面几层也能直接收到梯度信号对收敛帮助很大。这个技巧在后来的很多Transformer检测器里都被保留了下来基本成了标配。8. 常见问题与排查技巧亲手复现DETR的避坑指南8.1 训练不收敛或收敛太慢怎么办如果你在自建数据集上跑DETR遇到loss下降特别慢甚至震荡优先检查三件事。第一检查匈牙利匹配是否正常可以在训练日志里打印匹配到的目标数量如果远小于真实目标总数说明匹配代价设计有问题或者query数量太少。第二检查学习率Transformer部分的标准学习率1e-4对很多小数据集来说偏大降到3e-5往往就稳定了。第三检查数据增强如果目标本来就小再加大尺度随机裁剪会进一步降低小目标的可检测性建议先关掉scale增强跑一版对比。还有一种高频问题模型训练完AP很高但对小目标完全没作用。这基本是DETR的通病原因是特征图下采样30多倍后小目标信息几乎丢失query数量有限小目标在匹配时竞争不过大目标。解决思路是使用更高分辨率的输入或者改用Deformable DETR这类多尺度变体。8.2 推理时输出框的重叠问题理论上DETR不需要NMS但实际部署时你会遇到一个问题推理出来100个预测框有时同一个目标会有两三个框重叠在一起置信度都很高。这是为什么因为训练时query通过匹配学会了分工但又不是完美的一一对应关系偶尔会出现“竞标”成功的两个query同时盯上同一个目标。处理办法很简单推理时加一个置信度阈值比如0.7只保留分类概率高于阈值的结果。如果想更干净可以再加一个轻量的NMS但阈值要设得比较宽松比如IoU0.9只合并明显重复的框。实际测试下来加一层轻量NMS能提升大概0.1~0.3个点的AP代价可以忽略不计。8.3 显存占用过高与推理速度的问题DETR对显存的消耗相当惊人尤其在你提高输入分辨率时。800×800输入、batch size为1的情况下V100 16GB显存勉强够用如果把batch提到4张就需要32GB级别。原因在于自注意力的中间激活值实在太多Transformer的可学习参数加骨干网络模型整体大小大概40MBResNet-50版本。部署到GPU上做实时推理时我实测800×800输入单张V100的推理时间大概在25~30ms之间勉强做到40 FPS换到TensorRT量化后能压到15ms以内。如果是CPU推理那就比较痛苦了自注意力对矩阵乘法的需求远高于普通卷积建议别轻易尝试CPU实时方案。模型太大跑不动的时候可以把骨干网络换成ResNet-18用1×1卷积降到128维任务简单的场景下精度稍微损失但速度翻倍。8.4 用DETR训练自己的数据集时的适配步骤很多人私信问我DETR能在自己的数据集上跑吗能但要注意几个适配点。首先是类别数的改动把分类头的输出维度从你的类别数1改掉背景类是最后一位。然后是标注格式DETR的官方format是COCO格式标注里需要包含images和annotations两个部分boxes字段的坐标必须是xyxy绝对坐标不是YOLO那种xywh归一化坐标并且全部转换成标准化后的tensor。我第一次就把格式搞错了训练了一整天loss死活不降自查发现是box坐标范围对不上浪费了整整一个周末。训练自己的数据集时还有一个建议一开始先用小规模数据跑一两个epoch看loss能不能降下来同时观察匈牙利匹配到的目标数是否合理这能帮你快速判断数据格式和代码逻辑有没有问题。等小数据验证通过之后再上全量数据长训。8.5 常见问题速查表问题现象可能原因确认方式解决方案Loss震荡不降学习率过大打印前50步loss变化降低Transformer部分学习率到3e-5匈牙利匹配到的目标数异常少匹配代价设置不合理打印匹配阶段索引统计调整λ_giou或检查背景类逻辑小目标AP几乎为0特征图下采样过大查看不同尺度目标的分层AP提高输入分辨率/改用多尺度模型推理时大量重复框Query竞争同一目标观察置信度高于0.5的框数量提高置信度阈值/加轻量NMS显存溢出序列长度太长/注意力激活值过多观察不同分辨率的显存变化降低分辨率/减少编码器层数训练收敛明显慢于CNNTransformer结构本身收敛慢对比第50轮和200轮AP加辅助损失/用预训练骨干9. 部署要点与扩展方向DETR不是终点而是起点9.1 部署时如何把DETR转到ONNX和TensorRTDETR的部署比传统检测器稍麻烦一点主要在于Transformer里的动态张量操作和Layer Norm在ONNX导出时容易出问题。我的经验是先转成ONNX opset11以上用torch.onnx.export导出时带上dynamic_axes让batch维度和序列长度可变然后TensorRT在构建engine时用fp16模式实测精度损失在0.5 AP以内速度通常能比原生PyTorch快2倍以上。还有个部署细节解码器里的object queries是固定参数可以预先取出然后作为常量放到部署模型里不用每次前向都重新生成。位置编码也是固定的可以直接在模型外部算好传给网络省去一部分计算。9.2 从DETR到Deformable DETR关键改进思路如果有人问我DETR之后应该看什么我一定首推Deformable DETR。它把DETR收敛慢、小目标差的痛点做了一次针对性手术用可变形注意力替代普通自注意力注意力只在每个query周围的少量采样点上计算而不是全图扫描。这既保留了Transformer建模全局关系的能力又把复杂度从平方级降到线性级。我后来在工程项目里改造DETR第一个动作就是把普通自注意力换成可变形注意力结果在自定义数据集上收敛轮数从原来的300轮缩短到50轮小目标AP提升了接近8个点。如果你打算把DETR落地到实际业务我强烈建议直接看Deformable DETR的代码而不是死磕原版。当然这篇文章聚焦DETR本身把原理吃透之后再看改进版你会更清楚每个改动解决的是什么问题。9.3 DETR思想对其他任务的影响DETR的意义不止于目标检测本身。它的“集合预测二分匹配”范式后来被推广到了全景分割Panoptic Segmentation、目标跟踪、姿态估计等多个方向。比如DETR的原班人马后续做了MaX-DeepLab就用类似的query机制直接预测分割掩码。可以说DETR提供了一种通用框架如何把输出表示为“一组对象”再通过Transformer让模型自己学会对象之间的交互。对正在入门深度学习目标检测的同学来说与其花大量时间调锚框、调NMS不如静下心把DETR吃透——它让你看到Transformer在视觉领域的通用潜力也为之后理解SAM、DINO等更多新工作打下一个扎实的地基。我自己复现DETR的过程最大的体会是判别一个模型是否值得学不在它有多“新”而在于它有没有提供一种看问题的新角度。DETR确实有工程落地上的短板但它的“端到端集合预测”思路已经深刻影响了整个检测领域的方向。如果你也想上手跑一跑DETR我建议先别急着调参认真把匈牙利匹配的代码读一遍那是DETR的灵魂也是你理解后续所有改进版本的钥匙。
返回列表