【DETRソースコード解析】 3.Transformerモジュール

序文

最近、DETRのソースコードを見ていたのですが、1週間ほど断続的に見て、主要なモデルコードを整理しました。DETRのソースコード解析をどのような形で書こうかと考えています。考慮すべき形式の 1 つは、以前に作成した YOLOv5 のようにファイルを 1 行ずつ記述すること、もう 1 つは機能モジュールに従ってソース コードを文字列化することです。長い間考えた結果、時間を節約でき、全体を理解するのにも便利なため、2 番目の方法を使用することにしました。

コードを見ると、モデル全体が関数に分解され、最終的にはすべてのモジュールが直列に接続され、半分の労力で 2 倍の結果が得られることがわかります。

私が非常に重要だと思うもう 1 つの点は、オープンソース プロジェクトのコードを取得するには、デバッグを正常に実行できるように環境を即座に構成し、train.py を解析してメイン モデルに関連するコンテンツを即座に見つけて焦点を当てる能力が必要です。一部のログ、MAP 計算、描画などのコードは完全に無視できるため、時間を大幅に節約できます。そのため、今後ソース コードを説明するときは、完全に削除します。無関係なコードについては説明を省略し、モデル、改善、損失、その他の内容に焦点を当てます。

このセクションでは主に、models/transformer.py ファイルに関係する、エンコーダとデコーダを含む DETR のトランスフォーマ部分について説明します。

Github アノテーション バージョンのソース コード: HuKai97/detr-annotations

1. トランスの全体構造

まず呼び出しインターフェイスを見てください。

def build_transformer(args):
    return Transformer(
        d_model=args.hidden_dim,
        dropout=args.dropout,
        nhead=args.nheads,
        dim_feedforward=args.dim_feedforward,
        num_encoder_layers=args.enc_layers,
        num_decoder_layers=args.dec_layers,
        normalize_before=args.pre_norm,
        return_intermediate_dec=True,
    )

Transformer クラスを直接呼び出します。

class Transformer(nn.Module):

    def __init__(self, d_model=512, nhead=8, num_encoder_layers=6,
                 num_decoder_layers=6, dim_feedforward=2048, dropout=0.1,
                 activation="relu", normalize_before=False,
                 return_intermediate_dec=False):
        super().__init__()
        """
        d_model: 编码器里面mlp(前馈神经网络  2个linear层)的hidden dim 512
        nhead: 多头注意力头数 8
        num_encoder_layers: encoder的层数 6
        num_decoder_layers: decoder的层数 6
        dim_feedforward: 前馈神经网络的维度 2048
        dropout: 0.1
        activation: 激活函数类型 relu
        normalize_before: 是否使用前置LN
        return_intermediate_dec: 是否返回decoder中间层结果  False
        """
        # 初始化一个小encoder
        encoder_layer = TransformerEncoderLayer(d_model, nhead, dim_feedforward,
                                                dropout, activation, normalize_before)
        encoder_norm = nn.LayerNorm(d_model) if normalize_before else None
        # 创建整个Encoder层  6个encoder层堆叠
        self.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm)

        # 初始化一个小decoder
        decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward,
                                                dropout, activation, normalize_before)
        decoder_norm = nn.LayerNorm(d_model)
        # 创建整个Decoder层  6个decoder层堆叠
        self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers, decoder_norm,
                                          return_intermediate=return_intermediate_dec)

        # 参数初始化
        self._reset_parameters()

        self.d_model = d_model  # 编码器里面mlp的hidden dim 512
        self.nhead = nhead      # 多头注意力头数 8

    def _reset_parameters(self):
        for p in self.parameters():
            if p.dim() > 1:
                nn.init.xavier_uniform_(p)

    def forward(self, src, mask, query_embed, pos_embed):
        """
        src: [bs,256,19,26] 图片输入backbone+1x1conv之后的特征图
        mask: [bs, 19, 26]  用于记录特征图中哪些地方是填充的(原图部分值为False,填充部分值为True)
        query_embed: [100, 256]  类似于传统目标检测里面的anchor  这里设置了100个   需要预测的目标
        pos_embed: [bs, 256, 19, 26]  位置编码
        """
        # bs  c=256  h=19  w=26
        bs, c, h, w = src.shape
        # src: [bs,256,19,26]=[bs,C,H,W] -> [494,bs,256]=[HW,bs,C]
        src = src.flatten(2).permute(2, 0, 1)
        # pos_embed: [bs, 256, 19, 26]=[bs,C,H,W] -> [494,bs,256]=[HW,bs,C]
        pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
        # query_embed: [100, 256]=[num,C] -> [100,bs,256]=[num,bs,256]
        query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)
        # mask: [bs, 19, 26]=[bs,H,W] -> [bs,494]=[bs,HW]
        mask = mask.flatten(1)

        # tgt: [100, bs, 256] 需要预测的目标query embedding 和 query_embed形状相同  且全设置为0
        #                     在每层decoder层中不断的被refine,相当于一次次的被coarse-to-fine的过程
        tgt = torch.zeros_like(query_embed)
        # memory: [494, bs, 256]=[HW, bs, 256]  Encoder输出  具有全局相关性(增强后)的特征表示
        memory = self.encoder(src, src_key_padding_mask=mask, pos=pos_embed)
        # [6, 100, bs, 256]
        # tgt:需要预测的目标 query embeding
        # memory: encoder的输出
        # pos: memory的位置编码
        # query_pos: tgt的位置编码
        hs = self.decoder(tgt, memory, memory_key_padding_mask=mask,
                          pos=pos_embed, query_pos=query_embed)
        # decoder输出 [6, 100, bs, 256] -> [6, bs, 100, 256]
        # encoder输出 [bs, 256, H, W]
        return hs.transpose(1, 2), memory.permute(1, 2, 0).view(bs, c, h, w)

このクラスを注意深く分析すると、今のところモデルの詳細は理解できませんが、モデルの主要な枠組みは定義されていることがわかります。Transformer 全体は、実際には、特徴マップ src (次元を 256 に削減)、src_key_padding_mask (特徴マップの各位置がパディングされているかどうかを記録し、パッドはアテンションを計算する必要がないかを記録します)、および TransformerEncoder への位置コード pos の入力です。 TransformerEncoder は実際には TransformerEncoderLayer で構成されており、エンコーダの出力、マスク、位置コード、クエリ コードを TransformerEncoder に入力すると、TransformerEncoder は TransformerDecoderLayer で構成されます。

したがって、Transformer の具体的な詳細を理解するために、以下では TransformerEncoder と TransformerDecoder の 2 つのモジュールに分割します。

二、トランスフォーマーエンコーダー

この部分では、_get_clones 関数を呼び出し、6 つの TransformerEncoderLayer クラスをコピーし、順伝播のために 6 つの TransformerEncoderLayer クラスを順番に入力し、特徴マップの自己注意を継続的に計算し、特徴マップを継続的に強化して、最終的に最強のものを取得します。 (ほとんどの情報) 特徴マップ出力: [h*w, bs, 256]。TransformerEncoder プロセスの特徴マップ全体の形状が一定であることは注目に値します。

class TransformerEncoder(nn.Module):

    def __init__(self, encoder_layer, num_layers, norm=None):
        super().__init__()
        # 复制num_layers=6份encoder_layer=TransformerEncoderLayer
        self.layers = _get_clones(encoder_layer, num_layers)
        # 6层TransformerEncoderLayer
        self.num_layers = num_layers
        self.norm = norm  # layer norm

    def forward(self, src,
                mask: Optional[Tensor] = None,
                src_key_padding_mask: Optional[Tensor] = None,
                pos: Optional[Tensor] = None):
        """
        src: [h*w, bs, 256]  经过Backbone输出的特征图(降维到256)
        mask: None
        src_key_padding_mask: [h*w, bs]  记录每个特征图的每个位置是否是被pad的(True无效   False有效)
        pos: [h*w, bs, 256] 每个特征图的位置编码
        """
        output = src

        # 遍历这6层TransformerEncoderLayer
        for layer in self.layers:
            output = layer(output, src_mask=mask,
                           src_key_padding_mask=src_key_padding_mask, pos=pos)

        if self.norm is not None:
            output = self.norm(output)

        # 得到最终ENCODER的输出 [h*w, bs, 256]
        return output

def _get_clones(module, N):
    return nn.ModuleList([copy.deepcopy(module) for i in range(N)])

2.1、TransformerEncoderLayer

エンコーダ構造図:
ここに画像の説明を挿入
エンコーダ層 = マルチヘッド アテンション + add&Norm + フィードフォワード + add&Norm、マルチヘッド アテンションに焦点を当てています。

class TransformerEncoderLayer(nn.Module):

    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1,
                 activation="relu", normalize_before=False):
        super().__init__()
        """
        小encoder层  结构:multi-head Attention + add&Norm + feed forward + add&Norm
        d_model: mlp 前馈神经网络的dim
        nhead: 8头注意力机制
        dim_feedforward: 前馈神经网络的维度 2048
        dropout: 0.1
        activation: 激活函数类型
        normalize_before: 是否使用先LN  False
        """
        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):
        """
        src: [494, bs, 256]  backbone输入下采样32倍后 再 压缩维度到256的特征图
        src_mask: None
        src_key_padding_mask: [bs, 494]  记录哪些位置有pad True 没意义 不需要计算attention
        pos: [494, bs, 256]  位置编码
        """
        # 数据 + 位置编码  [494, bs, 256]
        # 这也是和原版encoder不同的地方,这里每个encoder的q和k都会加上位置编码  再用q和k计算相似度  再和v加权得到更具有全局相关性(增强后)的特征表示
        # 每用一层都加上位置编码  信息不断加强  最终得到的特征全局相关性最强  原版的transformer只在输入加上位置编码  作者发现这样更好
        q = k = self.with_pos_embed(src, pos)
        # multi-head attention   [494, bs, 256]
        # q 和 k = backbone输出特征图 + 位置编码
        # v = backbone输出特征图
        # 这里对query和key增加位置编码 是因为需要在图像特征中各个位置之间计算相似度/相关性 而value作为原图像的特征 和 相关性矩阵加权,
        # 从而得到各个位置结合了全局相关性(增强后)的特征表示,所以q 和 k这种计算需要+位置编码  而v代表原图像不需要加位置编码
        # nn.MultiheadAttention: 返回两个值  第一个是自注意力层的输出  第二个是自注意力权重  这里取0
        # key_padding_mask: 记录backbone生成的特征图中哪些是原始图像pad的部分 这部分是没有意义的
        #                   计算注意力会被填充为-inf,这样最终生成注意力经过softmax时输出就趋向于0,相当于忽略不计
        # attn_mask: 是在Transformer中用来“防作弊”的,即遮住当前预测位置之后的位置,忽略这些位置,不计算与其相关的注意力权重
        #            而在encoder中通常为None 不适用  decoder中才使用
        src2 = self.self_attn(q, k, value=src, attn_mask=src_mask,
                              key_padding_mask=src_key_padding_mask)[0]
        # add + norm + feed forward + add + norm
        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

    def forward_pre(self, src,
                    src_mask: Optional[Tensor] = None,
                    src_key_padding_mask: Optional[Tensor] = None,
                    pos: Optional[Tensor] = None):
        src2 = self.norm1(src)
        q = k = self.with_pos_embed(src2, pos)
        src2 = self.self_attn(q, k, value=src2, attn_mask=src_mask,
                              key_padding_mask=src_key_padding_mask)[0]
        src = src + self.dropout1(src2)
        src2 = self.norm2(src)
        src2 = self.linear2(self.dropout(self.activation(self.linear1(src2))))
        src = src + self.dropout2(src2)
        return src

    def forward(self, src,
                src_mask: Optional[Tensor] = None,
                src_key_padding_mask: Optional[Tensor] = None,
                pos: Optional[Tensor] = None):
        if self.normalize_before:  # False
            return self.forward_pre(src, src_mask, src_key_padding_mask, pos)
        return self.forward_post(src, src_mask, src_key_padding_mask, pos)  # 默认执行

(オリジナルのトランスエンコーダーとは異なる) 重要なポイントがいくつかあります。

  1. 各エンコーダの q と k が + 位置でエンコードされるのはなぜですか? トランスフォーマーを学習した場合は、通常、トランスフォーマーの入力に位置エンコーディングを追加します。位置エンコーディングを追加しなくても、各エンコーダーの qkv は等しくなります。ここでは、最初に q と k の両方に位置エンコーディングが追加され、次に q と k を使用して類似度が計算され、最後に v で重み付けされて、よりグローバルに関連する (強化された) 特徴表現が取得されます。各層に位置コーディングが追加され、各層のグローバル情報が継続的に強化され、最終的に最も強力なグローバル特徴が得られます。
  2. q と k + 位置エンコーディングが使用されるのに、v は位置エンコーディングを追加する必要がないのはなぜですか? q と k は画像特徴の各位置間の類似性/相関を計算するために使用され、位置エンコード後に計算されたグローバル特徴の相関性が高く、v は元の画像を表すため、位置コーディングを追加する必要はありません。

三、トランスフォーマーデコーダー

Decoder の構造は Encoder の構造と似ています。また、_get_clones を使用して TransformerDecoderLayer クラスの 6 つのコピーをコピーし、その後 6 つの TransformerDecoderLayer クラスの入力を順番に転送しますが、これとは異なり、Decoder は次の出力を入力する必要があります。 6 つの TransformerDecoderLayer と、それに続く 6 つのレイヤーの出力は、一緒に損失計算に参加します。

class TransformerDecoder(nn.Module):

    def __init__(self, decoder_layer, num_layers, norm=None, return_intermediate=False):
        super().__init__()
        # 复制num_layers=decoder_layer=TransformerDecoderLayer
        self.layers = _get_clones(decoder_layer, num_layers)
        self.num_layers = num_layers   # 6
        self.norm = norm               # LN
        # 是否返回中间层 默认True  因为DETR默认6个Decoder都会返回结果,一起加入损失计算的
        # 每一层Decoder都是逐层解析,逐层加强的,所以前面层的解析效果对后面层的解析是有意义的,所以作者把前面5层的输出也加入损失计算
        self.return_intermediate = return_intermediate

    def forward(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):
        """
        tgt: [100, bs, 256] 需要预测的目标query embedding 和 query_embed形状相同  且全设置为0
                            在每层decoder层中不断的被refine,相当于一次次的被coarse-to-fine的过程
        memory: [h*w, bs, 256]  Encoder输出  具有全局相关性(增强后)的特征表示
        tgt_mask: None
        tgt_key_padding_mask: None
        memory_key_padding_mask: [bs, h*w]  记录Encoder输出特征图的每个位置是否是被pad的(True无效   False有效)
        pos: [h*w, bs, 256]                 特征图的位置编码
        query_pos: [100, bs, 256]    query embedding的位置编码  随机初始化的
        """
        output = tgt   # 初始化query embedding  全是0

        intermediate = []  # 用于存放6层decoder的输出结果

        # 遍历6层decoder
        for layer in self.layers:
            output = layer(output, memory, tgt_mask=tgt_mask,
                           memory_mask=memory_mask,
                           tgt_key_padding_mask=tgt_key_padding_mask,
                           memory_key_padding_mask=memory_key_padding_mask,
                           pos=pos, query_pos=query_pos)
            # 6层结果全部加入intermediate
            if self.return_intermediate:
                intermediate.append(self.norm(output))

        if self.norm is not None:
            output = self.norm(output)
            if self.return_intermediate:
                intermediate.pop()
                intermediate.append(output)
        # 默认执行这里
        # 最后把  6x[100,bs,256] -> [6(6层decoder输出),100,bs,256]
        if self.return_intermediate:
            return torch.stack(intermediate)

        return output.unsqueeze(0)   # 不执行

3.1、TransformerDecoderLayer

デコーダ層の構造図:
ここに画像の説明を挿入
デコーダ層 = マスクされたマルチヘッド アテンション + Add&Norm + マルチヘッド アテンション + add&Norm + フィードフォワード + add&Norm。重要なポイントは 2 つのアテンション層にあり、これら 2 つの層の原理と違いを理解することが 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):
        """
        tgt: 需要预测的目标 query embedding  负责预测物体  用于建模图像当中的物体信息  在每层decoder层中不断的被refine
             [100, bs, 256]  和 query_embed形状相同  且全设置为0
        memory: [h*w, bs, 256]  Encoder输出  具有全局相关性(增强后)的特征表示
        tgt_mask: None
        memory_mask: None
        tgt_key_padding_mask: None
        memory_key_padding_mask: [bs, h*w]  记录Encoder输出特征图的每个位置是否是被pad的(True无效   False有效)
        pos: [h*w, bs, 256]  encoder输出特征图的位置编码
        query_pos: [100, bs, 256]  query embedding/tgt的位置编码  负责建模物体与物体之间的位置关系  随机初始化的
        tgt_mask、memory_mask、tgt_key_padding_mask是防止作弊的 这里都没有使用
        """
        # 第一个self-attention的目的:找到图像中物体的信息 -> tgt
        # 第一个多头自注意力层:输入qkv都和Encoder无关  都来自于tgt/query embedding
        # 通过第一个self-attention  可以不断建模物体与物体之间的关系  可以知道图像当中哪些位置会存在物体  物体信息->tgt
        # query embedding  +  query_pos
        q = k = self.with_pos_embed(tgt, query_pos)
        # masked multi-head self-attention  计算query embedding的自注意力
        tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
                              key_padding_mask=tgt_key_padding_mask)[0]

        # add + norm
        tgt = tgt + self.dropout1(tgt2)
        tgt = self.norm1(tgt)

        # 第二个self-attention的目的:不断增强encoder的输出特征,将物体的信息不断加入encoder的输出特征中去,更好地表征了图像中的各个物体
        # 第二个多头注意力层,也叫Encoder-Decoder self attention:key和value来自Encoder层输出   Query来自Decoder层输入
        # 第二个self-attention 可以建模图像 与 物体之间的关系
        # 根据上一步得到的tgt作为query 不断的去encoder输出的特征图中去问(q和k计算相似度)  问图像当中的物体在哪里呢?
        # 问完之后再将物体的位置信息融合encoder输出的特征图中(和v做运算)  这样我得到的v的特征就有 encoder增强后特征信息 + 物体的位置信息
        # query = query embedding  +  query_pos
        # key = encoder输出特征 + 特征位置编码
        # value = encoder输出特征
        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]
        # ada + norm + Feed Forward + add + norm
        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)

        # [100, bs, 256]
        # decoder的输出是第一个self-attention输出特征 + 第二个self-attention输出特征
        # 最终的特征:知道图像中物体与物体之间的关系 + encoder增强后的图像特征 + 图像与物体之间的关系
        return tgt

    def forward_pre(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):
        tgt2 = self.norm1(tgt)
        q = k = self.with_pos_embed(tgt2, query_pos)
        tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask,
                              key_padding_mask=tgt_key_padding_mask)[0]
        tgt = tgt + self.dropout1(tgt2)
        tgt2 = self.norm2(tgt)
        tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt2, 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)
        tgt2 = self.norm3(tgt)
        tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2))))
        tgt = tgt + self.dropout3(tgt2)
        return tgt

    def forward(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):
        if self.normalize_before:
            return self.forward_pre(tgt, memory, tgt_mask, memory_mask,
                                    tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)
        return self.forward_post(tgt, memory, tgt_mask, memory_mask,
                                 tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)

デコーダの動作を要約します。

  1. エンコーダの最終出力から、画像特徴メモリの拡張バージョンと特徴の位置情報 pos を取得します。
  2. 画像内のオブジェクト情報 tgt はカスタマイズされ、すべて 0 に初期化され、画像内のオブジェクト位置情報 query_pos はランダムに初期化されます。
  3. 最初のセルフ アテンション: qk=tgt+query_pos, v=tgt, は画像内のオブジェクト間の相関を計算し、画像内のオブジェクト情報のモデル化を担当します。最後の tgt1 はオブジェクト情報の拡張バージョンです。これらの位置情報にはオブジェクト間の位置関係が含まれます。
  4. 2 番目のセルフアテンション: q=tgt+qyery_pos、k=memory+pos、v=memory、オブジェクト情報 tgt をクエリとして使用し、画像特徴メモリにアクセスして質問 (相関関係を計算)、画像内のオブジェクトに質問それはどこにある?質問が終了した後, オブジェクトの位置情報が画像特徴に統合されます (v). プロセス全体が画像特徴とオブジェクト特徴の間の関係をモデル化する責任を負います. 最終結果はより強力な画像特徴 tgt2,エンコーダの出力を含む 強化された画像機能 + オブジェクトの位置機能。
  5. 最後に、tgt1 + tgt2 = エンコーダによって出力された強化された画像特徴 + オブジェクト情報 + オブジェクトの位置情報がデコーダの出力として使用されます。

質問1
ここで定義する物体情報tgtが全て0に初期化され、物体位置情報query_posがランダムに初期化されるのはなぜなのか疑問に思う方もいるかもしれませんが、これほど複雑な意味を表現できるのでしょうか?明らかにすべて 0 に初期化されるか、ランダムに初期化されますが、モデルはそれらが何を表すかをどのようにして知るのでしょうか? これは実際には損失関数に関係しており、損失関数が定義された後、ネットワークは損失、勾配リターンを計算することによって継続的に学習し、最終的に学習された tgt と query_pos がここで表現された意味です。これは回帰損失と同じです。xywh を表すためにこれら 4 つのチャネルを定義した後、ネットワークはどのようにしてそれを認識するのでしょうか? 損失関数の勾配リターンを通じて、ネットワークは学習を続け、最終的にこれら 4 つのチャネルが xywh を表すことを認識します。

質問 2
ここでデコーダの出力として tgt1 + tgt2 を使用するのはなぜですか? tgt1 または tgt2 を単独で使用する代わりに?

  1. まず、tgt1 は画像内のオブジェクト情報 + オブジェクトの位置情報を表しますが、画像の特徴が多すぎて許容できず、最終的な予測効果は明ら​​かに良くありません (オブジェクト カテゴリの予測は確かにあまり正確ではありません)。

  2. 第二に、tgt2 + オブジェクトの位置情報で表されるエンコーダの強化版の画像特徴では、オブジェクトの情報が欠如しており、これは許容できず、最終的な予測効果は明ら​​かに良くありません (オブジェクトの位置は明らかにあまり正確ではありません)。

したがって、この 2 つの加算された特徴は、オブジェクトのカテゴリと位置を予測するためのデコーダーの出力として使用され、その効果は最高です。

参照

公式ソースコード: https://github.com/facebookresearch/detr

bステーションのソースコード解説:鉄壁の組立ライン作業員

Zhihu [Brother Buffalo]: DETR ソース コードの解釈

CSDN [頑張るリス] ソースコード解説:DETRソースコードメモ(1)

CSDN [頑張るリス] ソースコード解説:DETRソースコードメモ(2)

CVは全滅しないことを知る~【ソースコード解析対象検知越境スターDETR(1)、概要とモデル推論】

CVは消滅しないことを知る - [ソースコード解析対象検出越境スターDETR(2)、モデル学習プロセスとデータ処理]

CVは全滅しないことを知る - [ソースコード解析対象検出越境スターDETR(3)、バックボーンと位置エンコーディング]

CVは全滅しないと知りながら~【ソースコード解析対象検出 越境スターDETR(4)、Transformerによる検出】

CVは全滅しないことを知る - [ソースコード解析対象検出クロスオーバースターDETR(5)、損失関数とハンガリアンマッチングアルゴリズム]

CVは全滅しないことを知る - [ソースコード解析対象検出越境スターDETR(6)、モデル出力と予測生成]

おすすめ

転載: blog.csdn.net/qq_38253797/article/details/127616634