0. 简介
已经好久不写深度学习相关的博客了。但是我觉得DETR值得我重新时期来进行详细介绍。Deformable DETR作为这两年来最有名的DETR变种之一,当然代码量也是比较多的。这里我们从论文到代码来详细分析Deformable DETR的精华之处。最近两年时间从Attention到NAS再到Transformer,视觉检测行业被Transformer拉到了一个新的高度。而DETR作为Transformer中近两年备受关注的方法,也吸引了很多人争相的去改动。其中比较有名的就是Deformable DETR。DETR 提出在目标检测方法中去除人为组件,也可保持优异性能。但由于 Transformer 注意力模块只能有限地处理图像特征图,它的收敛速度就比较慢,特征空间分辨率有限。为了缓解这些问题,作者提出了 Deformable DETR,其注意力模块只会关注目标框周围少量的关键采样点。Deformable DETR 能够取得比 DETR 更优异的性能(尤其在小物体上)。在 COCO 基准上大量的实验证明了该方法的有效性。
1. 文章贡献
目前的目标检测都使用了很多人为组件,比如 anchor 生成、基于规则的训练目标分配机制、NMS 后处理等。而 Carion 等人提出了 DETR 来去除人为组件,第一次构建了一个完全端到端的目标检测器。利用一个简单的结构,将 CNN 和 Transformer 编码器-解码器结合起来使用。但是DETR也有几个问题:(1) 需要更长的训练时间来收敛。例如,在 COCO 基准上,DETR 需要500个 epochs 才能收敛,要比 Faster R-CNN 慢了10-20倍。(2) DETR 对小目标检测表现相对较差。当前的目标检测器通常使用多尺度特征,从高分辨率特征图上可以检测小目标。而对 DETR 来说,高分辨率特征图意味着高复杂度。这些问题主要可以归结为 Transformer 缺乏处理图像特征图的组件。Transformer 编码器的注意力权重的计算,相对于像素个数来说是平方计算的。因此,要处理高分辨率特征图,其计算量是非常高的,内存复杂度也是非常高的。 而Deformable DETR,通过下述的方法加快了 DETR 的收敛速度,并降低高复杂度问题。
- 它将 deformable 卷积的最佳稀疏空间采样方法和 Transformer 的关系建模能力结合起来。作者使用了(多尺度)deformable 注意力模块替换 Transformer 注意力模块,来处理特征图。
- Deformable DETR 开启了探索端到端目标检测器的机会,因为它收敛很快,内存消耗和计算量都较低。
2. Transformer
在讲Deformable DETR之前,我们需要先了解一下Transformer和DETR。这一节我们主要通过self-attention来介绍Transformer中最主要的Q、K、V三个参数,分别对应查询向量(Query)、键向量(Key)、值向量(Value)。
X_1与W^Q权重矩阵相乘得到q_1, 就是与这个单词相关的查询向量。最终使得输入序列的每个单词的创建一个查询向量q_1、一个键向量k_1和一个值向量v_1。
具体步骤为:
-
生成三个向量QKV:对于每个输入X_i,通过词嵌入与三个权重矩阵相乘创建三个向量:一个查询向量、一个键向量和一个值向量。
-
计算得分:为每个输入X_i计算自注意力向量,利用其它输入X对当前X_i打分,这些分数决定了在编码X_i的过程中有多重视输入的其它部分。
-
稳定梯度:将分数除以8(默认值),8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定,也可以使用其它值。
-
归一化:通过softmax传递结果,其作用是使所有X的分数归一化,得到的分数都是正值且和为1。这个softmax分数决定了每个单词对编码当下位置(“Thinking”)的贡献。显然,已经在这个位置上的单词将获得最高的softmax分数,但有时关注另一个与当前单词相关的单词也会有帮助。
-
每个值向量乘以softmax分数:为之后将它们求和做准备,达到关注语义上的相关,并弱化其不相关(乘以非常小的小数)。
-
对加权值向量求和:含义:在编码某个X_i时,将所有其它X的表示(值向量)进行加权求和 ,其权重是通过X_i的表示(键向量)与被编码X_i表示(查询向量)的点积并通过softmax得到。即得到自注意力层在该位置的输出。然后即得到自注意力层在该位置的输出(在我们的例子中是对于第一个单词)。
2.1 将张量引入图景
我们已经了解了模型的主要部分,接下来我们看一下各种向量或张量(译注:张量概念是矢量概念的推广,可以简单理解矢量是一阶张量、矩阵是二阶张量。)是怎样在模型的不同部分中,将输入转化为输出的。
像大部分NLP应用一样,我们首先将每个输入单词通过词嵌入算法转换为词向量。每个单词都被嵌入为512维的向量,我们用这些简单的方框来表示这些向量。
如上述已经提到的,一个编码器接收向量列表作为输入,接着将向量列表中的向量传递到自注意力层进行处理,然后传递到前馈神经网络层中,将输出结果传递到下一个编码器中。
输入序列的每个单词都经过自编码过程。然后,他们各自通过前向传播神经网络——完全相同的网络,而每个向量都分别通过它。
2.2 什么是查询向量、键向量和值向量向量?
它们都是有助于计算和理解注意力机制的抽象概念。请继续阅读下文的内容,你就会知道每个向量在计算注意力机制中到底扮演什么样的角色。
计算自注意力的第二步是计算得分。假设我们在为这个例子中的第一个词“Thinking”计算自注意力向量,我们需要拿输入句子中的每个单词对“Thinking”打分。这些分数决定了在编码单词“Thinking”的过程中有多重视句子的其它部分。
这些分数是通过打分单词(所有输入句子的单词)的键向量与“Thinking”的查询向量相点积来计算的。所以如果我们是处理位置最靠前的词的自注意力的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。
最后,由于我们处理的是矩阵,我们可以将步骤2到步骤6合并为一个公式来计算自注意力层的输出。
3. Multi-head Transformer
这一节主要围绕Transformer 的 Multi-head 注意力。通过增加一种叫做“多头”注意力(“multi-headed” attention)的机制,论文进一步完善了自注意力层,并在两方面提高了注意力层的性能:
1.它扩展了模型专注于不同位置的能力。在上面的例子中,虽然每个编码都在z_1中有或多或少的体现,但是它可能被实际的单词本身所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模型的“多头”注意机制会起到作用。
2.它给出了注意力层的多个“表示子空间”(representation subspaces)。接下来我们将看到,对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用八个注意力头,因此我们对于每个编码器/解码器有八个矩阵集合)。这些集合中的每一个都是随机初始化的,在训练之后,每个集合都被用来将输入词嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间中。
在“多头”注意机制下,我们为每个头保持独立的查询/键/值权重矩阵,从而产生不同的查询/键/值矩阵。和之前一样,我们拿X乘以W^Q、W^K、W^V矩阵来产生查询/键/值矩阵。如果我们做与上述相同的自注意力计算,只需八次不同的权重矩阵运算,我们就会得到八个不同的Z矩阵。
但是,前馈层不需要8个矩阵,它只需要一个矩阵(由每一个单词的表示向量组成)。所以需要把这八个矩阵压缩成一个矩阵,可以直接把这些矩阵拼接在一起,然后用一个附加的权重矩阵W^O与它们相乘。
这几乎就是多头自注意力的全部。这确实有好多矩阵,我们试着把它们集中在一个图片中,这样可以一眼看清。这也可以说明我们在多头输入后可以得到相关联的部位对该位置的影响。
3.1 使用位置编码表示序列的顺序
到目前为止,我们对模型的描述缺少了一种理解输入单词顺序的方法。
为了解决这个问题,Transformer为每个输入的词嵌入添加了一个向量。这些向量遵循模型学习到的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的直觉是,将位置向量添加到词嵌入中使得它们在接下来的运算中,能够更好地表达的词与词之间的距离。(这一点在Vision Transfomer中也很重要)
为了让模型理解单词的顺序,我们添加了位置编码向量,这些向量的值遵循特定的模式。如果我们假设词嵌入的维数为4,则实际的位置编码如下:
在下图中,每一行对应一个词向量的位置编码,所以第一行对应着输入序列的第一个词。每行包含512个值,每个值介于1和-1之间。我们已经对它们进行了颜色编码,所以图案是可见的。
那如何将位置编码和词嵌入整合到一起,这里设置了20字(行)的位置编码实例,词嵌入大小为512(列)。你可以看到它从中间分裂成两半。这是因为左半部分的值由一个函数(使用正弦)生成,而右半部分由另一个函数(使用余弦)生成。然后将它们拼在一起而得到每一个位置编码向量。
原始论文里描述了位置编码的公式(第3.5节)。你可以在 get_timing_signal_1d()
函数中看到生成位置编码的代码。这不是唯一可能的位置编码方法。然而,它的优点是能够扩展到未知的序列长度(例如,当我们训练出的模型需要翻译远比训练集里的句子更长的句子时,或者检测到更多的目标时)。
4. DETR
DETR 构建于 Transformer 编码器-解码器之上,结合使用了匈牙利损失,该损失通过二分匹配为每个 ground-truth 边框分配唯一的预测框。李沐的B站账号中也对DETR进行了详细的学习和讲述。
DEtection TRansformer,其大大简化了目标检测的框架,更直观。其将目标检测任务视为一个图像到集合(image-to-set)的问题,即给定一张图像,模型的预测结果是一个包含了所有目标的无序集合。
DETR 分为四个部分,首先是一个 CNN 的 backbone,Transformer 的 Encoder,Transformer 的 Decoder,最后的预测层 FFN。
4.1 CNN Backbon
CNN 的特征提取部分没有什么可以说的,在 2020 年时候,还没有 Swin 这样的可以针对不同分辨率图像输入的 Transformer Backbone。目标检测的图一般比较大,那么直接上 Transformer 计算上吃不消,所以先用 CNN 进行特征提取并缩减尺寸,再使用 Transformer 是常规操作。
原始 DETR 使用 Imagenet 预训练好的 Resnet,这一部分就极其多变了,可以上 Swin 等等了。那么通常 Backbone 的输出通道为 2048,图像高和宽都变为了 1/32。
4.2 Transformer Encoder
经过 Backbone 后,将Transformer Encoder输入特征图定义为$ $x\in \mathbb{R}^{C\times H\times W},因为C = 2048 是每个 token 的维度,还是比较大,所以先经过一个1 \times 1 的卷积进行降维,然后再输入Transformer Encoder 会更好。H,W分别表示特征图的高度和宽度。自注意力的计算复杂度就是O(H^2W^2C)$$。
此时自注意力机制在特征图上进行全局分析,因为最后一个特征图对于大物体比较友好,那么在上面进行 Self-Attention 会便于网络更好的提取不同位置不同大物体之间的相互关系的联系,比如有桌子的地方可能有杯子,有草坪的地方有树,有一个鸟的地方可能还有一个鸟等等。所以 DETR 在大目标上效果比 Faster RCNN 好就比较容易理解到了。
然后位置编码是被每一个 Multi-Head Self-Attention 前都加入了的。为了体现图像在 x 和 y 维度上的信息,作者的代码里分别计算了两个维度的 Positional Encoding,然后 Cat 到一起。整个 Transformer Encoder 和之前的没什么不同。
每一层的操作完全一致,每一层的具体细节为:
-
进入Encoder的参数有三个:
- backbone最后一层输出的特征图(维度映射到了C,所以也可以叫做特征向量了,因为不再是二维的了)
src,shape=(HW,batch,C)
; - 最后一层的特征图的位置编码
pos,shape=(HW,batch,C)
; - 以及对应最后一层输出的特征图对应的
mask,shape=(batch,HW)
。
- backbone最后一层输出的特征图(维度映射到了C,所以也可以叫做特征向量了,因为不再是二维的了)
-
EncoderLayer的前向过程分为两种情况:
- 一种是在输入多头自注意力层和前向反馈层前先进行归一化;
- 另一种则是在这两个层输出后再进行归一化操作。
-
这里一般默认选择在多头自注意力层和前向反馈层后进行归一化,将”1”中的特征图进行LayerNormlize(与batchNormlize不同,LayerNormlize是在Channel这个维度去做归一化。)
看图片不一定能看得很清楚,举个例子,对于一个(HW,batch,C)的张量:
- BN:会在batch size这个维度,对不同样本的同一个通道直接做归一化,得到C个均值和方差,以及C个$$\gamma ,\beta$$。
注意:BN在做归一化的时候,是对整个Batch size张同一通道的特征图也就是 $$Batch_H_W$$ 个值求一个均值而不是对Batch size个值求一个均值。其目的就是让每一层的分布稳定下来,让后面的层可以在前面层的基础上安心学习知识。而对于同一张特征图而言,共用了同一个卷积,所以需要整张图分布趋同。 - LN:会在Channel这个维度进行归一化处理,也就是,对同一个样本的不同Channel之间做均值LN,最后求得Batch Size个均值。
- BN:会在batch size这个维度,对不同样本的同一个通道直接做归一化,得到C个均值和方差,以及C个$$\gamma ,\beta$$。
-
将特征图分成三份,一份直接作为V值向量,其余两份与位置编码向量直接相加,分别作为K(键向量),Q(查询向量)。
- 将$$KVQ$$输入多头注意力模块,输出一个
src1,shape=(HW,batch,C)
; - 与原src直接相加短接;
- 进行第一次LN;
- linear,Relu激活,dropout,linear还原维度,dropout,再与8的输入短接。
- 第二次LN。
- 输入下一个EncoderLayer;
- 经过6个EncoderLayer后,encoder结束;
4.3 Transformer Decoder
对于 DETR 中的 Transformer 解码器,输入包括编码器的特征图、位置 表示的 N 个目标 query(比如N = 100) 。解码器中一共有两类注意力模块,跨注意力和自注意力模块。在 跨注意力模块中,目标 queries 从解码器特征图上提取特征。Query 元素属于目标 queries,key元素则属于编码器输出的特征图。N_k=H\times WN。跨注意力的复杂度是 O(HWC^2 +NHWC)。复杂度与特征图大小呈线性增长关系。在自注意力模块中,目标queries彼此之间相互交流,从而获取彼此的关系。query 和 key 元素都属于目标 queries。N_q=N_k=N,自注意力模块的复杂度是O(2NC^2 +N^2C)。当目标queries数量不多时,复杂读是可以接受的。
decoder的结构与encoder的机构非常相似,decode的输入包括了几个部分:
- query特征图
embeding ,shape=(num_queries,batch,C)
- query位置编码
pos,shape=(num_queries,batch,C)
- encoder的输出
shape=(HW,batch,C)
decoder在结构上相比encoder每层多了一个多头注意力机制和Add & Norm,目的是对query特征图与query位置编码进行学习,注意:和Encoder相同,DecoderLayer也是6层,每一层输入都是除了上一层的输出以外,还要单独重新加入query pos与encoder中的positional encoding。
decoder流程:
- query embeding(第一次输入是query embeding,第二次是上一层的输出out)与query pos相加得到q,k;
- 将q与k输入以及out(作为值向量)输入第一个多头注意力模块,得到输出shape=(num_queries,batch,C);
- 将上层输出进行dropout与out相加输出;
- 将out+query pos相加作为q,将encoder的输出与词嵌入向量pos想加作为k,encoder的输出作为v作为输入,进入第二个多头注意力模块
LN+drop… - 经过6个DecoderLayer后输出:[num_queries, batch,C]的向量
- 将output堆叠成一个(6,num_queries, batch,C)的向量后输出
4.4 FFN
最后是接了一个FFN,就是两个全连接层,分别进行分类和bbox坐标的回归。
分类分支输出一个outputs_class,shape=(6,batch,100,num_classs)
的tensor。(outputs_class原本输出为(batch,100,hidden),经stack6层)
bbox坐标回归分支输出一个outputs_coord,shape=(6,batch,100,4)的tensor
4.5 Loss
损失一共分为:loss_labels,loss_cardinality,loss_boxes,loss_masks(如果要做分割任务)。
在源码中,专门用了一个SetCriterion(nn.Modlue)类来进行loss的计算。
- 分类loss:CEloss(交叉熵损失);
- 回归loss的计算包括预测框与GT的中心点和宽高的L1 loss以及GIoU loss
- loss_cardinality,后面再说。
5. 用于端到端目标检测的 Deformable DETR
作者将Deformable DETR 概括为一个公式:
对比公式1就可以发现,这里主要不同在于k的取值有原先的整个集合\Omega_k具体为K个近邻点,而每个近邻点的位置通过\delta p_{mqk}学习获得,另外A_{mqk}也是由数据直接网络生成并进行softmax生成。
deformAttn和self-attention的不同点:
- self attention是全局特征作为key值, 而deformAttn是在每个query附近自主学习K个key值
- self attention中的权重是key和queries对的关系刻画,比如内积等,而deformAttn则是直接由线性层获得。
deformAttn位置选择和deformable cnn的不同点:deformable cnn是在正常的cnn kernel点上分别预测偏移量,而deformAttn则是在当前一个点上直接预测多个偏移量。
6. 多尺度的deformable attention模块。
直接看公式:
其中l表示不同的尺度层,\phi_l(\cdot)表示将对应位置映射到第l层,可以发现和公式2相比,不同点在于公式2中从单层生成了K个点,而公式3是每层产生K个点,生成LK个采样位置,然后进行聚合。另外W_m还是每层共享的。
6.1 Deformable Transformer Encoder
通过可形变注意力模块,作者直接将其替换transformer注意力模块,每个encoder的输入输出都是相同分辨率的多尺度特征图,这里分辨率是指相同尺度下不同encoder的输入。多尺度的特征图直接来自ResNet的最后3个stage,而没有使用FPN,因为多尺度可形变注意力模块本身能够融合交换不同scale的信息
对于query像素位置,除了position信息外,还融合了该query所在的level,即{e_l}_{l=1}^L
6.2 可行变transformer解码器
在解码器中除了self-attention还有cross-attention。因为deformable attention作用在卷积层上,因此这里只有cross-attention可以被替代,而self-attention则保持不变。对于每一个object query,由线性层和sigmoid学习出其对应的参考点的2d归一化坐标,然后即可以使用deformable attention操作。
另外deformable DETR又把DETR拉回了相对坐标回归的路子,预测box时回归的是相对于参考点的距离,能够加快收敛。
6.3 Deformable DETR的其他改进和变种
Iterative Bounding Box Refinement 针对于多个decoder,每个decoder的输入是前一层的decoder的输出
两阶段deformable DETR: DETR中的queries是随机初始化的,而两阶段方式则是由Deformable DETR的变种生成初始的候选queries。在第一阶段,移除Deformable DETR中的decoder模块,仅使用encoder模块,每个像素位置都作为queries,直接预测box,然后选择score最大的box作为候选位置。其实第一阶段就有点类似于无锚框单阶段检测思路。
7. 参考链接
https://www.jianshu.com/p/a1f4831a21b2
https://blog.csdn.net/calvinpaean/article/details/113789556
https://blog.csdn.net/qq_41750911/article/details/124189983
https://blog.csdn.net/qq_20549061/article/details/109142205
https://blog.csdn.net/baidu_36913330/article/details/120495817