DETR论文阅读
1. 贡献概述
文章提出一种基于Transformer架构的目标检测框架,并表现出了和Faster RCNN相当的性能。利用Encoder-Decoder结构简化了检测流程,并在此基础上提出一种集合的全局损失,通过二部匹配实现唯一预测。它的流程为:给定Quary学习对象集,DETR推理学习对象与全局的上下文关系,并行输出最终预测集。其贡献可以描述为:
- 提出了一套简洁的基于Transformer架构的框架
- 并行预测解码方式
- 提出一种损失集合,用二部匹配的方式实现预测集到真实框之间的匹配
- 可迁移到全景分割任务
二分图匹配(bipartite matching):图的顶点可以分为两个集合,每条边应对的顶点分别属于这两个集合,G=[V,E],结点集合V可以分割为互不相交的子集(V1,V2)。匹配则用的匈牙利算法寻找最多边数,从而实现最大匹配。
2. 相关工作
直接集预测:
为了避免接近的框的重复预测,采用NMS来抑制这些框,Transformer的直接集合预测是无后处理的。前人的方法采用RNN或者Linear实现恒定大小的集合预测,成本很高,对于RNN这些损失函数应保持预测排序不变。匈牙利算法被用于解决预测和真值之间的二部匹配问题。保证了每种元素都有一个唯一的匹配。
Transformer:
这种基于注意力机制的网络,能够得到全局输入的序列信息。Transformer最早被用于代替序列预测,之后用于Seq2Seq任务,以及文本生成、以及语音领域、机器翻译、词表学习和语音识别。
bipartite matching:
- 使用卷积或者全卷积结合NMS
- 使用非唯一匹配(non-unique assignment)在真值和预测值之间,并使用了NMS
- 利用可学习的NMS方法与关系网络以及注意力显式地构建不同预测之间的关系
3. 网络结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LV6SWmQT-1684417021803)(C:\Users\yufeixie\AppData\Roaming\Typora\typora-user-images\image-20230518161343957.png)]
- 目标检测集预测损失
通过decoder预测一组固定大小的N个预测,其中N应明显大于图像中的典型对象数量。二分图匹配实现在预测框与真实框之间的匹配,最低的搜索成本可以建模为:
σ
^
=
a
r
g
m
i
n
∑
i
N
L
m
a
t
c
h
(
y
i
,
y
σ
(
i
)
^
)
\hat{\sigma} = argmin \sum_{i}^{N} L_{match}(y_i,\hat{y_{\sigma(i)}})
σ^=argmini∑NLmatch(yi,yσ(i)^)
在计算成本时,类别和框的预测都被考虑其中,其中每个真实框的表示可以为:(其中
c
i
c_i
ci为类别,
b
i
b_i
bi为中心坐标以及相对于原图的相对尺寸wh)
y
i
=
(
c
i
,
b
i
)
y_i = (c_i,b_i)
yi=(ci,bi)
最终的
L
m
a
t
c
h
L_{match}
Lmatch可以表示为:
L
m
a
t
c
h
(
y
i
,
y
σ
(
i
)
^
)
=
−
l
c
i
≠
⊘
p
σ
i
^
(
C
i
)
+
l
c
i
≠
⊘
L
b
o
x
(
b
i
,
b
σ
(
i
)
^
)
L_{match}(y_i,\hat{y_{\sigma(i)}}) = -l_{c_{i} \neq \oslash} \hat{p_{\sigma_{i}}}(C_i) + l_{c_{i} \neq \oslash}L_{box}(b_i,\hat{b_{\sigma(i)}})
Lmatch(yi,yσ(i)^)=−lci=⊘pσi^(Ci)+lci=⊘Lbox(bi,bσ(i)^)
-
CNN backbone+ Encoder-Decoder Transformer + FFN
CNN backbone: 用于降低输入分辨率,将[3,h,w] --> [2048,h/32,w/32]
Encoder-Decoder Transformer: 首先通过一个1* 1卷积进行降维成[d,h,w],之后再将后面两个维度合并成为[d,w * h],通过位置编码完成最终的输入,每个Encoder包含多层,每一层有多头注意力机制。在Decoder阶段主要使用可学习的object query位置编码,并且可以并行进行Encoder,在这些嵌入层上使用自注意力和编码器-解码器注意力,该模型使用它们之间的成对关系对所有对象进行全局推理,同时能够将整个图像用作上下文。
Encoder-Decoder:与普通的Transfomer结构不同,这里的二维位置编码即加入到了Encoder Input中,又加入到了Decoder的两个多头注意力中,并且Object queries也加入到了两层的注意力输入口。在实验中表明,Encoder不加任务Spatial PE也仅仅下降1.3AP。DETR在计算attention的时候没有使用masked attention,因为将特征图展开成一维以后,所有像素都可能是互相关联的,因此没必要规定mask。而src_key_padding_mask是用来将zero_pad的部分给去掉。
**全景分割头:**添加了一个掩码头,它为每个预测框预测一个二进制掩码,它将每个对象的转换器解码器的输出作为输入,并计算该嵌入的多头注意力分数在编码器的输出上,以一个小的分辨率为每个对象生成 M 个注意力热图。为了做出最终预测并增加分辨率,使用了类似 FPN 的架构。
4. 研究细节
- Encoder 每个decoder层都会进行预测,文章直接评价每一层预测的精度,随着层数增加性能越来越好。凭借基于集合的损失,DETR不再需要NMS操作。第一层中加入的NMS对于AP提升较大,后面逐渐减小。decoder的可视化大象结果图中,显示decoder中的注意力更倾向于注意细节和局部信息,比如大象的四肢或者脚。
- FFN在transformers中可以认为是1x1的卷积层,使encoder更像attention增广的卷积网络
- 两种位置编码方式:空间位置编码和输出位置编码即目标queries。输出位置编码是必须的,不可或缺的
5. 代码笔记
class DETR(nn.Module):
""" This is the DETR module that performs object detection """
def __init__(self, backbone, transformer, num_classes, num_queries, aux_loss=False):
""" Initializes the model.
Parameters:
backbone: torch module of the backbone to be used. See backbone.py
transformer: torch module of the transformer architecture. See transformer.py
num_classes: number of object classes
num_queries: number of object queries, ie detection slot. This is the maximal number of objects
DETR can detect in a single image. For COCO, we recommend 100 queries.
aux_loss: True if auxiliary decoding losses (loss at each decoder layer) are to be used.
"""
super().__init__()
self.num_queries = num_queries
self.transformer = transformer
hidden_dim = transformer.d_model
# 分类
self.class_embed = nn.Linear(hidden_dim, num_classes + 1)
# 回归
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)
# self.query_embed 类似于传统目标检测里面的anchor 这里设置了100个 [100,256]
# nn.Embedding 等价于 nn.Parameter
self.query_embed = nn.Embedding(num_queries, hidden_dim)
self.input_proj = nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size=1)
self.backbone = backbone
self.aux_loss = aux_loss # True
def forward(self, samples: NestedTensor):
""" The forward expects a NestedTensor, which consists of:
- samples.tensor: batched images, of shape [batch_size x 3 x H x W]
- samples.mask: a binary mask of shape [batch_size x H x W], containing 1 on padded pixels
It returns a dict with the following elements:
- "pred_logits": the classification logits (including no-object) for all queries.
Shape= [batch_size x num_queries x (num_classes + 1)]
- "pred_boxes": The normalized boxes coordinates for all queries, represented as
(center_x, center_y, height, width). These values are normalized in [0, 1],
relative to the size of each individual image (disregarding possible padding).
See PostProcess for information on how to retrieve the unnormalized bounding box.
- "aux_outputs": Optional, only returned when auxilary losses are activated. It is a list of
dictionnaries containing the two above keys for each decoder layer.
"""
if isinstance(samples, (list, torch.Tensor)):
samples = nested_tensor_from_tensor_list(samples)
# out: list{0: tensor=[bs,2048,19,26] + mask=[bs,19,26]} 经过backbone resnet50 block5输出的结果
# pos: list{0: [bs,256,19,26]} 位置编码
features, pos = self.backbone(samples)
# src: Tensor [bs,2048,19,26]
# mask: Tensor [bs,19,26]
src, mask = features[-1].decompose()
assert mask is not None
# 数据输入transformer进行前向传播
# self.input_proj(src) [bs,2048,19,26]->[bs,256,19,26]
# mask: False的区域是不需要进行注意力计算的
# self.query_embed.weight 类似于传统目标检测里面的anchor 这里设置了100个
# pos[-1] 位置编码 [bs, 256, 19, 26]
# hs: [6, bs, 100, 256]
hs = self.transformer(self.input_proj(src), mask, self.query_embed.weight, pos[-1])[0]
# 分类 [6个decoder, bs, 100, 256] -> [6, bs, 100, 92(类别)]
outputs_class = self.class_embed(hs)
# 回归 [6个decoder, bs, 100, 256] -> [6, bs, 100, 4]
outputs_coord = self.bbox_embed(hs).sigmoid()
out = {'pred_logits': outputs_class[-1], 'pred_boxes': outputs_coord[-1]}
if self.aux_loss: # True
out['aux_outputs'] = self._set_aux_loss(outputs_class, outputs_coord)
# dict: 3
# 0 pred_logits 分类头输出[bs, 100, 92(类别数)]
# 1 pred_boxes 回归头输出[bs, 100, 4]
# 3 aux_outputs list: 5 前5个decoder层输出 5个pred_logits[bs, 100, 92(类别数)] 和 5个pred_boxes[bs, 100, 4]
return out
@torch.jit.unused
def _set_aux_loss(self, outputs_class, outputs_coord):
# this is a workaround to make torchscript happy, as torchscript
# doesn't support dictionary with non-homogeneous values, such
# as a dict having both a Tensor and a list.
return [{'pred_logits': a, 'pred_boxes': b}
for a, b in zip(outputs_class[:-1], outputs_coord[:-1])]