0
点赞
收藏
分享

微信扫一扫

DETR代码学习笔记(二)

Raow1 2022-02-26 阅读 141

        上一篇DETR代码学习笔记(一) 记录完了DETR的backbone以及数据进入encoder之前的部分,这篇开始介绍DETR的transformer部分,大体上DETR的transformer和Attention is All You Need 中提出框架基本是一致的,只是数据上有些不同。

        首先是encoder部分,整个encoder部分,从代码上可以很直观的看出他的整体流程,其中的最初的q,k是由backbone中得到的Feature Map加上位置编码得到,这个位置编码是由backbone中与Feature Map一同输出的mask生成的,如果忘了这个是怎么来的可以回去看上一篇加深印象。同样接上一篇,还是假设输入为[2,768,768],得到的特征图为[2,256,24,24],reshape并转换维度之后得到[576,2,256],此时的初始的q,k相等(Feature Map加上位置编码),而v则是Feature Map,他并没有没有加上位置编码,q,k,v的维度保持不变还是[576,2,256]

        进入自注意力层后q,k,v通过线性层进行初始化,之后reshape,由输入的[HWxNxC]->[NXnum_heads,HxW,head_dim],即[576,2,256]->[16,576,32],通过如下的公式保持最后的输出维度不变,还是[576,2,256]。

         encoder的代码:

class TransformerEncoderLayer(nn.Module):

    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1,
                 activation="relu", normalize_before=False):
        super().__init__()
        self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        # Implementation of Feedforward model
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)

        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

        self.activation = _get_activation_fn(activation)
        self.normalize_before = normalize_before

    def with_pos_embed(self, tensor, pos: Optional[Tensor]):
        return tensor if pos is None else tensor + pos

    def forward_post(self,
                     src,
                     src_mask: Optional[Tensor] = None,
                     src_key_padding_mask: Optional[Tensor] = None,
                     pos: Optional[Tensor] = None):
        # q,k由最初输入的src加上pos的位置编码构成,且q=k,shape为[576,2,256]
        # 该位置编码是由backbone中输出的mask生成
        q = k = self.with_pos_embed(src, pos)
        # 自注意力层,src2 = softmax(q*kt/sqrt(dk))*v,其中dk=32
        # 进入自注意力层后会对q,k,v进行reshape,由输入的[HWxNxC]->[NXnum_heads,HxW,head_dim],即[576,2,256]->[16,576,32]
        # 自注意力层的输出依旧是[576,2,256]
        src2 = self.self_attn(q, k, value=src, attn_mask=src_mask,
                              key_padding_mask=src_key_padding_mask)[0]
        src = src + self.dropout1(src2)
        src = self.norm1(src)
        src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))
        src = src + self.dropout2(src2)
        src = self.norm2(src)
        return src

 从代码上能比较直观的得到encoder的框架图。

         原文中encoder有6层,也就是第一层encoder的输出作为下一层encoder的输入,直到第六层最后输出的memory,这个memory将作为decoder的输入。

        接下来就是decoder部分,decoder与encoder的输入上存在差异,最初自注意力层的输入是词嵌入向量,其中q和k是一个[100,2,256]全零张量加上词嵌入向量,v则是[100,2,256]全零张量,之后进入自注意力层,这里的流程和上面encoder中是一样的。

        自注意力层的输出将作为多头注意力层中的q,而k和v来自encoder的输出,其中k还要加上位置编码。此时k和v维度为[576,2,256],q的维度为[100,2,256],同样的在计算权重时,会将各个张量进行reshape,即q:[100,2,256]->[16,100,32],k,v:[576,2,256]->[16,576,32],再利用公式计算,最后多头注意力层的输出依旧是[100,2,256]。

        decoder代码:

class TransformerDecoderLayer(nn.Module):

    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1,
                 activation="relu", normalize_before=False):
        super().__init__()
        self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        # Implementation of Feedforward model
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)

        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

        self.activation = _get_activation_fn(activation)
        self.normalize_before = normalize_before

    def with_pos_embed(self, tensor, pos: Optional[Tensor]):
        return tensor if pos is None else tensor + pos

    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由最初输入的tgt加上query_pos的词嵌入向量构成,且q=k,shape为[100,2,256]
        # 其中tgt是shape为[100,2,256]的全零输入
        q = k = self.with_pos_embed(tgt, query_pos)
        # 自注意力层,tgt2 = softmax(q*kt/sqrt(dk))*v,其中dk=32
        # 进入自注意力层后会对q,k,v进行reshape,由输入的[HWxNxC]->[NXnum_heads,HxW,head_dim],即[100,2,256]->[16,100,32]
        # 自注意力层的输出依旧是[100,2,256]
        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 = softmax(q*kt/sqrt(dk))*v,其中dk=32
        # 但是出入的shape发生变化,memory是encoder的输出,shape为[576,2,256],用它作为k,v,k还要加上位置编码
        # 多头自注意力层同样对q,k,v进行reshape,由输入的[HWxNxC]->[NXnum_heads,HxW,head_dim]
        # 即q:[100,2,256]->[16,100,32]与k,v:[576,2,256]->[16,576,32]
        # 多头注意力层的输出依旧是[100,2,256]
        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

        根据代码也比较好得到如下的框架图 

        与encoder相同,decoder也有6层,第一层decoder的输出作为下一层decoder的输入,最后一层的输出会边界框和类别分类。

 

        将以上的流程组合后得到整体的框架图。

         最后附上论文原文中的框架图

        到这里大致把transformer部分的处理过程理清,接下来是构建损失函数。有空再整理一波。

举报

相关推荐

0 条评论