目标检测:DETR详解
前言
DETR是第一篇将Transformer应用到目标检测方向的算法。DETR是一个经典的Encoder-Decoder结构的算法,它的骨干网络是一个卷积网络,Encoder和Decoder则是两个基于Transformer的结构。DETR的输出层则是一个MLP。
它使用了一个基于二部图匹配(bipartite matching)的损失函数,这个二部图是基于ground truth和预测的bounding box进行匹配的。
最终性能与Faster-RCNN持平。
DETR
Backbone
当我们利用卷积神经网络时,会有两个假设:
平移不变性:kernel 的参数在图像任何地方时一致的。
局部性: 要找某一个特征只需要在一个区域的周围检索,不需要全局观察。
而detr则是从0开始学起的,所以它的backbone采用经典的ResNet101网络对图像提取特征,为下面的Encoder获取先验知识。
流程如下:
- 假设我的图像输入为:3 * 800 * 1066 (CHW)。
- 通过CNN提取特征后,得到了 2058 * 25 * 34的feature map。
- 为了减少计算量,下采样feature得到 256 * 25 * 34。
Encoder
在这里需要把 数据转化为序列化数据,直接把hw合并,维度转化为 256 * 850.
在这里作者采用二维sin、cos的位置编码(通过实验各位置编码方法结果相差不大),具体公式本文不在展示。
Detr与Transformer相比,后者是直接在Encoder之前做 position encoder,然后在生成 qkv,然而Detr则是只对 key 与 query 编码。我认为key query 是负责取检索特征计算注意力分数,而value只负责提供对应位置的值,从而不需要位置编码。
把位置编码与feature结合的方式主要是add操作,所以我们要把位置编码的维度与feature的维度一致。其中我们的编码方式是根据feature的x、y两个方向的编码。
流程图下:
由于相应的feature map 的 H * W 为 25 * 34
- 在H方向上为每个对应点赋予 128 * 25 * 34
- 在W方向上为每个对应点赋予128 * 25 * 34
- add 成 256 * 25 * 34
- 与feature map add
- 把数据转化为序列化数据
- 用 没有position的feature生成 V,有的生成KQ,执行attention
- 通过Encoder后,feature map 与input一致,还是 256 * 850
Decoder
这里的Decoder的输入有两个:
- Encoder的输出
- object queries
首先我们说一下object queries,在代码中,它的本质实际就是一个 learnable Embedding position。这里假设 初始化100(远远大于 num_classes)个object queries,每个的维度为256(方便与encoder输出矩阵乘法),所以它的维度为 256 * 100.
这里说个番外~,为什么object queries是一个 learnable position Embedding 呢?,我们知道,初始化要先通过一个Embedding层后才能输入后面的注意力层,而这个embedding层我们可以把它理解为全连接层,权重矩阵为w,这里的w是就是代码中用来学习object query的“learnable position embedding”,代码如下:
self.query_embed = nn.Embedding(num_queries, hidden_dim)
decoder 源码:
其中 tgt 就是实质上的 object query(全0初始化), 而之前所指的 learnable position embedding 是tgt的embedding权重,它是可学习的,tgt通过learnable position embedding 编码得到真正的 object query用来 在后面的cross attention 与encoder 中的 v、k 计算attention 。
class TransformerDecoderLayer(nn.Module):
def forward_post(self, tgt, memory,
tgt_mask: Optional[Tensor] = None,
memory_mask: Optional[Tensor] = None,
tgt_key_padding_mask: Optional[Tensor] = None,
memory_key_padding_mask: Optional[Tensor] = None,
pos: Optional[Tensor] = None,
query_pos: Optional[Tensor] = None):
q = k = self.with_pos_embed(tgt, query_pos)
tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
key_padding_mask=tgt_key_padding_mask)[0]
tgt = tgt + self.dropout1(tgt2)
tgt = self.norm1(tgt)
tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt, query_pos),
key=self.with_pos_embed(memory, pos),
value=memory, attn_mask=memory_mask,
key_padding_mask=memory_key_padding_mask)[0]
tgt = tgt + self.dropout2(tgt2)
tgt = self.norm2(tgt)
tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))
tgt = tgt + self.dropout3(tgt2)
tgt = self.norm3(tgt)
return tgt
模型通过学习会把它图像分成100个区域,每个queries负责关注特定的区域。到这里你会发现:Object queries充当的其实是位置编码的作用。
通过embedding后,把它传输给multi-head self-attention层,然后这里与传统的Transformer不同,这里是直接输出所有的prediction,而 Transformer采取的是 自回归的方式从左到右依次输出。
到了每个Decoder的第2个multi-head self-attention,它的Key和Value来自Encoder的输出张量 ,其中Key值还进行位置编码。Query值一部分来自第1个Add and Norm的输出,另一部分来自Object queries,充当可学习的位置编码。