做响应式网站的框架,西安编程培训机构,河北网站开发公司,大连开发区网站建设一、前言
在开始介绍RT-DETR这个网络之前#xff0c;我们首先需要先了解DETR这个系列的网络与我们常提及的anchor-base以及anchor-free存在着何种差异。 首先我们先简单讨论一下anchor-base以及anchor-free两者的差异与共性#xff1a; 1、两者差异#xff1a;顾名思义我们首先需要先了解DETR这个系列的网络与我们常提及的anchor-base以及anchor-free存在着何种差异。 首先我们先简单讨论一下anchor-base以及anchor-free两者的差异与共性 1、两者差异顾名思义这两者一个显而易见的差别就是有无anchoranchor-base是需要手工选取不同比例大小的anchor来得到proposals而anchor-free则不需要。当然两者具体差异肯定不是这么几句话就能说的清的这里不做详细讨论所以按下不表。 2、两者共性两者虽然在获取proposals的原理方面存在不同但是其最终还是存在了一个多对一的问题也就是会对同一个目标产生多个proposal也即最后两者最后都是需要通过阈值选取以及nms去过滤掉多余的预测框。此处可参考Nms-Free时代 但是当问题涉及到了工业或者生活等其他领域落地问题上面的时候无论是anchor-base还是anchor-free所面临的这中两套阈值问题(要先完成阈值筛选Confidence threshold和非极大值抑制NMS处理两个关键步骤总是会对时效性产生很大的影响。
二、DETR系列网络的动机
在引言中我们提到了无论是anchor-base还是anchor-free其实都存在着一个阈值问题而这个阈值问题就决定了绕不开nms这个机制nms这一机制的存在也就导致无法真正意义上去实现一个端到端的简易部署的网络模型而DETR既不需要proposal也不需要anchor而是直接利用Transformers这种能全局建模的能力通过将目标检测视为一个集合预测问题。同时由于这种全局建模能力使得DETR系列网络不会输出那么多冗余的框。在此处个人水平有限害怕个人水平对读者的理解造成扭曲因此在这就不对DETR做过多解释如果有兴趣的可以移步沐神的精讲视频李沐DETR论文精读
而DETR系列网络究竟有多简洁上述一小段文字自然无法很好的解释其简洁性大家可以通过下面视频截图的50行代码看出DETR的简洁性。 三、RT-DETR网络
首先RT-DETR网络的检测动机其实非常明确从这个命名也能看出来其是想打造一个能实际用于工业界的DETR网络。但是对于DETR而言虽然其具备简单易用的特点然而其实时性较差在DETR的论文中的实验可以看到其FPS较之于Faster-RCNN更低(如下图所示)因此这是DETR的一个缺点。同时DETR还有另外一个缺点就是其虽然借助于自注意机制带来的全局信息使得对于大目标检测效果表现较好但是其对于小目标的检测效果相对而言较弱当然在RT-DETR中也未解决这个问题因此此处只提一下按下不表。 好了既然目前已经知道DETR有一个实时性较差的缺点那么接下来就应该对造成推理耗时高的原因进行一下分析在RT-DETR中作者认为在Encoder部分作用在 S 5 S_5 S5最顶层特征即 C 5 C_5 C5只是换个表达方式这样就可以大幅度减小计算量、提高计算速度同时不会损伤模型性能这样做的的原因是作者是基于一下两点考虑
以往的DETR如Deformable DETR是将多尺度的特征都拉平成拼接在其中构成一个序列很长的向量尽管这可以使得多尺度之间的特征进行充分的交互但也会造成极大的计算量和计算耗时。RT-DETR认为这是当前的DETR计算速度慢的主要原因之一RT-DETR认为相较于较浅 S 3 S_3 S3特征和 S 4 S_4 S4特征 S 5 S_5 S5特征拥有更深、更高级、更丰富的语义特征这些语义特征是Transformer更加感兴趣的和需要的对于区分不同物体的特征是更加有用的而浅层特征因缺少较好的语义特征而起不到什么作用。
于个人而言感觉这个观点很类似YOLOF的观点在YOLOF中也是直接使用顶层的特征层有感兴趣的可以去看看YOLOF这篇文章。
而为了验证这个观点作者的团队设计了若干对照组实验实验如下图所示 对于对照组(a)其结构就是DINO-R50但移除了DINO-R50中的多尺度encoder直接将 S 3 S_3 S3、 S 4 S_4 S4和 S 5 S_5 S5拼接在一起交给后续的网络去处理得到最终的输出。注意这里的拼接是先将二维的 H × W H×W H×W拉平成 H W HW HW然后再去拼接: H 1 W 1 H 2 W 2 H 3 W 3 H_1W_1H_2W_2H_3W_3 H1W1H2W2H3W3。
对于对照组(b)作者团队在(a)基础上加入了单尺度的Transformer EncoderSSE仅包含一层Encoder层分别处理三个尺度的输出注意三个尺度共享一个SSE而不是为每一个尺度都设计一个独立的SSE。通过这一共享的操作三个尺度的信息是可以实现一定程度的信息交互。最后将处理结果拼接在一起交给后续的网络去处理得到最终的输出。
对于对照组©作者团队使用多尺度的Transformer EncoderMSE大概是Deformable DETR所采用的那一类MSE结构。将三个尺度的特征拼接在一起后交由MSE来做处理使得三个尺度的特征同时完成“尺度内”和“跨尺度”的信息交互和融合最后将处理结果交给后续的网络去处理得到最终的输出。
对于对照组(d)作者团队则先用共享的SSE分别处理每个尺度的特征然后再使用PAN-like的特征金字塔网络去融合三个尺度的特征最后将融合后的多尺度特征拼接在一起交给后续的网络去处理得到最终的输出。
对于对照组(e)作者团队则使用一个SSE只处理 S 5 S_5 S5特征即所谓的AIFI模块随后再使用CCFM模块去完成跨尺度的特征融合最后将融合后的多尺度特征拼接在一起交给后续的网络去处理得到最终的输出。 最终对比结果如上方的图所示我们简单地来分析一下这组对比试验说明了什么样的结论。
首先A即对照组(a)取得到了43.0 AP的结果注意A的设置是不包含Encoder的即没有自注意力机制在Backbone之后直接接Decoder去做处理从而获得输出结果可想而知A的性能应该是最差的这也是后续实验的Baseline随后在A的基础上B引入了共享的SSE去处理每个尺度的特征使得性能从43.0提升至44.9表明使用共享的SSE是可以提升性能的。从研究的角度来说对于B应该再做一个额外的实验即使用三个独立的SSE分别去处理每个尺度的特征以此来论证“共享SSE”的必要性。当然我们可以感性地认识到共享SSE会更好但从理性的严谨层面来讲补上这一对比试验还是有必要的。简而言之B证明了加入Encoder会更好相较于BC则是使用MSE即使用MSE来同步完成“尺度内”和“跨尺度”的特征融合应该和Deformable DETR的多尺度Encoder做法相似这一做法可以让不同尺度的特征之间得到更好的交互和融合可预见地会提升了AP指标其实验结果也意料内地证明了这一点AP指标从43.0提升至45.6要高于B的44.9这表明MSE的做法是有效的即“尺度内”和“跨尺度”的特征融合是必要的。但是从速度的角度来看MSE拖慢了推理速度Latency从7.2增加值13.3 ms要高于B组的11.1 ms随后是对照组D不同于CD是相当于解耦了C中的MSE先使用共享的SSE分别去处理每个尺度的特征完成“尺度内”的信息交互然后再用一个PAN风格的跨尺度融合网络去融合不同尺度之间的特征完成“跨尺度”的信息融合。这种做法可以有效地避免MSE中因输入的序列过长而导致的计算量增加的问题。相较于CD的这种解耦的做法不仅仅使得Latency从13.3 ms降低至12.2 ms性能也从45.6 AP提升至46.4 AP这表明MSE的做法并不是最优的先处理“尺度内”再完成“跨尺度”性能会更好随后是对照组D不同于CD是相当于解耦了C中的MSE先使用共享的SSE分别去处理每个尺度的特征完成“尺度内”的信息交互然后再用一个PAN风格的跨尺度融合网络去融合不同尺度之间的特征完成“跨尺度”的信息融合。这种做法可以有效地避免MSE中因输入的序列过长而导致的计算量增加的问题。相较于CD的这种解耦的做法不仅仅使得Latency从13.3 ms降低至12.2 ms性能也从45.6 AP提升至46.4 AP这表明MSE的做法并不是最优的先处理“尺度内”再完成“跨尺度”性能会更好最后就是对照组E在DS5的基础上E重构了跨尺度特征融合模块构建了新的CCFM模块由于论文里没给出CSF模块的细节所以CCFM模块究竟调整了哪里也是无法获知的但就CCFM本身来看大体上也是一个PAN-like的结构。在换上了CCFM后性能从46.8进一步地提升至47.9。
综上这组对比试验证明了两件事1) Transformer的Encoder部分只需要处理high-level特征既能大幅度削减计算量、提升计算速度同时也不会损伤到性能甚至还有所提升2 对于多尺度特征的交互和融合我们仍可以采用CNN架构常用的PAN网络来搭建只需要一些细节上的调整即可。
另外我们仔细来看一下这个对照组(e)注意根据论文给出的结果和源代码所谓的CCFM其实还是PaFPN如下方的图10所示其中的Fusion模块就是一个CSPBlock风格的模块。 为了看得更仔细一些我们来看一下官方源码如下方的Python代码所示。依据论文的设定HybridEncoder包含AIFI和CCFM两大模块其中AIFI就是Transformer的Encoder部分而CCFM其实就是常见的PaFPN结构首先用若干层1x1卷积将所有的特征的通道数都映射至同一数目如256随后再进行top-down和bottom-up两部分的特征融合。
# https://github.com/PaddlePaddle/PaddleDetection/blob/develop/ppdet/modeling/transformers/hybrid_encoder.pyclass HybridEncoder(nn.Layer):__shared__ [depth_mult, act, trt, eval_size]__inject__ [encoder_layer]def __init__(self,in_channels[512, 1024, 2048],feat_strides[8, 16, 32],hidden_dim256,use_encoder_idx[2],num_encoder_layers1,encoder_layerTransformerLayer,pe_temperature10000,expansion1.0,depth_mult1.0,actsilu,trtFalse,eval_sizeNone):super(HybridEncoder, self).__init__()self.in_channels in_channelsself.feat_strides feat_stridesself.hidden_dim hidden_dimself.use_encoder_idx use_encoder_idxself.num_encoder_layers num_encoder_layersself.pe_temperature pe_temperatureself.eval_size eval_size# channel projectionself.input_proj nn.LayerList()for in_channel in in_channels:self.input_proj.append(nn.Sequential(nn.Conv2D(in_channel, hidden_dim, kernel_size1, bias_attrFalse),nn.BatchNorm2D(hidden_dim,weight_attrParamAttr(regularizerL2Decay(0.0)),bias_attrParamAttr(regularizerL2Decay(0.0)))))# encoder transformerself.encoder nn.LayerList([TransformerEncoder(encoder_layer, num_encoder_layers)for _ in range(len(use_encoder_idx))])act get_act_fn(act, trttrt) if act is None or isinstance(act, str, dict)) else act# top-down fpnself.lateral_convs nn.LayerList()self.fpn_blocks nn.LayerList()for idx in range(len(in_channels) - 1, 0, -1):self.lateral_convs.append(BaseConv(hidden_dim, hidden_dim, 1, 1, actact))self.fpn_blocks.append(CSPRepLayer(hidden_dim * 2,hidden_dim,round(3 * depth_mult),actact,expansionexpansion))# bottom-up panself.downsample_convs nn.LayerList()self.pan_blocks nn.LayerList()for idx in range(len(in_channels) - 1):self.downsample_convs.append(BaseConv(hidden_dim, hidden_dim, 3, stride2, actact))self.pan_blocks.append(CSPRepLayer(hidden_dim * 2,hidden_dim,round(3 * depth_mult),actact,expansionexpansion))def forward(self, feats, for_motFalse):assert len(feats) len(self.in_channels)# get projection featuresproj_feats [self.input_proj[i](feat) for i, feat in enumerate(feats)]# encoderif self.num_encoder_layers 0:for i, enc_ind in enumerate(self.use_encoder_idx):h, w proj_feats[enc_ind].shape[2:]# flatten [B, C, H, W] to [B, HxW, C]src_flatten proj_feats[enc_ind].flatten(2).transpose([0, 2, 1])if self.training or self.eval_size is None:pos_embed self.build_2d_sincos_position_embedding(w, h, self.hidden_dim, self.pe_temperature)else:pos_embed getattr(self, fpos_embed{enc_ind}, None)memory self.encoder[i](src_flatten, pos_embedpos_embed)proj_feats[enc_ind] memory.transpose([0, 2, 1]).reshape([-1, self.hidden_dim, h, w])# top-down fpninner_outs [proj_feats[-1]]for idx in range(len(self.in_channels) - 1, 0, -1):feat_heigh inner_outs[0]feat_low proj_feats[idx - 1]feat_heigh self.lateral_convs[len(self.in_channels) - 1 - idx](feat_heigh)inner_outs[0] feat_heighupsample_feat F.interpolate(feat_heigh, scale_factor2., modenearest)inner_out self.fpn_blocks[len(self.in_channels) - 1 - idx](paddle.concat([upsample_feat, feat_low], axis1))inner_outs.insert(0, inner_out)# bottom-up panouts [inner_outs[0]]for idx in range(len(self.in_channels) - 1):feat_low outs[-1]feat_height inner_outs[idx 1]downsample_feat self.downsample_convs[idx](feat_low)out self.pan_blocks[idx](paddle.concat([downsample_feat, feat_height], axis1))outs.append(out)return outs而对于其中解码器的decoder部分使用的是DINO Head的decoder部分也即使用了DINO的一个“去噪思想”具体我未了解过有感兴趣的可以自行了解。但是看官方源码可知在推理阶段的时候RT-DETR的decoder部分与DETR并无区别由于我只关注推理阶段因此关于去噪思想这部分各位可以详细看看DINO以及官方源码自行了解。 class RTDETRTransformer(nn.Layer):__shared__ [num_classes, hidden_dim, eval_size]def __init__(self,num_classes80,hidden_dim256,num_queries300,position_embed_typesine,backbone_feat_channels[512, 1024, 2048],feat_strides[8, 16, 32],num_levels3,num_decoder_points4,nhead8,num_decoder_layers6,dim_feedforward1024,dropout0.,activationrelu,num_denoising100,label_noise_ratio0.5,box_noise_scale1.0,learnt_init_queryTrue,query_pos_head_inv_sigFalse,eval_sizeNone,eval_idx-1,eps1e-2):super(RTDETRTransformer, self).__init__()assert position_embed_type in [sine, learned], \fValueError: position_embed_type not supported {position_embed_type}!assert len(backbone_feat_channels) num_levelsassert len(feat_strides) len(backbone_feat_channels)for _ in range(num_levels - len(feat_strides)):feat_strides.append(feat_strides[-1] * 2)self.hidden_dim hidden_dimself.nhead nheadself.feat_strides feat_stridesself.num_levels num_levelsself.num_classes num_classesself.num_queries num_queriesself.eps epsself.num_decoder_layers num_decoder_layersself.eval_size eval_size# backbone feature projectionself._build_input_proj_layer(backbone_feat_channels)# Transformer moduledecoder_layer TransformerDecoderLayer(hidden_dim, nhead, dim_feedforward, dropout, activation, num_levels,num_decoder_points)self.decoder TransformerDecoder(hidden_dim, decoder_layer,num_decoder_layers, eval_idx)# denoising partself.denoising_class_embed nn.Embedding(num_classes,hidden_dim,weight_attrParamAttr(initializernn.initializer.Normal()))self.num_denoising num_denoisingself.label_noise_ratio label_noise_ratioself.box_noise_scale box_noise_scale# decoder embeddingself.learnt_init_query learnt_init_queryif learnt_init_query:self.tgt_embed nn.Embedding(num_queries, hidden_dim)self.query_pos_head MLP(4, 2 * hidden_dim, hidden_dim, num_layers2)self.query_pos_head_inv_sig query_pos_head_inv_sig# encoder headself.enc_output nn.Sequential(nn.Linear(hidden_dim, hidden_dim),nn.LayerNorm(hidden_dim,weight_attrParamAttr(regularizerL2Decay(0.0)),bias_attrParamAttr(regularizerL2Decay(0.0))))self.enc_score_head nn.Linear(hidden_dim, num_classes)self.enc_bbox_head MLP(hidden_dim, hidden_dim, 4, num_layers3)# decoder headself.dec_score_head nn.LayerList([nn.Linear(hidden_dim, num_classes)for _ in range(num_decoder_layers)])self.dec_bbox_head nn.LayerList([MLP(hidden_dim, hidden_dim, 4, num_layers3)for _ in range(num_decoder_layers)])self._reset_parameters()def forward(self, feats, pad_maskNone, gt_metaNone, is_teacherFalse):# input projection and embedding(memory, spatial_shapes,level_start_index) self._get_encoder_input(feats)# prepare denoising trainingif self.training:denoising_class, denoising_bbox_unact, attn_mask, dn_meta \get_contrastive_denoising_training_group(gt_meta,self.num_classes,self.num_queries,self.denoising_class_embed.weight,self.num_denoising,self.label_noise_ratio,self.box_noise_scale)else:denoising_class, denoising_bbox_unact, attn_mask, dn_meta None, None, None, Nonetarget, init_ref_points_unact, enc_topk_bboxes, enc_topk_logits \self._get_decoder_input(memory, spatial_shapes, denoising_class, denoising_bbox_unact,is_teacher)# decoderout_bboxes, out_logits self.decoder(target,init_ref_points_unact,memory,spatial_shapes,level_start_index,self.dec_bbox_head,self.dec_score_head,self.query_pos_head,attn_maskattn_mask,memory_maskNone,query_pos_head_inv_sigself.query_pos_head_inv_sig)return (out_bboxes, out_logits, enc_topk_bboxes, enc_topk_logits,dn_meta)同样在RT-DETR中提及的IoU-aware Query Selection其实在推理阶段也不存在其是在训练期间约束检测器对高 IoU 的特征产生高分类分数对低 IoU 的特征产生低分类分数。因此若只关注推理阶段的读者这部分也可无需关注而关于IoU-aware Query Selection此部分其实是在assignment阶段和计算loss的阶段classification的标签都换成了 “IoU软标签” 所谓的“IoU软标签”就是指将预测框与GT之间的IoU作为类别预测的标签。熟悉YOLO工作的读者一定对此不会陌生其本质就是已经被广泛验证了的IoU-aware。在最近的诸多工作里比如RTMDet、DAMO-YOLO等工作中都有引入这一理念去对齐类别和回归的差异。之所以这么做是因为按照以往的one-hot方式完全有可能出现“当定位还不够准确的时候类别就已经先学好了”的“未对齐”的情况毕竟类别的标签非0即1。但如果将IoU作为类别的标签那么类别的学习就要受到回归的调制只有当回归学得也足够好的时候类别才会学得足够好否则类别不会过快地先学得比回归好因此后者显式地制约着前者。
四、总结
最后先看一下论文在实验部分给出的对比表格如下方的图所示 由于在这里我考虑的是RT-DETR的实时性、GFLOPS以及相应的检测性能因此我只关注其与YOLO系列的对比而在YOLO系列中目前使用较多的是YOLOV5以及PPYOLOE网络。而RT-DETR与这两个网络对应的L与X对比能看到其FPS是V5的一倍同样也比PP高出十来FPS而算力开销也是与两者接近检测性能同样高出了这两者几个点。同时RT-DETR这个网络也继承了DETR网络只要支持CNN与Transformer就能使用以及其推理阶段简洁性的特点。不难看出在未来DETR系列的网络应能全面铺开而且其网络的简洁性对于实际部署工作也是有利的。
总而言之在未来完完全全可以在实时的DETR网络方面好好期待一把。因此于此处记录一下阅读这篇文章以及DETR的读后感。受限于个人能力其中纰漏肯定还有不少欢迎各位大力指出共同成长。
参考链接 《目标检测》-第33章-浅析RT-DETR 超越YOLOv8飞桨推出精度最高的实时检测器RT-DETR PaddlePaddle DETR 论文精读 Nms-Free时代