论文题目:End-to-End Object Detection with Transformers 2020
论文复现可参考:项目复现 | DETR:利用transformers端到端的目标检测_夏天|여름이다的博客-CSDN博客
论文地址:2005.12872.pdf (arxiv.org)
目录
一:论文翻译
*二:关键知识点
2.1.编码-解码Encoder-Decoder:
2.2.多头注意力multi-head attention:
2.3.位置编码Positional encoding:
*三:疑问与解答
3.1.这篇论文与传统的transformer的区别是什么?
3.2.encoder处为什么只有qk加了位置编码(v就没有)?
3.3.
*四:总结
参考文献:
我们提出了一种将对象检测视为直接集合预测问题的新方法。我们的方法简化了检测方式,有效地消除了对许多人工设计的组件的需求,例如非最大抑制过程(NMS)或锚点生成,这些组件明确地编码了我们关于任务的先前知识。新框架的主要成分称为DEtection TRansformer简写为DETR,是基于集合的全局损耗,通过二分匹配强制进行独特的预测,以及transformer编码器 - 解码器架构。给定一组固定的学习对象查询,DETR 推理对象和全局图像上下文的关系,以直接并行输出最终的预测集。与许多其他现代探测器不同,新模型在概念上很简单,不需要专门的库。DETR 在具有挑战性的 COCO 对象检测数据集上演示的准确性和运行时性能与完善且高度优化的更快 RCNN 基线相当。此外,DETR可以很容易地泛化,以统一的方式产生全景分割。我们表明,它的表现明显优于竞争基线。训练代码和预训练模型可在 https://github.com/facebookresearch/detr 获得。
物体检测的目标是为每个感兴趣的物体预测一组边界框和类别标签。现有的检测器以间接的方式解决这个集合预测的问题以间接的方式解决这个集合预测的任务,方法是在大量的建议[37,5]、锚点[23]或窗口中心[53,46]上定义替代回归和分类问题。它们的性能受到后处理步骤的显著影响,这些后处理步骤可以折叠近乎重复的预测,锚点集的设计和启发式方法将目标框分配给锚[52]。为了简化这些管道。我们提出了一种直接的集合预测方法来绕过代理任务。这种端到端的理念已经在复杂的结构化预测任务中取得了重大进展,如机器翻译或语音识别,但还没有在物体检测:以前的尝试[43,16,4,39]要么添加了其他形式的先验知识,要么没有被证明在具有挑战性的基准上与强大的基线竞争。本文旨在弥补这一差距。
我们通过将物体检测看作是一个直接的集合预测问题来简化训练预测问题。我们采用了基于transformers的编码器-解码器架构[47],这是一种流行的序列预测架构。transformers的自注意机制,它明确地模拟了序列中元素之间的所有成对互动,使这些架构特别适合于集预测的特定限制,如去除重复的预测。
我们的检测变换器(DETR,如图1)一次预测所有的物体。一次性预测所有的物体,并通过设定的损失函数进行端到端的训练,在预测的物体和地面真实物体之间进行两方匹配。DETR通过放弃多个手工设计的编码先验知识的组件来简化检测管道。先验知识,如空间锚或非最大压制。与大多数现有的检测方法不同,DETR不需要任何定制的层,因此可以很容易地在任何地方复制。因此,可以在任何包含标准CNN和转化器类的框架中轻松复现。
图1:DETR 通过结合一个普通的CNN和transformer架构,直接预测(并行)最终的检测集合。
在训练过程中,两方匹配独一无二地将预测框进行分配。没有匹配的预测应该产生一个 "无对象"(∅)类预测。
与以前大多数关于直接集预测的工作相比,DETR的主要特点是结合了二元匹配损失和transformers与(非自回归)并行解码[29,12,10,8]。相比之下,以前的工作着重于用RNN进行自回归解码[43,41,30,36,42]。我们的匹配损失函数唯一地将预测结果分配给真实预测值,并且对
对预测对象的变异是不变的,所以我们可以并行地映射它们。
我们在最流行的物体检测数据集COCO[24]之一上评估了DETR,与非常有竞争力的Faster R-CNN基线[37]进行对比。Faster RCNN经历了多次设计迭代,其性能自最初发表以来得到了极大的提高。自最初发表以来,其性能得到了极大的改善。我们的实验表明,我们的新模型实现了相当的性能。更确切地说,DETR表现出在大型物体上的性能明显更好,这一结果可能是由transformers的非局部计算。然而,它在小物体上获得的性能较低。我们希望未来的工作能够改善这方面的情况
就像FPN[22]对Faster R-CNN的开发一样。
DETR的训练设置在多个方面与标准物体探测器不同。新模型需要超长的训练时间表,并从transformer的辅助解码损失中获益。我们彻底探讨了哪些组件对所展示的性能至关重要。
DETR的设计理念很容易扩展到更复杂的任务。在我们的实验中,我们表明一个简单的分割头在预训练的DETR的基础上进行训练,在Panoptic Segmentation[19]上超过了竞争的基线。这是一项具有挑战性的像素级识别任务,最近已经得到了普及。
我们的工作建立在几个领域的现有工作之上:用于集合预测的双点匹配损失,基于transformer的编码器-解码器架构、平行解码,以及物体检测方法。
没有典型的深度学习模型可以直接预测集合。基本的集合预测的基本任务是多标签分类(例如,见[40,33]中关于计算机视觉的参考文献)。其中的基线方法,一比一,并不适用于检测等问题,在这些问题中,元素之间存在着一个基本结构元素(即近乎相同的盒子)之间的结构。这些任务中的第一个困难是要避免近似的重复。目前大多数检测器使用后处理,如非最大限度的抑制来解决这个问题,但是直接的集合预测是不需要后处理。他们需要全局推理方案,对所有预测元素之间的相互作用进行建模。所有预测元素之间的相互作用,以避免冗余。对于恒定大小的集合预测,密集的全连接网络[9]是足够的,但成本很高。一个一般的的方法是使用自动回归的序列模型,如递归神经网络[48]。在所有的情况下,损失函数应该是不变的,因为预测的变化是不变的。通常的解决方案是在匈牙利算法[20]的基础上设计一个损失,以找到地面实况和预测之间的双点匹配。这就加强了变异的不变性,并保证每个目标元素有一个唯一的匹配。我们采用了双点匹配的损失方法。然而,与大多数先前的工作不同,我们摆脱了自回归模型,而使用了transformer与并行解码,我们将在下面描述。
transformer是由Vaswani等人[47]提出的,是一种新的基于注意力的机器翻译的新构件。注意力机制[2]是聚合整个输入序列信息的神经网络层。transformer引入了自我注意层,与非本地神经网络[49]类似,它扫描序列的每个元素,并通过聚合整个序列的信息来更新它。基于注意力的模型的主要优势之一是它们的全局计算和完美的记忆,这使得它们比RNNs更适合于长序列。transformer现在是在自然语言处理、语音处理和计算机视觉的许多问题中取代了RNNs[8,27,45,34,31]。 transformer首先被用于自动回归模型,遵循早期的序列-序列模型[44],逐一生成输出标记。然而,令人望而却步的推理成本(与输出长度成正比,且难以批量化)导致了并行序列生成的发展,在音频领域[29]。机器翻译[12,10],单词表示学习[8],以及最近的语音识别[6]。我们还将transformer和平行解码结合起来,因为他们在计算成本和执行全局计算的能力之间进行了适当的权衡集预测所需的全局计算的能力。
大多数现代物体检测方法是相对于一些初始猜测进行预测的。两阶段检测方法[37,5]以提议为依据预测方框,而单阶段方法则以锚点[23]或可能的物体中心网格[53,46]进行预测。最近的工作[52]表明,这些系统的最终性能这些系统的最终性能在很大程度上取决于这些初始猜测的确切设置方式。在我们的模型中,我们能够消除这种手工制作的过程,并通过直接预测具有绝对箱体的检测集来简化检测过程。预测输入图像,而不是一个锚。
基于集合的损失。一些物体检测器[9,25,35]使用了双点匹配的。然而,在这些早期的深度学习模型中,不同的预测之间的关系之间的关系只用卷积层或全连接层来建模,并采用手工设计的NMS后处理。手工设计的NMS后处理可以提高其性能。最近的检测器[37,23,53]使用了基础事实和预测之间的非唯一分配规则以及NMS。
可学习的NMS方法[16,4]和关系网络[17]明确地模拟了注意不同预测之间的关系。使用直接集合损失。它们不需要任何后处理步骤。然而,这些方法采用额外的手工制作的上下文特征,如建议箱坐标来有效地模拟探测之间的关系,而我们寻找的解决方案是减少在模型中编码的先验知识。
递归检测器。与我们的方法最接近的是端到端集合预测用于物体检测[43]和实例分割[41,30,36,42]。与我们类似。他们使用基于CNN激活的编码器-解码器架构的双比特匹配损失来直接产生一个预测。CNN的激活来直接产生一组边界框。然而,这些方法只在小数据集上进行了评估,并没有针对现代基线。特别是,它们是基于自回归模型(更确切地说,是RNN)。所以它们没有利用最近的并行解码的transformer。
检测中的直接集合预测有两个要素:(1)一个集合预测损失,迫使预测和地面真实之间的唯一匹配。(2)一个能预测(单次)一组物体并对其关系进行建模的结构。我们在图2中详细描述了我们的架构。
DETR的整体结构出奇地简单,在图2中描述。它包含三个主要部分,我们将在下面进行描述:一个CNN主干,用于提取一个紧凑的特征表示,一个编码-解码器转换器,以及两个CNN主干。提取一个紧凑的特征表示,一个编码器-解码器转化器,一个简单的前馈网络(FFN),进行最终的检测预测。
与许多现代检测器不同,DETR可以在任何深度学习框架中实现,该框架提供了一个通用的CNN骨干和一个仅有几百行的转化器架构实现。DETR的推理代码可以在PyTorch[32]中用不到50行实现。我们希望我们的方法的简单性能够吸引新的研究人员加入检测社区。
图2: DETR使用一个传统的CNN主干来学习一个输入图像的二维表示。该模型对其进行扁平化处理,并在将其传递给变换器编码器之前用位置编码进行补充。将其传递给一个transformer编码器。然后,transformer解码器将学习到的少量位置嵌入作为输入。我们称其为对象查询,并在解码器中输入少量固定数量的位置嵌入。另外还关注编码器的输出。我们将解码器的每个输出嵌入传递给一个共享的前馈网络(FFN),该网络预测一个检测(类别和边界框)或 "无物体 "类。
主干网络。从初始图像Ximg∈R 3×H0×W0(有3个色通道),一个传统的CNN骨干网会生成一个较低分辨率的激活图f∈R C×H×W。我们使用的典型值是C = 2048,H, W =H0/32 ,W0/32 .
Transformer编码器。首先,一个1x1的卷积将高级激活图f的通道维度从C减少到较小的维度d。
新的特征图z0∈R d×H×W。编码器希望有一个序列作为输入,因此我们将z0的空间维度折叠成一个维度,从而得到一个d×HW的特征图。每个编码器层都有一个标准的结构,由一个多头的自我注意模块和一个前馈网络(FFN)。由于变换器结构是不变的,我们用固定的位置编码[31,3],这些编码被添加到每个注意力层的输入中。我们架构的详细定义将推迟到补充材料中,它遵循[47]中的描述。
Transformer解码器。解码器遵循转化器的标准结构。transformer的标准结构,使用多头的自和
编码器-解码器注意机制。与原始转化器的区别是,我们的模型在每个解码器层对N个对象进行并行解码。而Vaswani等人[47]使用一个自回归模型(autoregressive model),每次预测输出的顺序一次预测一个元素。我们请不熟悉这些概念的读者参考补充材料。考补充由于解码器也是互换不变的。N个输入嵌入必须是不同的,以产生不同的结果。这些输入嵌入是学习到的位置编码,我们将其称为对象查询。与编码器类似,我们将它们添加到每个注意力层的输入中。N个对象查询被解码器转化为输出嵌入。然后,它们被一个前馈网络独立地解码为盒子坐标和类别标签。一个前馈网络(在下一小节中描述),产生N个最终的预测。使用自我和编码器-解码器对这些嵌入的关注。该模型使用成对的关系对所有物体进行全局推理之间的关系,同时能够使用整个图像作为背景。
归纳预测前馈网络(FFNs)。最终的预测是由一个具有ReLU激活函数和隐藏维度d的3层感知器和一个线性投影层计算的。FFN预测的是归一化的中心坐标、预测框的高度和宽度(相对于输入图像),而线性层则使用softmax函数预测类别标签。由于我们预测的是一个固定大小的N个边界框,而N通常比图像中感兴趣的物体的实际数量要大得多,所以一个额外的特殊类标签∅被用来表示在一个槽内没有检测到任何物体。这个类别扮演着与标准物体检测方法中的 "背景 "类类似的角色。
辅助解码损失。我们发现在训练过程中使用辅助损失[1]对解码器很有帮助,特别是帮助模型输出每个类别的正确数量的对象。我们在每个解码层之后添加预测的FFN和匈牙利损失。解码器层之后。所有的预测FFNs共享它们的参数。我们使用一个额外的共享层规范来规范来自不同解码层的预测FFN的输入。
我们表明,与Faster R-CNN相比,DETR在COCO的评估中取得了有竞争力的结果。然后,我们提供了一个详细的消融架构和损失的详细研究,并提供见解和定性结果。最后,为了表明DETR是一个多功能和可扩展的模型,我们提出了以下结果我们展示了在全景分割上的结果,只在固定的DETR上训练了一个小的扩展。我们提供代码和预训练的模型来重现我们的实验,网址是:https://github.com/facebookresearch/detr。
数据集。我们在COCO 2017检测和全景分割数据集[24,18]上进行了实验,包含118k训练图像和5k验证图像。每张图像都有边界框和全景分割的注释。每张图像平均有7个实例,在训练集的图像中最多有63个实例,在相同的图像中从小到大不等。如果没有指定,我们将AP报告为bbox AP,即多个阈值的积分指标。为了与Faster R-CNN进行比较,我们报告了最后一次训练时的验证AP,对于消融,我们报告了最后10次的验证结果的中位数epochs。
技术细节。我们用AdamW[26]训练DETR,将初始变压器的学习率设置为10-4,主干网为10-5
,而权重衰减为10-4.所有转化器的权重都是用Xavier init [11]初始化的,骨干网是用来Torchvision的ImageNet-pretrained ResNet模型[15]和冻结的batchnorm层。我们报告了两个不同骨干的结果:ResNet50和ResNet101。相应的模型分别被称为DETR和DETR-R101。继[21]之后,我们还通过在最后一个阶段增加一个扩张来提高特征分辨率。在骨干网的最后一个阶段增加了一个扩张,并从这个阶段的第一个卷积中删除了一个跨度。阶段的第一个卷积,从而提高特征分辨率。相应的模型分别被称为DETR-DC5和DETR-DC5-R101(扩张的C5阶段)。这种修改将分辨率提高了2倍,从而提高了对小物体的性能。物体的性能,但代价是编码器的自我关注度提高了16倍。导致整体计算成本增加2倍。全面比较这些模型和Faster R-CNN的FLOPs的全面比较见表1。
表1:与带有ResNet-50和ResNet-101主干的Faster R-CNN的在COCO验证集上的比较。顶端部分显示了Faster R-CNN模型在Detectron2[50]中的结果,中间部分显示Faster R-CNN模型与
GIoU[38]、随机作物训练时间的增加和长的9倍训练计划的Faster R-CNN模型的结果。DETR模型取得了与重度调整的Faster R-CNN基线相当的结果。有较低的APS,但大大提高了APL。我们使用torchscript Faster R-CNN和DETR模型来测量FLOPS和FPS。名称中没有R101的结果对应于ResNet-50。
我们使用比例增强法,调整输入图像的大小,使其最短的一面至少为480,最多为800像素,而最长的一面为1333[50]。边至少是480,最多是800像素,而最长的是1333[50]。为了通过编码器的自我注意来帮助学习全局关系。我们还在训练过程中应用了随机裁剪增强,提高了大约1个AP的性能。具体来说,训练图像被裁剪为概率0.5裁剪成一个随机的矩形斑块,然后再次调整大小为
800-1333. 变换器的训练默认为0.1的滤波。在推理中时,有些槽的预测是空类。为了优化AP,我们用第二高分的类来覆盖这些槽的预测,使用相应的置信度。与过滤掉空槽相比,这样可以提高2分的AP。其他训练超参数可以在A.4节中找到。对于我们的消融实验中,我们使用了300轮的训练计划,学习率在200轮后下降了10倍。200轮后,学习率下降10倍,其中一个历时是对所有训练图像的一次传递。在16个V100 GPU上训练300个epochs的基线模型需要3天时间,每台GPU有4张图像(因此总批次大小为64)。对于用来与Faster R-CNN进行比较的较长的时间表,我们训练了500个epochs400轮后,学习率下降。与较短的计划相比,增加了1.5个AP。
transformers通常是用Adam或Adagrad优化器进行训练的,训练时间很长和dropout,这对DETR也是如此。然而,R-CNN是用SGD进行训练的,数据量很小,而且我们还没有发现Adam或Dropout的成功应用。我们不知道Adam或dropout的成功应用。尽管存在这些差异,我们还是试图使Faster R-CNN的基线更加强大。为了与DETR保持一致,我们将广义的IoU[38]添加到箱体损失中,同样的随机增加和长时间的训练,已知可以改善结果[13]。结果在表1中列出。在上面的部分,我们显示了Faster R-CNN的结果用3倍时间表训练的模型,来自Detectron2 Model Zoo [50]。在
中间部分我们显示了相同模型的结果(带 "+"),但训练了用9倍的时间表(109轮)和所述的增强措施,总共增加了1-2个AP。在表1的最后一节,我们显示了多个DETR模型的结果。为了在参数的数量上具有可比性,我们选择了一个具有6个变压器和6个解码器的模型,宽度为256,有8个注意头。与带有FPN的Faster R-CN一样,这个模型有41.3M的参数,其中23.5M的参数是在分辨中。其中23.5M在ResNet-50中,17.8M在变换器中。尽管Faster R-CNN和DETR仍有可能通过更长时间的训练来进一步训练,我们可以得出结论,DETR可以在相同的参数数量下与Faster R-CNN竞争,在COCO值子集上达到42个AP。DETR实现这一目标的方法是提高APL(+7.8),但是要注意的是模型在APS方面仍然落后(-5.5)。DETR-DC5在相同的参数数和类似的FLOP数下的参数和类似的FLOP数有更高的AP,但在APS方面仍然明显落后。更快的R-CNN和带有ResNet-101主干的DETR显示了的结果也是相当的。
表2:编码器尺寸的影响。每一行都对应于一个具有不同数量的编码器层数和固定的解码器层数的模型,性能逐渐提高随着编码器层数的增加。
A.1.序言:多头注意力层
多头:
A.2.损失
A.3.细节架构
图10给出了在DETR中使用的transformer的详细描述,在每个注意层都有位置编码传递。来自CNN主干的图像特征通过transformer编码器,同时还有空间位置编码,这些编码被添加到每个多头自我注意层的查询和密钥中。然后,解码器接收查询(最初设置为零)。输出位置编码(对象查询)和编码器内存,通过多个多头自我注意和解码器-编码器注意产生最终的预测类标签和边界框。第一自我注意层中的第一个解码器层可以被跳过。
A.6.PyTorch 推理代码
为了证明该方法的简单性,我们在清单1中加入了推理代码,其中包括清单1中的PyTorch和Torchvision库。该代码在Python 3.6以上版本中运行。PyTorch 1.4和Torchvision 0.5。请注意,它不支持批处理,因此它只适用于推理或使用分布式数据并行的训练,每个GPU有一个图像。还请注意,为了清晰起见,这段代码在编码器中使用了学习过的位置编码,而非编码,而不是固定的编码,并且位置编码只被添加到输入中,而不是在每次输入时。而不是在每个转换层添加位置编码。做这些改变需要超越PyTorch对变换器的实现,这妨碍了可读性。重现这些实验的全部代码将在会议前公布。
import torch
from torch import nn
from torchvision.models import resnet50
class DETR(nn.Module):
def__init__(self,num_classes,hidden_dim,nheads,num_encoder_layers,num_decoder_layers):
super().__init__()
self.backbone=nn.Sequential(*list(resnet50(pretrained=True),children())[:-2])
self.conv=nn.Conv2d(2048,hidden_dim,nheads,num_encoder_layers.num_decoder_layers)
self.linear_class=nn.Linear(hidden_dim,num_class+1)
self.linear_bbox=nn.Linear(hidden_dim,4)
self.query_pos=nn.Parameter(torch.rand(100,hidden_dim))
self.row_embed=nn.Parameter(torch.rand(50,hidden_dim//2))
self.col_embed=nn.Parameter(torch.rand(50,hidden_dim//2))
def forward(self,inputs):
x=self.backbone(inputs)
h= self.conv(x)
H,W=h.shape[-2:]
pos=torch.cat([
self.col_embed[:W].unsqueeze(0).repeat(H, 1, 1),
self.row_embed[:H].unsqueeze(1).repeat(1, W, 1),
], dim=-1).flatten(0, 1).unsqueeze(1)
h = self.transformer(pos + h.flatten(2).permute(2, 0, 1),
self.query_pos.unsqueeze(1))
return self.linear_class(h), self.linear_bbox(h).sigmoid()
detr=DETR(num_classes=91,hidden_dim=256,nheads=8,num_encoder_layers=6,num_decoder_layers=6)
detr.eval()
inputs=torch.randn(1,3,800,1200)
logits,bboxs=detr(inputs)
清单1:DETR PyTorch推理代码。为了清楚起见,它在编码器中使用了学习过的位置编码,而不是固定的,并且位置编码被添加到了输入中而不是在每个变换器层中。做这些改变需要超越
PyTorch对变换器的实现,这妨碍了可读性。整个代码重现实验的全部代码将在会议前提供。
Encoder将输入序列 映射到一个连续表示序列,对于编码得到的z,Decoder每次解码生成一个符号,直到生成完整的输出序列: 。对于每一步解码,模型都是自回归的,即在生成下一个符号时将先前生成的符号作为附加输入。
Class Encoder(nn.Module)
在init函数(初始化)中搭建模型的基本框架,即:one-hot矩阵生成,位置编码信息生成,encoder内部架构的堆叠;
在forward函数(实现)中以数据流动的形式进行编写。这里的enc_outputs先是数据流动的一个载体,才是最后的输出结果(因为流到尽头就是最后的结果)
- 对于encoder,输入的形状是[batch_size * source_len]即batch*句子长度
- 把数字索引转化为对应的向量
- 将词向量层结果(即数字索引对应的矩阵)放入位置编码层,再将得到的位置编码信息,与词向量相加(即信息内容为:词向量+位置编码)位置编码层实现可见后文PositionalEncoding
- 滤过pad信息
- 用循环将encoderlayer堆叠起来,接受输入为上一层的信息和哪些是pad信息。
Class EncoderLayer(nn.Module)
在init函数(初始化)中搭建模型的基本框架,即:多头子注意力机制层,前馈神经网络层
在forward函数(实现)中以数据流动的形式进行编写。首先进入多头自注意力机制层,输入的形状是(q*k*v*pad信息)
Class MultiHeadAttention(nn.Module)
在init函数(初始化)中搭建模型的基本框架,即:3类映射矩阵Q,K,V;并且保证3类矩阵的头数相同,,层标准化LayerNorm
在forward函数(实现)中以数据流动的形式进行编写。
- 首先进行映射分头,注意q和k的维度要一致,不如无法相乘计算(即点积DotProductAttention方法计算我们的attention_scores)。
- 接下来给每个头都传输pad的信息,以便我们后续的计算有效性。
- 接下来实现点积DotProductAttention的计算attention_scores,并且依据注意力得分得到了新的权重后的矩阵。在这一步之中我们完成了pad信息的归零化。
-位置编码是一个常规参数,不参与更新
-forward函数(实现)中,执行的内容是将经过词向量的一个参数和位置编码相加(即信息整合,内容为:词向量+位置编码)
-在论文《attention is all you need》有详细解释
- 可参考【3】
class PositionalEncoding(nn.Module):
def __init__(self, dim, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
if dim % 2 != 0:
raise ValueError("Cannot use sin/cos positional encoding with "
"odd dim (got dim={:d})".format(dim))
"""
构建位置编码pe
pe公式为:
PE(pos,2i/2i+1) = sin/cos(pos/10000^{2i/d_{model}})
"""
pe = torch.zeros(max_len, dim) # max_len 是解码器生成句子的最长的长度,假设是 10
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp((torch.arange(0, dim, 2, dtype=torch.float) *
-(math.log(10000.0) / dim)))
pe[:, 0::2] = torch.sin(position.float() * div_term)
pe[:, 1::2] = torch.cos(position.float() * div_term)
pe = pe.unsqueeze(1)
self.register_buffer('pe', pe)
self.drop_out = nn.Dropout(p=dropout)
self.dim = dim
def forward(self, emb, step=None):
emb = emb * math.sqrt(self.dim)
if step is None:
emb = emb + self.pe[:emb.size(0)]
else:
emb = emb + self.pe[step]
emb = self.drop_out(emb)
return emb
import torch
from torch import nn
from torchvision.models import resnet50
class DETR(nn.Module):
def __init__(self, num_classes, hidden_dim, nheads,
num_encoder_layers, num_decoder_layers):
super().__init__()
# We take only convolutional layers from ResNet-50 model
self.backbone = nn.Sequential(*list(resnet50(pretrained=True).children())[:-2])
self.conv = nn.Conv2d(2048, hidden_dim, 1)
self.transformer = nn.Transformer(hidden_dim, nheads,
num_encoder_layers, num_decoder_layers)
self.linear_class = nn.Linear(hidden_dim, num_classes + 1)
self.linear_bbox = nn.Linear(hidden_dim, 4)
#object query
self.query_pos = nn.Parameter(torch.rand(100, hidden_dim))
#position embedding
self.row_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))
self.col_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))
def forward(self, inputs):
#inputs是[1,3,800,1200]
x = self.backbone(inputs)
#x是[1,2048,25,38]
h = self.conv(x)
#h是[1,256,25,38]
H, W = h.shape[-2:]
pos = torch.cat([
self.col_embed[:W].unsqueeze(0).repeat(H, 1, 1),
self.row_embed[:H].unsqueeze(1).repeat(1, W, 1),
], dim=-1).flatten(0, 1).unsqueeze(1)
#pos是[950,1,256]
#self.query_pos是[100,256]
#src是encoder输入,tgt是decoder输入
h = self.transformer(src = pos + h.flatten(2).permute(2, 0, 1),
tgt = self.query_pos.unsqueeze(1)
)
#h是[100,1,256]
return self.linear_class(h), self.linear_bbox(h).sigmoid()
#coco是91个类, hidden dimension是256, 多头注意力是8, encoder,decoder layer都是6
detr = DETR(num_classes=91, hidden_dim=256, nheads=8, num_encoder_layers=6, num_decoder_layers=6)
detr.eval()
inputs = torch.randn(1, 3, 800, 1200)
logits, bboxes = detr(inputs)
print(logits, bboxes)
#logits是[100,1,92]
#bboxes是[100,1,4]
pytorch下代码理解编码解码
import torch
from torch import nn
'''torch.atleast_1d(*张)
返回每个输入张量的一维视图,其维数为零。具有一个或多个维度的输入张量按原样返回。
参数
输入(张量或张量列表) –
返回
输出(张量或张量元组)'''
x = torch.randn(2)
print(x)
torch.atleast_1d(x)
x = torch.tensor(1.)
print(x)
torch.atleast_1d(x)
x = torch.tensor(0.5)
y = torch.tensor(1.)
torch.atleast_1d((x,y))
print(x,y)
#############################Transformer#######################################
'''参数
d_model – 编码器/解码器输入中预期功能的数量(默认值为 512)。
nhead – 多头注意模型中的头数(默认值为 8)。
num_encoder_layers – 编码器中子编码器层数(默认值为 6)。
num_decoder_layers – 解码器中子解码器层的数量(默认值为 6)。
dim_feedforward – 前馈网络模型的维度(默认值为 2048)。
辍学 – 辍学值(默认值为 0.1)。
激活 ― 编码器/解码器中间层的激活函数, 可以是字符串 (“relu” 或 “gelu”) 或一元可调用的。默认值:重新
custom_encoder – 自定义编码器(默认值为“无”)。
custom_decoder – 自定义解码器(默认值为“无”)。
layer_norm_eps – 层规范化组件中的 eps 值(默认值为 1e-5)。
batch_first – 如果 ,则输入和输出张量作为 (批处理、seq、特征)提供。默认值:(序列、批处理、功能)。TrueFalse
norm_first – 如果 编码器和解码器层将在其他注意和前馈操作之前执行 LayerNorms,否则将在之后执行。默认值:(之后)。TrueFalse
'''
#举例
transformer_model=nn.Transformer(nhead=16,num_encoder_layers=12)
#可改变多头的个数和编码层数,输入与输出相同
src = torch.rand((10, 32, 512))
tgt = torch.rand((20, 32, 512))
out = transformer_model(src, tgt)
print(src,tgt)
############################Encoder#######################################
#Transformer编码器是 N 个编码器层的堆栈
'''参数
encoder_layer – 转换器编码器层() 类(必需)的实例。
num_layers – 编码器中的子编码器层数(必需)。
norm – 层归一化分量(可选)。
enable_nested_tensor – 如果为 True,输入将自动转换为嵌套张量(并在输出时转换回)。这将在填充速率较高时提高变压器编码器的整体性能。默认值:(禁用)。False'''
encoder_layer = nn.TransformerEncoderLayer(d_model=512, nhead=8)
transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=6)
src = torch.rand(10, 32, 512)
out = transformer_encoder(src)
print(src.shape)
############################Decoder同理#######################################
DETR使用了一个基于二分图匹配(bipartite matching)的损失函数,这个二分图是基于ground truth和预测的bounding box进行匹配的,并根据匹配的结果计算loss来对模型进行优化。本文使用匈牙利匹配算法来得到ground truth和bounding box的最优二分制匹配方案。
匈牙利算法通俗来讲就是“A集合是选择B集合的方法”.基本的理论基础是针对Cost Ma
举例:
有三个人准备买手机,现有三个品牌梨牌,香蕉牌,栗子牌。
少女说:这三个手机品牌都可以
少男说:我只想要梨牌手机
大叔说:我只想要香蕉牌手机
那么关系图如下
想要有效的匹配的话,就需要最大匹配(Max Matching).也是希望所有人可以买到最合适的手机品牌,但是中间有很多因素,而匈牙利算法就是为了解决多目标多任务的任务分配问题,一般在多目标追踪,多目标检测应用,为求得最优分配。
行操作:删掉最小值,得到一个矩阵
列操作:删掉最小值,得到新矩阵
import itertools
import numpy as np
from numpy import random
from scipy.optimize import linear_sum_assignment
# 任务分配类
class TaskAssignment:
# 类初始化,需要输入参数有任务矩阵以及分配方式,其中分配方式有两种,全排列方法all_permutation或匈牙利方法Hungary。
def __init__(self, task_matrix, mode):
self.task_matrix = task_matrix
self.mode = mode
if mode == 'all_permutation':
self.min_cost, self.best_solution = self.all_permutation(task_matrix)
if mode == 'Hungary':
self.min_cost, self.best_solution = self.Hungary(task_matrix)
# 全排列方法
def all_permutation(self, task_matrix):
number_of_choice = len(task_matrix)
solutions = []
values = []
for each_solution in itertools.permutations(range(number_of_choice)):
each_solution = list(each_solution)
solution = []
value = 0
for i in range(len(task_matrix)):
value += task_matrix[i][each_solution[i]]
solution.append(task_matrix[i][each_solution[i]])
values.append(value)
solutions.append(solution)
min_cost = np.min(values)
best_solution = solutions[values.index(min_cost)]
return min_cost, best_solution
# 匈牙利方法
def Hungary(self, task_matrix):
b = task_matrix.copy()
# 行和列减0
for i in range(len(b)):
row_min = np.min(b[i])
for j in range(len(b[i])):
b[i][j] -= row_min
for i in range(len(b[0])):
col_min = np.min(b[:, i])
for j in range(len(b)):
b[j][i] -= col_min
line_count = 0
# 线数目小于矩阵长度时,进行循环
while (line_count < len(b)):
line_count = 0
row_zero_count = []
col_zero_count = []
for i in range(len(b)):
row_zero_count.append(np.sum(b[i] == 0))
for i in range(len(b[0])):
col_zero_count.append((np.sum(b[:, i] == 0)))
# 划线的顺序(分行或列)
line_order = []
row_or_col = []
for i in range(len(b[0]), 0, -1):
while (i in row_zero_count):
line_order.append(row_zero_count.index(i))
row_or_col.append(0)
row_zero_count[row_zero_count.index(i)] = 0
while (i in col_zero_count):
line_order.append(col_zero_count.index(i))
row_or_col.append(1)
col_zero_count[col_zero_count.index(i)] = 0
# 画线覆盖0,并得到行减最小值,列加最小值后的矩阵
delete_count_of_row = []
delete_count_of_rol = []
row_and_col = [i for i in range(len(b))]
for i in range(len(line_order)):
if row_or_col[i] == 0:
delete_count_of_row.append(line_order[i])
else:
delete_count_of_rol.append(line_order[i])
c = np.delete(b, delete_count_of_row, axis=0)
c = np.delete(c, delete_count_of_rol, axis=1)
line_count = len(delete_count_of_row) + len(delete_count_of_rol)
# 线数目等于矩阵长度时,跳出
if line_count == len(b):
break
# 判断是否画线覆盖所有0,若覆盖,进行加减操作
if 0 not in c:
row_sub = list(set(row_and_col) - set(delete_count_of_row))
min_value = np.min(c)
for i in row_sub:
b[i] = b[i] - min_value
for i in delete_count_of_rol:
b[:, i] = b[:, i] + min_value
break
row_ind, col_ind = linear_sum_assignment(b)
min_cost = task_matrix[row_ind, col_ind].sum()
best_solution = list(task_matrix[row_ind, col_ind])
return min_cost, best_solution
# 生成开销矩阵
rd = random.RandomState(10000)
task_matrix = rd.randint(0, 100, size=(5, 5))
# 用全排列方法实现任务分配
ass_by_per = TaskAssignment(task_matrix, 'all_permutation')
# 用匈牙利方法实现任务分配
ass_by_Hun = TaskAssignment(task_matrix, 'Hungary')
print('cost matrix = ', '\n', task_matrix)
print('全排列方法任务分配:')
print('min cost = ', ass_by_per.min_cost)
print('best solution = ', ass_by_per.best_solution)
print('匈牙利方法任务分配:')
print('min cost = ', ass_by_Hun.min_cost)
print('best solution = ', ass_by_Hun.best_solution)
*二分图匹配和匈牙利算法的关系:
- 数学上的解释可参考【4】
论文中调用的是torch.nn.Transformer的模块,在原有的修改了三个地方:
A.位置编码通过MHattention传递
B.在编码器末端的layerNorm层被移除了
C.解码器返回解码器所有激活层的数据
其中,移除encoder layer末端的layerNorm(正则化模块)以及在decoder layer引入辅助损失参数(Auxiliary decoding losses)属于网络层面上的微操技巧。与原来在自然语言处理中应用的transformer网络结构最大的不同还是position embedding layer。
self-attention是怎么实现的?
4.1.DETR中主要由三部分组成:backbone layers(特征提取器)->图片预处理过程、transformer layers(编码解码过程)、prediction layers(预测框及类别)。
4.2.在DETR的论文中,作者将原图分割为32*32大小的图像块,而数量不限,类似自然语言处理中句子单词的数量不限。
4.3.在DETR中,将patch image features embedding 与 position embedding 直接相加,然后导入transformer 网络层进行训练。
4.4.在一般的CNN中,后面的特征提取,依赖于前面的特征的提取大小,而在Transformer中,做到并行化,不需要受到之前的结果影响。把目标检测任务也变成一个 Set Prediction 任务,即一口气预测一个集合,而不是按照 RNN 一样一个一个预测。
4.6.不同的Transformer中用的编码方式不同
Transformer Position Embedding(PE), -sin-cos-1d
PE矩阵可以看作是两个矩阵相乘,一个矩阵是pos(/左边),另一个矩阵是i(/右边),奇数列和偶数列再分别乘sin和cos.
通过这样的PE,可以实现任意位置通过已有的位置编码线性组合表示,不要求偶数列是sin,奇数列是cos,也可以前一半是sin,后一半是cos.
import torch
# 1d绝对sin_cos编码
def create_1d_absolute_sin_cos_embedding(pos_len, dim):
assert dim % 2 == 0, "wrong dimension!"
position_emb = torch.zeros(pos_len, dim, dtype=torch.float)
# i矩阵
i_matrix = torch.arange(dim//2, dtype=torch.float)
i_matrix /= dim / 2
i_matrix = torch.pow(10000, i_matrix)
i_matrix = 1 / i_matrix
i_matrix = i_matrix.to(torch.long)
# pos矩阵
pos_vec = torch.arange(pos_len).to(torch.long)
# 矩阵相乘,pos变成列向量,i_matrix变成行向量
out = pos_vec[:, None] @ i_matrix[None, :]
# 奇/偶数列
emb_cos = torch.cos(out)
emb_sin = torch.sin(out)
# 赋值
position_emb[:, 0::2] = emb_sin
position_emb[:, 1::2] = emb_cos
return position_emb
if __name__ == '__main__':
print(create_1d_absolute_sin_cos_embedding(4, 4))
VIT PE - trainable 1d
这里的position embedding的思想类似word embedding,用一个table做embbeding。
这里的table是随机初始化的,在模型中是可学习的
实现就比较简单了,使用nn.Embedding即可。
import torch
import torch.nn as nn
def create_1d_learnable_embedding(pos_len, dim):
pos_emb = nn.Embedding(pos_len, dim)
# 初始化成全0
nn.init.constant_(pos_emb.weight, 0)
return pos_emb
Swin Transformer PE - trainable relative bias 2d
使用的是可学习的二维的相对位置编码, bias是两两patch的相对位置偏差,相对位置偏置bias加到每个head上计算相似度
bias当作索引从bias_emb_table里面查找出一个可学习向量B, B加到Q乘K的结果上,Q乘K shape是[seqL, seqL],因此B的shape是[num_head, seqL, seqL]
import torch
import torch.nn as nn
def create_2d_relative_bias_trainable_embedding(n_head, h, w, dim):
pos_emb = nn.Embedding((2*w-1)*(2*h-1), n_head)
nn.init.constant_(pos_emb.weight, 0.)
def get_2d_relative_position_index(height, width):
# m1/m2.shape = [h, w],m1所有行值相同,m2所有列数相同
m1, m2 = torch.meshgrid(torch.arange(height), torch.arange(width))
# [2, h, 2]
coords = torch.stack([m1, m2], dim=0)
# 将h和w维度拉直,[2, h*w]
coords_flatten = torch.flatten(coords, start_dim=1)
# 变成3维列向量[2, h*w, 1] 减去 3维行向量,得到坐标差值
# relative_coords_bias.shape = [2, h*w, h*w],反应两个方向任何两个点之间的差值
relative_coords_bias = coords_flatten[:, :, None] - coords_flatten[:, None, :]
# 方向差距变为正数,bias ∈ [0, 2(h - 1)]/[0, 2(w - 1)]
relative_coords_bias[0, :, :] += height - 1
relative_coords_bias[1, :, :] += width - 1
# 将两个方向转换一个方向坐标, [i, j] -> [i*cols + j]
relative_coords_bias[0, :, :] *= relative_coords_bias[1, :, :].max()+1
return relative_coords_bias.sum(0) # [h*w, h*w]
relative_pos_bias = get_2d_relative_position_index(h, w)
# 基于相对bias去Embedding中去查
bias_emb = pos_emb(relative_pos_bias.flatten()).reshape([h*w, h*w, n_head])
# 转置一下n_head,放到第0维
bias_emb = bias_emb.permute(2, 0, 1).unsqueeze(0) # [1, n_head, h*w, h*w]
return bias_emb
emb = create_2d_relative_bias_trainable_embedding(1, 2, 2, 4)
print(emb.shape)
MAE PE - sin-cos-2d
使用的2维的fixed的sine-cosine PE,没有用相对位置和layer scaling
import torch
import trainable_1d_pe
def create_2d_absolute_sin_cos_embedding(h, w, dim):
# 奇数列和偶数列sin_cos,还有h和w方向,因此维度是4的倍数
assert dim % 4 == 0, "wrong dimension"
pos_emb = torch.zeros([h*w, dim])
m1, m2 = torch.meshgrid(torch.arange(h), torch.arange(w))
# [2, h, 2]
coords = torch.stack([m1, m2], dim=0)
# 高度方向的emb
h_emb = trainable_1d_pe.create_1d_learnable_embedding(torch.flatten(coords[0]).numel(), dim // 2)
# 宽度方向的emb
w_emb = trainable_1d_pe.create_1d_learnable_embedding(torch.flatten(coords[1]).numel(), dim // 2)
# 拼接起来
pos_emb[:, :dim//2] = h_emb.weight
pos_emb[:, dim//2:] = w_emb.weight
return pos_emb
create_2d_absolute_sin_cos_embedding(2, 2, 4)
4..必读论文Attention is all you need2017;An Image is Worth 16×16 Words: Transformers for Image Recognition at Scale2020 (Vision Transformers);
【1】 深度学习之目标检测(十一)--DETR详解_木卯_THU的博客-CSDN博客_detr
【2】DETR详解 - 知乎 (zhihu.com) -可视化解释
【3】Transformer Architecture: The Positional Encoding - Amirhossein Kazemnejad's Blog
【4】 M:/Work/CLASSES/COMP572_Fall2006/Notes/Hungarian/Matching.dvi (hkust.edu.hk)