社区计算机视觉课程文档

DEtection TRansformer (DETR)

Hugging Face's logo
加入 Hugging Face 社区

并获取增强的文档体验

开始使用

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 的架构图 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,查询可以更好地定位用于边界框回归的区域。Deformable DETR 的解码器层 左图: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)

Transformer
nn.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)

参考文献

< > 在 GitHub 上更新