詳細な理解 (学習ノート) | DETR (Transformer のターゲット検出フレームワークの統合) DETR エントリの解釈と Transformer の実際の実装

I. 概要

DETR、正式名 DEtection TRansformer は、Facebook によって提案され、ECCV2020 で公開された Transformer ベースのエンドツーエンドのターゲット検出ネットワークです。

原文:
リンク
ソースコード:
リンク

DETR エンドツーエンドのターゲット検出ネットワーク モデルは、検出パイプラインの中心的な構成要素としてTransformerを統合することに成功した最初のターゲット検出フレームワーク モデルです。Transformers のエンドツーエンドのターゲット検出に基づいて、NMS の後処理ステップはなく、実際の実装ではアンカーを使用せず、Faster RCNN と比較して後者を上回っています。

COCO データセットの比較効果図は次のとおりです。

画像は原文より引用
上の図からわかるように、DETRの効果は非常に優れています。ResNet50に基づくDETR は、さまざまな微調整を経て、 Faster-RCNNと同等の効果を達成しました。同時に、DETR は大きなターゲットの検出では最高のパフォーマンスを示しますが、小さなターゲットでは若干劣っており、一致ベースの損失により学習の収束が困難になります (つまり、最適な状況を学習するのが困難になります)。Deformable DETRの登場により、これら 2 つの問題は比較的良好に改善されました。

Detection Transformer ( DETR)の新しいフレームワークの主な構成要素は、2 部マッチングとトランスフォーマー エンコーダ/デコーダ アーキテクチャを通じて独自の予測を強制するアンサンブル ベースのグローバル損失関数です。学習されたオブジェクト クエリの固定された小さなセットが与えられると、DETR はターゲット オブジェクトとグローバル画像コンテキストの間の関係を考慮し、最終的な予測セットを並列で直接出力します。

他の多くの最新の検出器とは異なり、新しいモデルは概念的にシンプルであり、特殊なライブラリを必要としません。DETR は、困難な COCO 物体検出データセットに対して、確立され高度に最適化された Faster R-CNN ベースラインと同等の精度と実行時パフォーマンスを実現します。さらに、DETR はパノプティック セグメンテーションなどの他のタスクに簡単に転送できます。

Detection Transformer は、すべてのオブジェクトの激しい動きを予測でき、予測されたオブジェクトとグラウンド トゥルース オブジェクトの間でバイナリ マッチングを実行する損失関数設定することによってエンドツーエンドでトレーニングされますDETR は、事前知識コンポーネントをエンコードする nms など、手動で設計された複数の後処理ステップを削除することにより、検出パイプラインを簡素化します既存のほとんどの検出方法とは異なり、DETR はカスタム レイヤを必要としないため、標準の CNN およびトランスフォーマー クラスを含むフレームワークで簡単に複製できます。

2. 変圧器

原文であるTransformer は、提案されて以来広く使用されており、その核心は、AI モデルが入力の特定の部分に選択的に焦点を合わせるようにするアテンション メカニズムの重ね合わせの使用であり、これにより推論がより効率的になります。NLP の分野で目覚ましい成果を上げているだけでなく、現在では CV の分野にも悪用されています。それはまだ本質的にエンコーダ-デコーダ構造です. エンコーダとデコーダは両方ともセルフアテンションモジュールの多重重ね合わせです. エンコードとデコードの形式を通じて, 複数のマルチヘッドアテンションで重要な特徴を学習および取得することが可能です,画像の「文脈語順情報」を統合して、より適切なターゲット検出を実行するモジュール構造を次の図に示します。

ここに画像の説明を挿入
ここに画像の説明を挿入
エンコーダモジュール:
ここに画像の説明を挿入

RNN などの従来のシーケンス モデルと比較して、Transformer は主に次の点を改善しています。

  1. RNN を複数の自己注意 Self-Attention 構造の重ね合わせに変える
  2. 他のすべての要素に対するシーケンス内の任意の要素の相関を並行して計算し、コンテキスト内の相関を効率的に抽出し、複数の視点から特徴を抽出するマルチヘッド アテンション メカニズムを導入します。
  3. 位置コードは、シーケンスの前後の情報を記述するために使用され、RNN シリアル計算プロセスを置き換えます。

Transformer の pytorch 実装

この記事では、pytorch インターフェイスを使用して、 Transformerの実際の使用方法を示します。

pytorch による Transformer のカプセル化は、主に次のパラメータを含む torch.nnTransformer を通じて実現されます。

torch.nn.Transformer(d_model: int = 512,nhead: int = 8,num_encoder_layers: int = 6,num_decoder_layers: int = 6,dim_feedforward: int = 2048,dropout: float = 0.1,activation: str = 'relu',custom_encoder: Optional[Any] = None,custom_decoder: Optional[Any] = None)

このうち、
d_model は単語埋め込みのチャネル数、
n_head はマルチヘッド アテンション ヘッドの数、
num_encoder_layers と num_decoder_layers はそれぞれエンコーダとデコーダのセルフ アテンション モジュールの数に対応し、
dim_feedforward は Linear inエンコーダ-デコーダ レイヤーの次元。

nn.Transformer の forward 関数は、エンコードとデコードのプロセスを実装します。

forward(src: torch.Tensor,tgt: torch.Tensor,src_mask: Optional[torch.Tensor] = None,tgt_mask: Optional[torch.Tensor] = None,memory_mask: Optional[torch.Tensor] = None,src_key_padding_mask: Optional[torch.Tensor] = None,tgt_key_padding_mask: Optional[torch.Tensor] = None,memory_key_padding_mask: Optional[torch.Tensor] = None)→ torch.Tensor

このうち入力が必要なパラメータは src と tgt の 2 つで、それぞれエンコーダの入出力とデコーダの入出力に対応します。tgt の役割は条件制約に似ており、Decoder の第 1 層の tgt 入力は単語埋め込みベクトルであり、前の層の計算結果は第 2 層からのものです。

他のオプションのパラメータのうち、[src/tgt/memory]_mask はマスク配列であり、原文のセクション 3.1 に対応するアテンションを計算するための戦略を定義します。一般的な説明は次のとおりです。単語シーケンスでは、各単語はその前の単語の影響を受けるだけなので、単語の後ろにあるすべての位置は無視する必要があります。したがって、注意を計算するときは、単語ベクトルとその後ろにある単語の相関関係が計算されます。ベクトルは0です。(ただし、実際には、特に中国語の各単語は、その単語の特定の意味をより良く学ぶために、文脈の意味論と語順に関連している必要があります。 )

[src、tgt、memory]_key_padding_mask もマスク配列であり、src、tgt、およびメモリ内のどの位置を予約する必要があり、どの位置を無視する必要があるかを定義します。

3. DETR

DETR の考え方は、従来のターゲット検出の本質的な考え方に似ていますが、表現方法は大きく異なります。アンカーベースの方法などの従来の方法は、基本的に、事前定義された密なアンカーのカテゴリを分類し、フレーム係数を回帰します。DETR では、ターゲットの検出をセット予測問題とみなします (セットとアンカーには同様の機能があります)。Transformer は本質的にシーケンス変換機能であるため、DETR は画像シーケンスからコレクション シーケンスへの変換プロセスとみなすことができます。このコレクションは、実際には学習可能な位置エンコーディング(記事ではオブジェクト クエリまたは出力位置エンコーディング、コードではquery_embed とも呼ばれます) です。

DETRのネットワーク構成図(アルゴリズムフロー):
ここに画像の説明を挿入
DETRが使用するトランス構造:
ここに画像の説明を挿入
空間位置符号化は著者が提案した二次元空間位置符号化法であり、位置符号化は符号器自己注目符号化者の相互注目に加えられる。同時に、オブジェクトのクエリデコーダの2 つのアテンションに追加されますオリジナルの Transformer は、入力および出力の埋め込みに位置エンコーディングを追加しました。エンコーダに位置コードを追加しなくても、最終的な AP は完全な DETR より 1.3 ポイント低いだけであることを著者がアブレーション実験で指摘したことは言及する価値があります。

このコードは、PyTorch に基づいてTransformerEncoderLayerクラスとTransformerDecoderLayerクラスを書き換えます。使用される唯一の PyTorch インターフェイスは、 nn.MultiheadAttendantクラスです。ソース コードには PyTorch 1.5.0 以降が必要です。

コードコアは、models/transformer.pyおよびmodels/detr.pyにあります。

Transformer.py

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__()
        encoder_layer = TransformerEncoderLayer(d_model, nhead, dim_feedforward,
                                                dropout, activation, normalize_before)
        encoder_norm = nn.LayerNorm(d_model) if normalize_before else None
        self.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm)
        decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward,
                                                dropout, activation, normalize_before)
        decoder_norm = nn.LayerNorm(d_model)
        self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers, decoder_norm,
                                          return_intermediate=return_intermediate_dec)

    def forward(self, src, mask, query_embed, pos_embed):
        # flatten NxCxHxW to HWxNxC
        bs, c, h, w = src.shape
        src = src.flatten(2).permute(2, 0, 1)
        pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
        query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)
        mask = mask.flatten(1)

        tgt = torch.zeros_like(query_embed)
        memory = self.encoder(src, src_key_padding_mask=mask, pos=pos_embed)
        hs = self.decoder(tgt, memory, memory_key_padding_mask=mask,
                          pos=pos_embed, query_pos=query_embed)
        return hs.transpose(1, 2), memory.permute(1, 2, 0).view(bs, c, h, w)

Transformer クラスには、Encoder オブジェクトと Decoder オブジェクトが含まれています。関連クラスの実装は、transformer.py にあります。forward 関数に注目すると、入力テンソルに変換操作があります: # NxCxHxW を HWxNxC に平坦化します。

PyTorch の src と tgt の形状定義と組み合わせると、**DETR のアイデアは、バックボーン出力特徴マップのピクセルを 1 次元に拡張し、それをシーケンス長として取得することであることがわかります。バッチとチャネルの定義は変更されません。**したがって、DETR は、他のすべてのピクセルに対する特徴マップの各ピクセルの相関を計算できます。これは、CNN では受容野に依存することで実現されており、Transformer は CNN よりも広い受容範囲をキャプチャできることがわかります。

DETR は、アテンションを計算するときにマスクされたアテンションを使用しません。これは、特徴マップが一次元に拡張された後、すべてのピクセルが互いに関連している可能性があるため、マスクを指定する必要がないためです。そして、src_key_padding_maskはzero_padの部分を削除するために使用されます。

forward関数には 2 つのキー変数pos_embedquery_embedがありますここで、 pos_embed は位置エンコーディングであり、models/position_encoding.pyにあります。

位置エンコーディング.py

2 次元特徴マップの特性に従って、DETR は独自の 2 次元位置エンコーディング方法を実装します。実装コードは次のとおりです。

class PositionEmbeddingSine(nn.Module):
    """
    This is a more standard version of the position embedding, very similar to the one
    used by the Attention is all you need paper, generalized to work on images.
    """
    def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
        super().__init__()
        self.num_pos_feats = num_pos_feats
        self.temperature = temperature
        self.normalize = normalize
        if scale is not None and normalize is False:
            raise ValueError("normalize should be True if scale is passed")
        if scale is None:
            scale = 2 * math.pi
        self.scale = scale

    def forward(self, tensor_list: NestedTensor):
        x = tensor_list.tensors
        mask = tensor_list.mask
        assert mask is not None
        not_mask = ~mask
        y_embed = not_mask.cumsum(1, dtype=torch.float32)
        x_embed = not_mask.cumsum(2, dtype=torch.float32)
        if self.normalize:
            eps = 1e-6
            y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
            x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale

        dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
        dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)

        pos_x = x_embed[:, :, :, None] / dim_t
        pos_y = y_embed[:, :, :, None] / dim_t
        pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
        pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
        pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
        return pos

内部では、マスクは位置マスクの配列であり、zero_pad を通過していない画像の場合、そのマスクはすべて 0 の配列です。

コードを比較すると、DETR が 2 次元特徴マップの x 方向と y 方向の位置コードを計算し、 xに対して各次元の位置コードの長さが num_pos_feats (この値は実際には hidden_​​dim の半分) であることがわかります。またはy、奇数位置のサインを計算し、偶数位置のコサインを計算し、pos_xpos_yを連結してNHWD配列を取得し、置換(0,3,1,2) を渡すと、形状はNDHWになります。ここで、 D はhidden_​​dimと同じですこの hidden_​​dim は、Transformer の入力ベクトルの次元であり、実装の観点からは、CNN バックボーンによって出力される特徴マップの次元と等しくなければなりません。したがって、pos コードと CNN 出力フィーチャーの形状はまったく同じです。

src = src.flatten(2).permute(2, 0, 1)         
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)

CNN によって出力されたフィーチャおよびpos コードに対して平坦化および並べ替え操作を実行して、形状をPyTorch の入力形状定義に準拠するSNEに変更します。TransformerEncoder では、src と pos_embed が追加されます。コードは自分で見ることができます。

detr.py

クラスDETR

このクラスは、 DETR計算プロセス全体をカプセル化しますまず、この論文で繰り返し言及されているオブジェクト クエリとは何なのかを見てみましょう。

答えはquery_embedです。

コードでは、query_embed は実際には埋め込み配列です。

self.query_embed = nn.Embedding(num_queries, hidden_dim)

このうち、num_queries は事前定義されたターゲット クエリの数で、コードのデフォルトでは 100 です。その意味は次のとおりです。 **Encoder エンコードの特性に従って、Decoder は 100 個のクエリを 100 個のターゲットに変換します。**通常は 100 個のクエリで十分です。(超集中的なタスクを除いて) 100 個を超えるターゲットを含むことができる画像はほとんどありません。対照的に、CNN ベースの方法で予測されるアンカーの数は数万であり、計算コストは​​非常に膨大ですとても大きい。

Transformerforward関数は、query_embedと同じ形状ですべて 0 の配列targetを定義し、次に query_embed と target をTransformerDecoderLayer前方に追加します(ここでquery_embedの役割は位置エンコーディングの役割と似ています)。クエリキー;マルチヘッド アテンションのクエリとして:

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

オブジェクト クエリがデコーダによって計算された後、形状TNEの配列が出力されます。ここで、T はオブジェクト クエリのシーケンス長、つまり 100、N はバッチ サイズ、E は特徴チャネルです。

最後に、クラス予測は線形を通じて出力されボックス予測は多層パーセプトロン構造を通じて出力されます

self.class_embed = nn.Linear(hidden_dim, num_classes + 1)
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)

#forward
hs = self.transformer(self.input_proj(src), mask, self.query_embed.weight, pos[-1])[0]
outputs_class = self.class_embed(hs)
outputs_coord = self.bbox_embed(hs).sigmoid()
out = {
    
    'pred_logits': outputs_class[-1], 'pred_boxes': outputs_coord[-1]}

分類出力のチャネルは num_classes+1 で、カテゴリは 0 から始まり、背景カテゴリは num_classes です。

クラス SetCriterion

このクラスは損失の計算を担当します。

CNN ベースのメソッドは、各アンカーの予測結果を計算し、予測結果とグランド トゥルース ボックスの間のiou計算を使用してその iou が特定のしきい値より大きいアンカーを正のサンプルとして選択して、そのクラスと値を返します。ボックスデルタ同様に、DETR は各オブジェクト クエリの予測も計算しますが、DETR はボックス デルタに基づくのではなく、ボックスの 4 隅の正規化された値を直接計算します。

次に、これらのオブジェクト予測グラウンド トゥルース ボックスの間でバイナリ マッチングを実行します。DETR は、ハンガリーのアルゴリズムを使用してこの照合プロセスを完了します。

完全なフローチャート:
ここに画像の説明を挿入
N 個のターゲットがある場合、100 個のオブジェクト予測のうち N 個が N 個のグラウンド トゥルースと一致し、その他は「オブジェクトなし」と正常に一致し、これらの予測のカテゴリラベルが割り当てられます。これはnum_classesであり、予測がバックグラウンドであることを意味します。

この設計は非常に優れており、DETR のハイライトであり、特徴の 1 つでもあります。そのため、理論上、各オブジェクト クエリには一意の一致ターゲットがあり、重複は存在しないため、DETR にはnmsが必要ありません。後処理。

マッチング結果に従って損失関数損失を計算します。損失関数の計算式はここでは記載されなくなりました。

class SetCriterion(nn.Module):
    def forward(self, outputs, targets):
        """ This performs the loss computation.
        Parameters:
             outputs: dict of tensors, see the output specification of the model for the format
             targets: list of dicts, such that len(targets) == batch_size.
                      The expected keys in each dict depends on the losses applied, see each loss' doc
        """
        outputs_without_aux = {
    
    k: v for k, v in outputs.items() if k != 'aux_outputs'}

        # Retrieve the matching between the outputs of the last layer and the targets
        indices = self.matcher(outputs_without_aux, targets)

        # Compute the average number of target boxes accross all nodes, for normalization purposes
        num_boxes = sum(len(t["labels"]) for t in targets)
        num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)
        if is_dist_avail_and_initialized():
            torch.distributed.all_reduce(num_boxes)
        num_boxes = torch.clamp(num_boxes / get_world_size(), min=1).item()

        # Compute all the requested losses
        losses = {
    
    }
        for loss in self.losses:
            losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))

self.matcherを通じて、outputs_without_auxターゲットを照合しますハンガリーのアルゴリズムは、srctargetのインデックスを含むインデックス タプルを返します具体的なマッチングプロセスについては、models/matcher.py を参照してください。

分類損失

分類損失はすべての予測クロスエントロピー損失を使用します

def loss_labels(self, outputs, targets, indices, num_boxes, log=True):
    src_logits = outputs['pred_logits']

    idx = self._get_src_permutation_idx(indices)
    target_classes_o = torch.cat([t["labels"][J] for t, (_, J) in zip(targets, indices)])
    target_classes = torch.full(src_logits.shape[:2], self.num_classes,
                                    dtype=torch.int64, device=src_logits.device)
    target_classes[idx] = target_classes_o

    loss_ce = F.cross_entropy(src_logits.transpose(1, 2), target_classes, self.empty_weight)
    losses = {
    
    'loss_ce': loss_ce}

    return losses

target_classes_o は、ターゲット インデックスに従って取得され、一致するすべての真理クラスを取得し、それらをsrc インデックスに従ってtarget_classesの対応する位置に置きます。一致しない予測は、target_classesself.num_classesで埋められます関数 _get_src_permutation_idxの機能は、 srcバッチ インデックスと、インデックス タプルから対応する一致インデックスを取得することです

箱紛失

ボックス損失は、正常に一致する予測にl1 損失giou 損失を使用します。

def loss_boxes(self, outputs, targets, indices, num_boxes):
    """Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU loss
       targets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]
       The target boxes are expected in format (center_x, center_y, w, h), normalized by the image size.
    """
    assert 'pred_boxes' in outputs
    idx = self._get_src_permutation_idx(indices)
    src_boxes = outputs['pred_boxes'][idx]
    target_boxes = torch.cat([t['boxes'][i] for t, (_, i) in zip(targets, indices)], dim=0)

    loss_bbox = F.l1_loss(src_boxes, target_boxes, reduction='none')

    losses = {
    
    }
    losses['loss_bbox'] = loss_bbox.sum() / num_boxes

    loss_giou = 1 - torch.diag(box_ops.generalized_box_iou(
        box_ops.box_cxcywh_to_xyxy(src_boxes),
        box_ops.box_cxcywh_to_xyxy(target_boxes)))
    losses['loss_giou'] = loss_giou.sum() / num_boxes
    return losses

target_boxes は、 target インデックスによって取得されたすべての成功した一致の真理値箱ですsrc_boxes は、 src インデックスによって取得された成功した一致の予測であり、それらの間のl1_loss と giou lossが計算されます

パノラマセグメンテーションへの DETR の適用 (浅い外観)

Decoderの各オブジェクト embeddingマスク ヘッドを追加することで、ピクセル レベルのセグメンテーションの機能を実現できます。
マスク ヘッドはbox embedと共同でトレーニングすることも、box embedのトレーニング後にマスク ヘッドを個別にトレーニングすることもできます

DETRのアプローチは、指定されたボックス予測に基づいてインスタンスに対応するボックスセグメンテーションを予測するMask-RCNNに似ています。ここで、DETR は、マスク ヘッドによって出力されたアテンション マップをアップサンプリングし、それをバックボーンのいくつかのブランチに追加し、 FPN関数を実装してから、すべてのボックスに対応するマスク マップの太字スタイルに対してビット単位の argmax演算を実行して、最終的な結果を取得します。セグメンテーション画像。

最後に(個人的な意見です)

CV分野におけるTransformer構造の適用は、CV と NLPの2 つのコンピューターAI分野間の密接な関係、およびコンピューター分野の主要なサブ分野間の相互促進関係を示しています。しかし、意味語順に対するTransformer構造の多大な効果は、自然言語処理においてさらに顕著であり、それが大規模CVの分野に応用されているのは、 などの特徴量データにおける特別な役割によるものだと個人的には考えています。検出や画像理解などのタイミング関連のタスクで非常に良い結果を達成しました。ただし、おそらくその後の改良により、この 2 つが相互にリンクされ、より良い結果が得られるでしょう。

この記事は学習と共有のみを目的としており、記録を記録します。侵害を削除するにはご連絡ください。

おすすめ

転載: blog.csdn.net/qq_53250079/article/details/127457575