社区计算机视觉课程文档
DEtection TRansformer (DETR)
并获取增强的文档体验
开始使用
DEtection TRansformer (DETR)
架构概览
DETR 主要用于目标检测任务,即检测图像中物体的过程。例如,模型的输入将是一张道路的图像,模型的输出可能是 [('car',X1,Y1,W1,H1),('pedestrian',X2,Y2,W2,H2)]
,其中 X、Y、W、H 代表 x、y 坐标,表示边界框的位置,以及框的宽度和高度。像 YOLO 这样的传统目标检测模型由手工制作的特征组成,例如锚框先验,这需要对物体位置和形状进行初步猜测,从而影响下游训练。然后使用后处理步骤来移除重叠的边界框,这需要仔细选择其过滤启发式方法。DEtection TRansformer,简称 DETR,通过在特征提取骨干网络之后使用编码器-解码器 Transformer 来简化检测器,以并行直接预测边界框,从而最大限度地减少后处理。
DETR 的模型架构从 CNN 骨干网络开始,类似于其他基于图像的网络,其输出经过处理并馈送到 Transformer 编码器,从而产生 N 个嵌入。编码器嵌入被添加到学习到的位置嵌入(称为对象查询)中,并在 Transformer 解码器中使用,生成另外 N 个嵌入。作为最后一步,N 个嵌入中的每一个都通过单独的前馈层来预测边界框的宽度、高度、坐标,以及对象类别(或是否存在对象)。
主要特点
编码器-解码器
与其他 Transformer 一样,Transformer 编码器期望 CNN 骨干网络的输出是一个序列。因此,尺寸为 [dimension, height, width]
的特征图被缩小尺寸,然后展平为 [dimension, 小于 height x width]
。 左图:可视化了特征图中 256 个维度中的 12 个维度。每个维度在缩小原始图像尺寸的同时,提取原始猫图像的一些特征。一些维度更关注猫身上的图案;一些维度更关注床单。 右图:保持原始特征维度大小为 256,宽度和高度进一步缩小尺寸并展平为大小 850。
由于 Transformer 是置换不变的,因此将位置嵌入添加到编码器和解码器中,以提醒模型嵌入来自图像的哪个位置。在编码器中,使用固定的位置编码,而在解码器中,使用学习到的位置编码(对象查询)。固定编码类似于原始 Transformer 论文中使用的编码,其中编码由不同特征维度上不同频率的正弦函数定义。它在没有任何学习参数的情况下,给出了位置感,由图像上的位置索引。学习到的编码也由位置索引,但每个位置都有一个单独的编码,该编码在整个训练过程中学习,以模型理解的方式表示位置。
基于集合的全局损失函数
在流行的目标检测模型 YOLO 中,损失函数包括边界框损失、对象性损失(即,感兴趣区域中存在对象的概率)和类别损失。损失是针对每个网格单元的多个边界框计算的,这是一个固定数字。另一方面,在 DETR 中,架构预计以置换不变的方式生成唯一的边界框(即,检测的顺序在输出中并不重要,并且边界框必须变化而不能全部相同)。因此,需要匹配来评估预测的好坏。
二分图匹配
二分图匹配是一种计算真实边界框和预测框之间一对一匹配的方法。它找到真实边界框和预测边界框以及类别之间相似度最高的匹配项。这确保了最接近的预测将与相应的真实值匹配,以便在损失函数中正确调整框和类别。如果不进行匹配,即使预测是正确的,未与真实值顺序对齐的预测也会被标记为不正确。
使用 DETR 检测物体
要查看如何使用 Hugging Face transformers 对 DETR 执行推理的示例,请参阅 DETR.ipynb
。
DETR 的演变
Deformable DETR
DETR 的两个主要问题是收敛过程漫长而缓慢,以及小物体检测效果欠佳。Deformable Attention
第一个问题通过使用可变形注意力来解决,这减少了需要关注的采样点数量。传统的注意力由于全局注意力而效率低下,并且严重限制了图像可以拥有的分辨率。该模型仅关注每个参考点周围固定数量的采样点,并且参考点由模型根据输入学习。例如,在狗的图像中,参考点可能位于狗的中心,采样点靠近耳朵、嘴巴、尾巴等。
多尺度可变形注意力模块
第二个问题类似于 YOLOv3 的解决方法,其中引入了多尺度特征图。在卷积神经网络中,较早的层提取较小的细节(例如线条),而较后的层提取较大的细节(例如车轮、耳朵)。以类似的方式,不同层的可变形注意力会导致不同级别的分辨率。通过将编码器中某些层的输出连接到解码器,可以使模型检测多种尺寸的对象。
Conditional DETR
Conditional DETR 也旨在解决原始 DETR 中训练收敛速度慢的问题,从而使收敛速度提高 6.7 倍以上。作者发现对象查询是通用的,并非特定于输入图像。通过在解码器中使用 Conditional Cross-Attention,查询可以更好地定位用于边界框回归的区域。 左图:DETR 解码器层。右图:Deformable DETR 解码器层
上图比较了原始 DETR 和 Deformable DETR 解码器层,主要区别在于交叉注意力模块的查询输入。作者区分了内容查询 cq(解码器自注意力输出)和空间查询 pq。原始 DETR 只是将它们加在一起。在 Deformable DETR 中,它们被连接在一起,其中 cq 关注对象的内容,pq 关注边界框区域。
空间查询 pq 是解码器嵌入和对象查询投影到同一空间(分别变为 T 和 ps)并相乘的结果。前几层的解码器嵌入包含边界框区域的信息,对象查询包含每个边界框的学习参考点的信息。因此,它们的投影组合成一种表示形式,允许交叉注意力衡量它们与编码器输入和正弦位置嵌入的相似性。这比仅使用对象查询和固定参考点的 DETR 更有效。
DETR 推理
您可以使用 Hugging Face Hub 上现有的 DETR 模型进行推理,如下所示
from transformers import DetrImageProcessor, DetrForObjectDetection
import torch
from PIL import Image
import requests
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
# initialize the model
processor = DetrImageProcessor.from_pretrained(
"facebook/detr-resnet-101", revision="no_timm"
)
model = DetrForObjectDetection.from_pretrained(
"facebook/detr-resnet-101", revision="no_timm"
)
# preprocess the inputs and infer
inputs = processor(images=image, return_tensors="pt")
outputs = model(**inputs)
# convert outputs (bounding boxes and class logits) to COCO API
# non max supression above 0.9
target_sizes = torch.tensor([image.size[::-1]])
results = processor.post_process_object_detection(
outputs, target_sizes=target_sizes, threshold=0.9
)[0]
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
box = [round(i, 2) for i in box.tolist()]
print(
f"Detected {model.config.id2label[label.item()]} with confidence "
f"{round(score.item(), 3)} at location {box}"
)
输出如下。
Detected cat with confidence 0.998 at location [344.06, 24.85, 640.34, 373.74]
Detected remote with confidence 0.997 at location [328.13, 75.93, 372.81, 187.66]
Detected remote with confidence 0.997 at location [39.34, 70.13, 175.56, 118.78]
Detected cat with confidence 0.998 at location [15.36, 51.75, 316.89, 471.16]
Detected couch with confidence 0.995 at location [-0.19, 0.71, 639.73, 474.17]
DETR 的 PyTorch 实现
下面显示了原始论文中 DETR 的实现
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, 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)
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()
逐行分析 forward 函数:
骨干网络
输入图像首先通过 ResNet 骨干网络,然后通过卷积层,将维度降低到 hidden_dim
。
x = self.backbone(inputs) h = self.conv(x)
它们在 __init__
函数中声明。
self.backbone = nn.Sequential(*list(resnet50(pretrained=True).children())[:-2])
self.conv = nn.Conv2d(2048, hidden_dim, 1)
位置嵌入
虽然在论文中,编码器和解码器分别使用了固定和训练的嵌入,但作者在实现中为了简单起见,对两者都使用了训练的嵌入。
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)
)
它们在此处声明为 nn.Parameter
。行和列嵌入组合起来表示图像中的位置。
self.row_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))
self.col_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))
调整大小
在进入 Transformer 之前,大小为 (batch size, hidden_dim, H, W)
的特征被重塑为 (hidden_dim, batch size, H*W)
。这使它们成为 Transformer 的顺序输入。
h.flatten(2).permute(2, 0, 1)
Transformernn.Transformer
函数将第一个参数作为编码器的输入,将第二个参数作为编码器的输入。如您所见,编码器接收添加到位置嵌入的调整大小的特征,而解码器接收 query_pos
,即解码器位置嵌入。
h = self.transformer(pos + h.flatten(2).permute(2, 0, 1), self.query_pos.unsqueeze(1))
前馈网络
最后,输出(大小为 (query_pos_dim, batch size, hidden_dim)
的张量)通过两个线性层馈送。
return self.linear_class(h), self.linear_bbox(h).sigmoid()
第一个线性层预测类别。为 No Object
类添加了一个额外的类别。
self.linear_class = nn.Linear(hidden_dim, num_classes + 1)
第二个线性层预测边界框,输出大小为 4,表示 xy 坐标、高度和宽度。
self.linear_bbox = nn.Linear(hidden_dim, 4)