Schwertlilien
As a recoder: notes and ideas.

2025-3-4-为什么DETR中要使用匈牙利算法???

匈牙利算法:Kuhn, H. W. “The Hungarian Method for the Assignment Problem.” Naval Research Logistics Quarterly, pp. 83–97, https://doi.org/10.1002/nav.3800020109.

DETR:Carion, Nicolas, et al. “End-to-End Object Detection with Transformers.” Computer Vision – ECCV 2020,Lecture Notes in Computer Science, 2020, pp. 213–29, https://doi.org/10.1007/978-3-030-58452-8_13.

记录下今天讨论的问题。感想是,交流有助于理解,一些可能需要很久能想通的问题(=-=)

DETR

目标检测=集合预测

  • 新目标函数:理想情况下每个物体只生成一个框(二分图匹配)
  • 使用了Transformer,额外有learned pbject query(类似anchor)
  • 并行(无法自回归),实效性高
image-20250303165553232

DETR流程:先使用CNN提取特征,拉直特征,进入Transformer

Transformer作用:进一步学习全局的信息。

  • encoder:每一个点/特征与图片中其他特征进行交互。这样就能大概知道哪块是哪个物体,全局的特征有利于移除冗余的框。
  • decoder:额外输入object query限定出多少个框(有点像anchor)

e.g.比如object query=100,那么就会固定输出100个框,然后使用二分图匹配的方法,找到对应(合适)的框,比如上图中,就只找到了两个框是对应的;那么剩下的98个框都被标记为"No object"。

推理:前面步骤一致,得到100个框。但是没有匹配的过程,直接在输出的100个框这里用阈值卡输出的置信度。比如置信度>0.7的作为前景物体。

缺陷:大物体结果好(归因于使用了Transformer);小物体结果不行。Deformable DETR解决了。

相关工作:集合预测+Transformer-Parallel+目标检测

Learnable NMS等等使用人工干预。基于集合的目标函数。DETR不想用过多的先验知识。

模型架构

DETR的模型的输出,是一个固定的集合。

image-20250303165539904

假如输入图片是3x800x1066--CNN-->2048x25x34--1x1降维-->256x25x34+位置编码256x25x34

--\(\bigoplus\)-->850x256--Transformer Encoder-->850x256(作为KV)

Q:100x256+KV:800x256--Transformer Decoder-->100x256--FFN(100个)

全连接层FFN进行两种预测:出框1x4+类别预测COCO:1x91

出框+分类后,使用匈牙利算法计算loss,梯度反向回传,进行更新模型。

  • object query自注意力操作:不能省、移除冗余框。
  • 计算loss:为了计算更稳定,在每个decoder后面加了auxiliary loss。

目标函数

\[ \hat{\sigma}=\underset{\sigma\in\mathfrak{S}_N}{\arg\min}\sum_{i}^{N}\mathcal{L}_{\text{match}}(y_i,\hat{y}_{\sigma(i)}) \]

其中,\(\hat{\sigma}\)是要优化求解的一个排列,\(\mathfrak{S}_N\)表示\(N\)个元素的所有排列的集合。\(\arg\min\)表示取使得后面求和式最小的\(\sigma\)值。\(\mathcal{L}_{\text{match}}(y_i,\hat{y}_{\sigma(i)})\)是一个匹配损失函数,用于衡量真实值 \(y_i\) 和经过排列\(\sigma\)后的预测值\(\hat{y}_{\sigma(i)}\)之间的差异,\(i\)\(1\)\(N\)进行求和。 \[ \mathcal{L}_{\text{match}}(y_i,\hat{y}_{\sigma(i)})=-\mathbb{1}_{\{c_i\neq\varnothing\}}\hat{p}_{\sigma(i)}(c_i)+\mathbb{1}_{\{c_i\neq\varnothing\}}\mathcal{L}_{\text{box}}(b_i,\hat{b}_{\sigma(i)}) \]

其中,\(\mathbb{1}_{\{c_i\neq\varnothing\}}\)是指示函数,当条件\(c_i\neq\varnothing\)成立时,函数值为\(1\),否则为\(0\)\(\hat{p}_{\sigma(i)}(c_i)\) 是与类别相关的预测概率,\(\mathcal{L}_{\text{box}}(b_i,\hat{b}_{\sigma(i)})\)是用于衡量边界框 \(b_i\)(真实边界框)和\(\hat{b}_{\sigma(i)}\)(预测边界框)之间差异的损失函数。

\[ \mathcal{L}_{\text{Hungarian}}(y,\hat{y})=\sum_{i = 1}^{N}\left[-\log\hat{p}_{\hat{\sigma}(i)}(c_i)+\mathbb{1}_{\{c_i\neq\varnothing\}}\mathcal{L}_{\text{box}}(b_i,\hat{b}_{\hat{\sigma}(i)})\right] \] 这是我们的真·目标函数。\(\mathcal{L}_{\text{Hungarian}}(y,\hat{y})\)表示匈牙利匹配损失函数,\(y\)\(\hat{y}\)分别代表真实值和预测值集合。对\(i\)\(1\)\(N\)进行求和,每一项中\(-\log\hat{p}_{\hat{\sigma}(i)}(c_i)\) 可能是对类别预测概率取负对数,\(\mathbb{1}_{\{c_i\neq\varnothing\}}\mathcal{L}_{\text{box}}(b_i,\hat{b}_{\hat{\sigma}(i)})\)含义同第二个公式中对应部分,即根据条件决定是否计算边界框损失 。

代码

image-20250305105008791
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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__()
# 使用预训练的ResNet50作为主干网络backbone,取其前若干层
self.backbone = nn.Sequential(*list(resnet50(pretrained=True).children())[:-2])
# 调整特征维度的卷积层--投射层--1x1降维
self.conv = nn.Conv2d(2048, hidden_dim, 1)
# Transformer模块
self.transformer = nn.Transformer(hidden_dim, nheads,
num_encoder_layers, num_decoder_layers)
# FFN1:类别预测,输出类别数 + 1(包含背景类)
self.linear_class = nn.Linear(hidden_dim, num_classes + 1)
# FFN2:边界框预测,输出4个坐标值(x,y,w,h)
self.linear_bbox = nn.Linear(hidden_dim, 4)

# 用于查询的位置编码参数
self.query_pos = nn.Parameter(torch.rand(100, hidden_dim))
# 列方向的位置编码参数
self.col_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))
# 行方向的位置编码参数
self.row_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)
# 将特征输入Transformer模块处理--100x256
h = self.transformer(h.flatten(2).permute(2, 0, 1), self.query_pos.unsqueeze(1))
# 预测类别得分和边界框坐标
return self.linear_class(h), self.linear_bbox(h).sigmoid()


# 创建模型实例--COCO:91类,隐含层:256,多头自注意力:8个,Encoder/Decoder=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)

探讨:为什么DETR中要使用匈牙利算法???

  • 这个问题背景下如何与图论挂钩、匹配上匈牙利算法?
  • 还能使用其他的算法完成吗?

仅给出个人意见:

DETR论文中做目标检测时,将神经网络输出的预测框与实际真实框的匹配视为二分图匹配问题。比如,预测框固定100个,一张图中真实框数量不定且远少于100个(\(\ll 100\))。将预测图和真实图各作为一类,以此构建二分图。

二分图最大匹配:在二分图中,行列代表两个类别的结点,将计算出的loss值作为边的权重填入,边表示两个图(框)之间的匹配度(相似度)。该问题转化为二分图最大权匹配问题,即要在二分图中找到一组匹配,使得这组匹配中所有边的权值之和最大,且匹配的边不共顶点。

一开始我以为可以用贪心算法(排序后从大到小挑边且不共顶点)或sort解决,但发现这样会落入局部最优、可能找不到全局最优。

然后又想,这是不是能用动态规划做?但是经过思考,这个不具有最优子结构的问题,所以不能动态规划做。

这就是一个单纯的图论的问题+有一定的限制条件(loss作为边权、每个任务匹配一个员工),所以是最大流问题/二分图最大匹配。所以除了匈牙利算法之外,能够解决最大流问题的算法都能使用,只是作者选择了匈牙利算法。

一些原纪录:

image-20250305115722074image-20250305115827149image-20250305115917639


image-20250305120045487image-20250305120111338image-20250305120553647


image-20250305120215016image-20250305120237240image-20250305120434315


image-20250305120652922image-20250305120706190image-20250305120721068


image-20250305120755820image-20250305120809804image-20250305120840052


image-20250305120912456image-20250305120934596image-20250305120953395


image-20250305121021574image-20250308153352813image-20250305121109667


image-20250305121122707

搜索
匹配结果数:
未搜索到匹配的文章。