- 论文:《End-to-End Object Detection with Transformers》、官方代码
- 论文:《Deformable DETR: Deformable Transformers for End-to-End Object Detection》、官方代码
- 参考李沐《DETR 论文精读》、《DETR精读笔记》
DETR(DEtection TRansformer)是2020年5月发布在Arxiv上的一篇论文,可以说是近年来目标检测领域的一个里程碑式的工作。从论文题目就可以看出,DETR其最大创新点有两个:end-to-end(端到端)和 引入Transformer。
目标检测任务,一直都是比图片分类复杂很多,因为需要预测出图片中物体的位置和类别。以往的主流的目标检测方法都不是端到端的目标检测,因为:
正是因为需要很多的人工干预、先验知识(Anchor)还有NMS,所以整个检测框架非常复杂,难调参难优化,并且部署困难(NMS需要的算子普通的库不一定支持,即不是所有硬件都支持)。所以说,一个端到端的目标检测是大家一直以来梦寐以求的。
DETR
利用Transformer
这种全局建模的能力,直接把目标检测视为集合预测问题(即给定一张图像,预测图像中感兴趣物体的集合)。然后使用可学习的object query
替代了生成anchor
的机制;使用了新的目标函数,并利用二分图匹配的方式,强制模型对每个物体生只生成一个预测框,从而替代了NMS这一步(后面会细讲)。
DETR
把之前不可学习的东西(anchor、NMS)变成可学的东西,删掉了这些依赖先验知识的部分,从而得到了一个简单有效的端到端的网络。所以DETR
不需要费尽心思的设计anchor,不需要NMS后处理,也就没有那么多超参需要调,也不需要复杂的算子。
除了端到端这一点,DETR
使用了 Transformer Encoder-Decoder 的架构。相比于原始的 Transformer,DETR
是并行预测的(in parallel),即所有预测框是一起出框的。
原始
Transformer Decoder
用于自然语言处理,为了屏蔽未来信息,使用了masked self attention,所以是使用自回归的方式,一个接一个的顺序预测。
但是目标检测任务是不需要顺序的,不存在说先得预测大物体才能预测小物体,或者说预测图片右边的物体必须依赖于图片左边的物体,所以没法做自回归预测;而且并行预测明显是更快更高效的。
Transformer Encoder
中进行全局建模,进一步通过自注意力学习全局特征。Transformer Encoder
,是因为Transformer 中的自注意力机制,使得图片中的每个点(特征)都能和图片中所有其他特征做交互了,这样模型就能大致知道哪块区域是一个物体,哪块区域又是另一个物体,从而能够尽量保证每个物体只出一个预测框。所以说这种全局特征非常有利于移除冗余的框。Transformer Decoder
生成N个预测框set of box prediction(默认取N=100,也就是一张图固定生成100个预测框)。no object
)。最后和之前的目标检测算法一样,计算这两个框的分类损失和回归损失。推理时,前三步是一样的。通过decoder生成N个预测框后,设置一个置信度阈值进行过滤,得到最终的预测框。(比如设阈值=0.7,表示只输出置信度大于0.7的预测框,剩下都当做背景框)
总的来说,Transformer Encoder
全局建模,用于区分物体;Transformer Decoder
用于描绘物体边界,将物体位置补充的更完整(见4.2可视化)。
在摘要中,作者卖了一下DETR的优点:
局限性:
DETR精度只有44 AP
,比当时SOTA模型差了近10个点,但是想法特别好,解决了目标检测里面的很多痛点,所以影响还是很大的。而且其本身只是一个简单的模型,还有很多可以改进的。比如 半年后提出的Deformable-DETR
, 融入了多尺度特征,成功解决小物体检测效果不好的问题,还解决了训练慢的问题。
另外DETR
不仅是一个目标检测方法,还是一个拓展性很强的框架。其设计理论,就是适用于更多复杂的任务,使其更加的简单,甚至是使用一个框架解决所有问题。后续确实有一系列基于它的改进工作,比如Omni-DETR, up-DETR, PnP-DETR, SMAC-DETR, DAB-DETR, SAM-DETR, DN-DETR, OW-DETR, OV-DETR等等,将DETR应用在了目标追踪、视频领域的姿态预测、语义分割等多个视觉任务上。(感觉类似CLIP出来之后,有一系列基于它的工作)
这一块介绍了三部分:
所以对比以前的工作发现,能让DETR工作的好最主要的原因就是使用了Transformer。比如上面两点,都是backbone学的特征不够好,才需要使用很多人工干预,或者说模型效果性能都不好。 所以说DETR的成功,还是Transformer的成功。
DETR 模型每次输出固定个数(N=100)的预测框,如何判断哪个预测框匹配哪个GT box呢?这就涉及到二分图匹配算法。
假设现在有 3 个工人和 4 个任务,由于每个工人的特长不一样,他们完成不同任务的时间(成本)也是不一样的,那如何分配任务能够使总的成本最低呢?最直接的最暴力的方法,就是用直接遍历,找出各种排列组合中的最优组合,但这样的复杂度无疑是很高的。匈牙利算法是解决该问题的一个知名且高效的算法,能够以较低的复杂度得到唯一的最优解。
在 scipy 库中,已经封装好了匈牙利算法,只需要将成本矩阵(cost matrix)输入进去就能够得到最优的排列。在 DETR 的官方代码中,也是调用的这个函数进行匹配(from scipy.optimize import linear_sum_assignment
)。
从N个预测框中,选出与M个GT Box最匹配的预测框,也可以转化为二分图匹配问题,这里需要填入矩阵的“成本”,就是每个预测框和GT Box的损失。对于目标检测问题,损失就是分类损失和边框损失组成。即: L m a t c h = ( y i , y ^ σ ( i ) ) = − 1 { c i ≠ ∅ } p ^ σ ( i ) + 1 { c o ≠ ∅ } L b o x ( b i , b ^ σ ( i ) ) − 1 L_{match}=(y_{i},\widehat{y}_{\sigma (i)})=-\mathbb{1}_{\{c_i\ne\empty\}}\hat{p}_{\sigma(i)}+\mathbb{1}_{\{c_o\ne\empty\}}\mathcal{L}_{box}(b_i,\hat{b}_{\sigma(i)})−1 Lmatch=(yi,y σ(i))=−1{ci=∅}p^σ(i)+1{co=∅}Lbox(bi,b^σ(i))−1
所以整个步骤就是:
linear_sum_assignment
(匈牙利算法)求出最优解,即找到每个GT Box最匹配的那个预测框。但是在DETR 中,损失函数有两点小改动:
L1 loss
(预测框和真实框坐标的L1 损失)。但是L1 loss和预测框的大小有关,框越大损失越大。 DETR 中,用 Transformer 提取的全局特征对大物体比较友好,经常出一些大框,这样就不利于优化,因此作者这里还添加了一个 GIoU Loss
。其实这里使用匈牙利算法找最优匹配,和之前使用anchor/proposal这种先验知识来匹配预测框和真实框是差不多的,只不过这里的约束更强,也就是强制模型对每个物体只输出一个预测框。
关于GIOU Loss
,可以参考我之前的帖子《YOLOv1——YOLOX系列及FCOS目标检测算法详解》4.3章节CIoU Loss
,其中详细介绍了回归损失使用L1 loss、IoU Loss、GIoU Loss和CIoU Loss的优劣。
下面参考官网的一个demo,以输入尺寸3×800×1066为例进行前向过程:
[800,1066,3]→[25,34,256]
)Transformer encoder
计算自注意力([25,34,256]→[850,256]
)Transformer decoder
解码,生成预测框learned object query
,其维度为100×256。在解码时,learned object query
和全局图像特征不停地做across attention
,最终输出100×256的自注意力结果。object query
即相当于之前的anchor/proposal,是一个硬性条件,告诉模型最后只得到100个输出。然后用这100个输出接FFN得到分类损失和回归损失。
object query
准确来说是learned positional embedding
,我感觉有点就类似Group ViT中的 grouping操作。简单说如果有一些聚类的中心点,从这些中心点开始发散,把周围相似的点逐渐扩散成一个group。
Group ViT使用计算单元Grouping Block,将可学习的Group Tokens一点点的group起来,最终变成物体掩模(segmentation mask)。Group ViT结构如下图所示:
- ViT 的Linear Projection层将图片分割成patch然后映射为
Pacth embeddings
,即图中token s i 1 \mathbf{s}_i^1 si1 (维度196×384),然后和learned group token
g i 1 \mathbf{g}_i^1 gi1一起输入Transformer Layer。- 学习6层之后使用Grouping Block模块,将图像块 token 分配到各个 group token 上,合并成为更大的、更具有高层语义信息的 group,即Segment Token(维度64×384,相当于一次聚类的分配)。
- 重复上述过程:添加新的 Group tokens g i 2 \mathbf{g}_i^2 gi2(8×384),经过 3 层 Transformer Layers 的学习之后,再次经过grouping block 分配,得到 s i 3 \mathbf{s}_i^3 si3(8×384) 。
有兴趣的可以看帖子《李沐论文精读系列四:CLIP和改进工作串讲(LSeg、GroupViT、VLiD、 GLIPv1、 GLIPv2、CLIPasso)》
除此之外还有部分细节:
下面是论文中给出的简化代码,可以直接跑,只是精度会差两个点。
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) # 1×1卷积层将2048维特征降到256维
self.transformer = nn.Transformer(hidden_dim, nheads, num_encoder_layers, num_decoder_layers)
self.linear_class = nn.Linear(hidden_dim, num_classes + 1) # 类别FFN
self.linear_bbox = nn.Linear(hidden_dim, 4) # 回归FFN
self.query_pos = nn.Parameter(torch.rand(100, hidden_dim)) # object query
# 下面两个是位置编码
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, bboxes = detr(inputs)
下图展示了对于一组基准点(图中红点)的 Encoder 注意力热力图的可视化,即基准点与图像中所有其他点的自注意力分布。
可以观察到,Transformer Encoder的自注意力已经做得非常好了, 基本能够非常清晰地区分开各个物体,甚至已经有一点实例分割的 mask 图的意思了。而且在严重遮挡的情况下,也能够清楚地区分左侧的两头牛。
所以Transformer Encoder的作用,正是可以把图片中的物体清楚地区分开,再在这个基础上做分割或者检测就会简单很多,效果也更好。
通过前面的可视化,我们已经看到Encoder 学习的全局特征,基本已经能够区分开图中不同的物体。但是对于目标检测来说,大致地区分开不同的物体是不够的,我们还需要物体边界框的精确坐标,这部分就由 Decoder 来做。
下图是 将Decoder自注意力用不同的颜色可视化出来 ,比如左图中的两头大象分别由蓝色和橙色表示。右侧斑马也用三个颜色表示。
可以观察到,即使在严重遮挡的情况下,每个物体边界的注意力还能区分开来,如大象尾巴、象腿等处。而且两头象的皮肤还有斑马上的花纹都差不多,但是轮廓都分的很清楚。作者认为这是 Decoder 在区分不同物体边界的极值点(extremities),在 Encoder 能够区分开不同的物体之后,Decoder 就只需要关注物体的边界位置,解决遮挡这些问题,最终精准地预测出不同物体的边框位置。因此,Encoder-Decoder 的结构是必要的(类似U-Net)。
3. object query 的可视化
下图将 COCO2017 验证集中所有图片的预测框可视化了出来,在N=100个预测框中只取了20个。下图每一个框代表一个object query,并且每张图都根据其尺寸进行了归一化(相当于每张图都除以高宽,得到1×1大小)。
- 论文《ViLT: Vision-and-Language Transformer Without Convolution or Region Supervision》、代码
- 李沐《ViLT 论文精读》、帖子《2021: ViLT》、知乎《ViLT:最简单的多模态Transformer》
- ViLT是直接在ViT的基础上改进的,建议先了解一下ViT模型。具体可以参考我的帖子《李沐论文精读系列二:Vision Transformer、MAE、Swin-Transformer》
天下苦目标检测久矣!!!
DETR
一经出世就广受热捧,因为它可以进行端到端的目标检测,使得目标检测的框架和流程都大大简化;另外引入Transformer之后,整个检测性能也不错,所以推动着整个目标检测工作都往这个方向走。
ViLT
也是一个极其简单的视觉文本多模态的框架,其最主要贡献,就是把多模态学习框架中的目标检测,也就是论文中反复强调的Region Feature
(区域性特征)直接拿掉了。这个操作简直是大快人心,因为它极大地简化了视觉模态特征的抽取过程,大大提高了模型的推理速度,可称之为多模态领域一个里程碑式的工作。
1. 抽取视觉特征的三种方式
现有的VLP
模型(Vision-and-Language Pre-training,视觉文本多模态模型)抽取文本特征基本上都使用 pre-trained BERT
的 tokenizer来得到text embedding
,但抽取视觉特征存在着差异。往往处理视觉特征的网络越复杂,模型效果就越好,所以抽取视觉特征是现有VLP模型的瓶颈。图下图所示,获取visual embedding
的方法总共有三大类:
Region Feature
:通常采用Faster R-CNN二阶段检测器提取区域性特征,这种操作也是最贵的;比如图像经过ResNet101 backbone提取特征,再经过RPN得到一些RoI,然后使用NMS过滤冗余的RoI,最后经过RoI Head得到一些一维的向量(Region Feature),也就是一个个bounding box。
grid feature
:将CNN backbone得到的feature map,作为网格特征,大大简化了计算量比如将ResNet50最后得到的7×7特征图拉直为一个序列,或者是上一层的14×14的特征图
patch projection
:使用类似ViT模型中的patch projection
层直接得到patch embeddings,ViLT是首个这么做的,有三个原因:
patch projection
层表现很好。在ViT
论文中,其作者对比了使用CNN backbone先抽特征再使用patch projection层(ViT Hybrid混合模型)和直接使用patch projection层(ViT
)两种将图片映射成patch embedding
的方式,发现最终结果差不多,可见只用patch projection
层模型也能工作的很好。受此启发, 作者直接将ViT的patch projection
层拿过来用,替代之前的提取网络!
这三种方法都是将抽取到的visual embedding
当做一个序列,和同样长度的text embedding
序列一起输入Transformer做后续的特征融合,其性能和运行时间如下:
Region Feature
:整个运行时间900ms,其中视觉特征抽取就要885ms,处理文本特征只有15ms,浪费了太多计算资源在视觉特征的处理上,比后面处理多模态融合的时间还多,所以也不是很合理(VLP应该花费更多精力在多模态特征的融合上)。ViLT
相比Region Feature
方法性能下降了很多,但是高于grid feature
方法,而且抽取视觉特征抽取只需要0.4ms,训练时间上千倍的减少,这也是本文的最大卖点。2. 模态交互
多模态特征的融合有两种常见方式:
这两种方法的效果其实差不多,dual-stream明显更贵,参数量、计算量更多,所以作者采用了Single-stream。
2017年以来,NLP领域基本就是被transformer一统江湖了,所以VLP模型的文本处理也只能这么做,没有什么好改的。但VLP要做Vision-Language Pre-training,就必须将图像的像素,转换为带有语义性质的离散的特征,这样才可以和文本tokens匹配起来,才能在后续输入transformer时进行特征融合,这也是大家研究的重点。
ViT
提出将图片分割成一个个固定大小的patch,然后使用线性层映射为patch embedding输入网络(比如patch size=16×16时,处理后序列长度从224×224降为14×14)。但ViT
是2021年的工作,之前的工作这部分处理都是依赖于一个目标检测器。选用目标检测器来处理图像特征有很多原因:
- 目标检测是一个天然的离散化过程,其得到的
bounding box
代表一个个物体,有明确的语义信息(可类比文本中的token),而且还是离散化的。所以这种方法简单粗暴,效果也好。- 以前的
VLP
下游任务(包括VLP领域的数据集),不管是VQA(视觉问答,给定图像回答问题)、visual captioning(VC,视觉字幕,给定图片或视频,生成对应的文本描述)还是image-text-retrieval(图文检索)等等,这些任务都跟物体有非常强烈的联系,一旦检测到物体,就很可能做出正确的答案。所以选择目标检测作为多模态模型的一部分,也是很合理的。目前VLP模型的目标检测器都是在 Visual Genome数据集上预训练的,其包含1400类物体和400类属性。如果物体类别太少,就和文本token匹配不起来了,因为文本token基本是无穷无尽的。
Pixel-BERT抽取网格
使用在ImageNet上预训练好的ResNet抽取特征,将ResNet最后得到的特征图当成是一个离散的序列,然后和文本特征一起输入transformer做融合,速度就快很多。
ViLT
三大贡献
patch projection
层抽取视觉特征,极大简化了多模态学习框架,减少了运行时间和参数量ViLT
是第一个不使用卷积特征和区域性特征的同时(Without Convolution or Region Supervision),模型性能还表现的比较好的模型VLP
训练中使用了整词掩码和图像数据增强,并被证明可以明显提升模型性能。CV领域早已证明数据增强是一个很有用的trick,但在多模态领域,始终要考虑图文匹配的问题,所以一直没有使用。比如文本是“草地上有只小白兔”,对图像使用数据增强,可能就不是白色兔子和绿色的草地了,这时新生成的图文对就不是一个正确的对。
这部分相当于多模态工作的简单综述,很多介绍多模态的工作都使用了下面这张图。
首先,作者根据1)图像和文本的表达力度(参数量/计算量)是否平衡(图像和文本特征一样重要,理论上比重应该差不多);2)多模态特征怎样融合;将VLP模型归结为四类:
对于大部分视觉文本多模态任务来说,模态融合一定要做的比较好,最后的效果才会比较好,跟之前抽取的特征关系不太大,即理想框架应该是
MI > VE = TE
。
MI > VE = TE
。ViLT
模型结构如下图所示:
模态嵌入即Modal-type embedding,使用0代表文本,1代表图像。因为在
Single-stream
模型中,图文特征是直接拼在一起输入一个transformer。如果不进行标注,模型是不知道哪一块是文本,哪一块是特征,这样不利于学习。加了模态嵌入可以区分之后,模型就可以在训练时找出图文之间的关系,学习的更好。
ViLT
使用了一般VLP模型常用的目标函数,即图文匹配loss( ITM,image text matching)和 BERT的掩码学习loss(MLM,Masked Language Modeling)。另外ViLT
还使用了Word Patch Alignment(WPA)。
ITM loss
:以50%的概率将文本对应的图片随机替换成数据集中的其它图片,然后将文本CLS token对应输出使用一个FC层映射成一个二值logits,用来判断图像文本是否匹配;MLM loss
:随机mask一个文本token,然后将其重建出来。WPA
:简单理解就是将文本和图像的输出都当做一个概率分布,然后使用最优运输理论计算一下两者的距离模型总结
ViLT
模型确实很简单,如果将图片这边patch embedding也看做token embedding,那这就是一个BERT
模型;如果将文本特征拿掉,那这就是一个ViT
。
另外ViLT
还使用了whole word masking
技巧,即将整个token masked掉而不是只掩码子词,避免了只通过单词上下文就可以进行预测。比如将“giraffe”词tokenized成3个部分[“gi”, “##raf”, “##fe”],可以mask成[“gi”, “[MASK]”, “##fe”],但是前后分别为"gi"和"e"的单词本来就没多少,模型很可能只通过文本的上下文信息就预测出这个单词就是“giraffe”,导致图像信息没有利用到,图文匹配loss就失去了意义。
上面提到的c类VLP模型,需要缓存特征,即在训练前就提前抽取好视觉特征,所以在下游任务微调时没法做图像数据增强的(如果想使用图像增强,就得重新抽取,成本太高,所以直到21年都还没有人这么做)。
ViLT
是一个端到端的模型,作者在微调时直接就上了 RandAugment。考虑到需要图文匹配,作者改动了其中两处,即去掉了cutout和color inversion(前者是随机去掉图像中某一区域,后者是进行颜色变换)。
ViLT
使用四个多模态数据集进行预训练:MSCOCO、VG、SBU、GCC。这四个数据集也叫4M,因为所有图片加在一起是400万张左右。
†
表示。1. 分类任务
下面在VQAv2
和NLVR2
两个数据集上对比了 ViLT-B/32
和其它模型的性能。这两个都可以简单理解为(转化为)多模态领域的分类任务。
RandAugment
数据增强策略;+表示训练更长的时间(20万steps)ViLT-B/32
在速度和精度之间平衡的比较好作者比较了 ViLT-B/32
和其它模型在Flickr30k和MSCOCO两个检索任务上的性能
上图是Zero-Shot的结果,下图是微调的结果。
可以看到ViLT-B/32
的取舍做的不错,速度很快,不过精度还有待提高。
下面是作者做的一些消融试验:
w表示整词掩码,m是图像的完形填空后重建,论文称之为MPP、a表示数据增强
ViLT
提出了一个极简的多模态框架,成功将BERT
和ViT
应用于多模态Transformer中。ViLT-B/32
证明了不使用卷积特征或者Region Feature
,只需要一个patch projection层,模型效果也不错,但性能还是有待提高。作者提出三种改进方向: