DAB-DETR は、Deformable-DETR、Conditional-DETR、Anchor-DETR などの吸収に基づいて完成されています。その主な貢献は、クエリを x、y、w、h 思考座標の形式に初期化することです。
このブログ投稿では、主に DAB-DETR によって行われる作業をコードの観点から分析します。
DAB-DETR は主に Decoder モデルを改善します。ブロガーは主に Decoder モジュールのモデルを分析します。
位置コード化された温度値の調整
1 つ目は、position_encoding.py ファイルです。このファイルは、PositionEmbeddingSineHW
高周波位置エンコード部分の幅と高さの温度値を分離する機能を持つメソッドを再定義し、幅と高さが異なる温度値を持つようにします。このファイルは、sincos 位置エンコード方法と学習可能な位置エンコード方法も改善します。
class PositionEmbeddingSineHW(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, temperatureH=10000, temperatureW=10000, normalize=False, scale=None):
super().__init__()
self.num_pos_feats = num_pos_feats
self.temperatureH = temperatureH
self.temperatureW = temperatureW
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)
# import ipdb; ipdb.set_trace()
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_tx = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
dim_tx = self.temperatureW ** (2 * (dim_tx // 2) / self.num_pos_feats)
pos_x = x_embed[:, :, :, None] / dim_tx
dim_ty = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
dim_ty = self.temperatureH ** (2 * (dim_ty // 2) / self.num_pos_feats)
pos_y = y_embed[:, :, :, None] / dim_ty
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)
# import ipdb; ipdb.set_trace()
return pos
トランス全体のアーキテクチャ
まず、Transformer の全体的なアーキテクチャを理解しましょう。
まず、forward によって渡されるパラメーターを見てみましょう。
src: バックボーンによって抽出された特徴情報。形状は最初は torch.Size([2, 256,19,24] ) になり、 torch .Size([456, 2, 256])
マスク: 画像のマスク情報を完成させます。形状は最初 torch.Size([2, 19, 24]) で、その後 torch.Size( [2, 456] )
refpoint_embed: 参照点座標のエンコーディング、つまりobject_query、 torch.Size([300, 4])。DAB-DETR モジュール定義で初期化される Decoder モジュールで使用されます: self.refpoint_embed = nn.Embedding(num_queries, query_dim)、最初は torch.Size([300,4])、その後 refpoint_embed = refpoint_embed.unsqueeze( 1) ).repeat(1, bs, 1) は torch.Size([300, 4]) になります。
pos_embed: 位置エンコード情報。形状は最初 torch.Size([2, 256,19,24]) で、その後 torch.Size([456, 2, 256]) になります。 上記処理の実行コードは以下のとおりです
。
# flatten NxCxHxW to HWxNxC
bs, c, h, w = src.shape #初始为2,256,19,24
src = src.flatten(2).permute(2, 0, 1)#拉平:
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
refpoint_embed = refpoint_embed.unsqueeze(1).repeat(1, bs, 1)
mask = mask.flatten(1)
次に、データは Encoder モジュールに送信され、出力メモリは torch.Size([456, 2, 256]) になります。
memory = self.encoder(src, src_key_padding_mask=mask, pos=pos_embed)
次に tgt を初期化し、self.num_patterns に従ってそのパターンを判断します。ここではデフォルトは 0 です。tgt はすべて 0 に初期化され、形状は torch.Size([300, 2, 256]) で、初期デコーダ入力として使用される DETR に似ています。
num_queries = refpoint_embed.shape[0]
if self.num_patterns == 0:
tgt = torch.zeros(num_queries, bs, self.d_model, device=refpoint_embed.device)
else:
tgt = self.patterns.weight[:, None, None, :].repeat(1, self.num_queries, bs, 1).flatten(0, 1) # n_q*n_pat, bs, d_model
refpoint_embed = refpoint_embed.repeat(self.num_patterns, 1, 1) # n_q*n_pat, bs, d_model
次に、それを Decoder モジュールに送信します。
hs, references = self.decoder(tgt, memory, memory_key_padding_mask=mask,
pos=pos_embed, refpoints_unsigmoid=refpoint_embed)
return hs, references
エンコーダモジュールの構造
DAB-DETR のエンコーダ モジュールは DETR とあまり変わりません。
エンコーダーレイヤー
src_mask=None
src_key_padding_mask
: [2, 456] の形状で画像を補完します
src
。 ResNet によって抽出された特徴は 2 次元から 1 次元に変換され、形状は torch.Size([456, 2, 256]) になります
pos
。位置エンコード情報には、本来sincos位置エンコードと学習型位置エンコードの2種類があり、さらにDAB-DETRでは幅と高さをジャンプできる位置エンコード方式も提案されています。形状は
src2
セルフアテンションで得られた torch.Size([456, 2, 256]) で、その後にドロップアウト層、ノルム層が続きます。最終的な出力結果は src: torch.Size([456, 2, 256]) で、Decoder に送信されます。
q = k = self.with_pos_embed(src, pos)
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
DETR と同様、with_pos_embed
直接加算です。
def with_pos_embed(self, tensor, pos: Optional[Tensor]):
return tensor if pos is None else tensor + pos
エンコーダモジュール
Encoder は 6 つの EncoderLayer で構成されます。
class TransformerEncoder(nn.Module):
def __init__(self, encoder_layer, num_layers, norm=None, d_model=256):
super().__init__()
self.layers = _get_clones(encoder_layer, num_layers)
self.num_layers = num_layers
self.query_scale = MLP(d_model, d_model, d_model, 2)
self.norm = norm
def forward(self, src,
mask: Optional[Tensor] = None,
src_key_padding_mask: Optional[Tensor] = None,
pos: Optional[Tensor] = None):
output = src
for layer_id, layer in enumerate(self.layers):
# rescale the content and pos sim
pos_scales = self.query_scale(output)
output = layer(output, src_mask=mask,
src_key_padding_mask=src_key_padding_mask, pos=pos*pos_scales)
if self.norm is not None:
output = self.norm(output)
return output
デコーダの概要
デコーダ部分query ancor
(アンカー ボックス) では、[2, 300, 4] に初期化され、Anchor Sine Encoding
x、y、w、h がすべて 128 次元に変換され、4 は 512 次元に変換されて渡されますMLP
。 256.
位置エンコード方式は次のとおりです。合計 4 で、エンコード次元は 128 次元になります。
主な革新の 1 つは、幅と高さの調整によるアテンション メカニズムが追加されたことです。これは、アテンションが幅と高さに対してより敏感になるようにするためです。
デコーダモジュールコードの実装
まず、tgt
の値を与えますoutput
。ここでわかるように、出力結果は でoutput
、その形状は torch.Size([300, 2, 256]) です。
output = tgt
正規化されますreference_points
が、形状は torch のままです。Size([300, 2, 4])
reference_points = refpoints_unsigmoid.sigmoid()
Decoder ループに入ると、まずreference_points
高周波位置をエンコードします。つまり、すべての値を取り出し、高周波位置エンコード モジュールに入り、torch.Size([300, 2, 4]) から torch に変更します。 Size([300, 2, 512]) は、次のようにそれぞれ 128 になります。torch.Size([300, 2, 256]) に変更する
と、self.ref_point_head(MLP)
obj_center = reference_points[..., :self.query_dim] #torch.Size([300, 2, 4])
query_sine_embed = gen_sineembed_for_position(obj_center) #torch.Size([300,2,512])
query_pos = self.ref_point_head(query_sine_embed) #torch.Size([300, 2, 256])
gen_sineembed_for_position
以下のような方法:
def gen_sineembed_for_position(pos_tensor):
# n_query, bs, _ = pos_tensor.size()
# sineembed_tensor = torch.zeros(n_query, bs, 256)
scale = 2 * math.pi
dim_t = torch.arange(128, dtype=torch.float32, device=pos_tensor.device)
dim_t = 10000 ** (2 * (dim_t // 2) / 128)
x_embed = pos_tensor[:, :, 0] * scale
y_embed = pos_tensor[:, :, 1] * scale
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=3).flatten(2)
pos_y = torch.stack((pos_y[:, :, 0::2].sin(), pos_y[:, :, 1::2].cos()), dim=3).flatten(2)
if pos_tensor.size(-1) == 2:
pos = torch.cat((pos_y, pos_x), dim=2)
elif pos_tensor.size(-1) == 4:
w_embed = pos_tensor[:, :, 2] * scale
pos_w = w_embed[:, :, None] / dim_t
pos_w = torch.stack((pos_w[:, :, 0::2].sin(), pos_w[:, :, 1::2].cos()), dim=3).flatten(2)
h_embed = pos_tensor[:, :, 3] * scale
pos_h = h_embed[:, :, None] / dim_t
pos_h = torch.stack((pos_h[:, :, 0::2].sin(), pos_h[:, :, 1::2].cos()), dim=3).flatten(2)
pos = torch.cat((pos_y, pos_x, pos_w, pos_h), dim=2)
else:
raise ValueError("Unknown pos_tensor shape(-1):{}".format(pos_tensor.size(-1)))
return pos
次に、何らかの初期化が実行され、self.query_scale
出力MLP
は Decoder の前の層の出力結果と見なすことができます。
最初の 256 次元を取り出しますquery_sine_embed
。つまり、pos_transformation
x、y に (最初のレイヤーの 1) を掛けます。これは、入力次元が 256、中間層の幅が 256、出力次元が 2、隠れ層が 2 の
ref_anchor_head
MLP です。refHW_cond は torch.Size([300, 2, 2]) query_sine_embed は最初は torch.Size([300, 2, 512]) ですが、次のようにtorch.Size([300, 2, 256]) に変更すると、この文はこのコードは、最初の 256 次元を取得することを意味します。self.ref_anchor_head = MLP(d_model, d_model, 2, 2)
query_sine_embed = query_sine_embed[...,:self.d_model] * pos_transformation
if self.query_scale_type != 'fix_elewise':#执行
if layer_id == 0:#第一层时执行
pos_transformation = 1
else:
pos_transformation = self.query_scale(output) #query_scale为MLP
else:
pos_transformation = self.query_scale.weight[layer_id]
#取出 query_sine_embed的前256维,即x,y与pos_transformation相乘
query_sine_embed = query_sine_embed[...,:self.d_model] * pos_transformation
if self.modulate_hw_attn:
refHW_cond = self.ref_anchor_head(output).sigmoid() #将其送入MLP后进行归一化 torch.Size([300, 2, 2])
query_sine_embed[..., self.d_model // 2:] *= (refHW_cond[..., 0] / obj_center[..., 2]).unsqueeze(-1)
query_sine_embed[..., :self.d_model // 2] *= (refHW_cond[..., 1] / obj_center[..., 3]).unsqueeze(-1)
上記のコードは実際に次のプロセスを実行します。 この時点で PE(Xref) と PE(Yref) が乗算されていないのではなく、それが 1 に設定されているため、つまり 2 番目のコードでそれが確認できることに注意してください。 DecoderLayer のレイヤーpos_transformation = 1
。
次に、データを DecoderLayer に送信します。この時点では DecoderLayer が最初のレイヤーであることに注意してください。
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, query_sine_embed=query_sine_embed,
is_first=(layer_id == 0))
最初の層 DecoderLayer モジュール
自己注意
まず、DecoderLayer でセルフ アテンション メカニズムを計算します。
データがどのように変化するかを見てみましょう。
tgt
つまり、DecoderLayer の前の層の出力はこの時点ではすべて 0 であり、形状は torch.Size([300, 2, 256]) が最初に線形層を通過します
。 (sa_qcontent_proj = nn.Linear (d_model, d_model)) q_content
torch.Size([300, 2, 256]) として形状を取得
します。tgt
線形層を介して qkv 初期化が完了すると、tgt
すべて 0 ですが、 q、k、v はそうではありません
次に、q_pos
(高周波位置エンコーディングと MLP を通じてアンカーによって取得された xywh 情報) も線形層のsa_qpos_proj
次元を変更せずに通過します。形状は torch.Size([300, 2, 256])
で、k、v も次のように初期化されます。同じように。DETR と同様に、v には位置情報がありません。
自分なりにまとめると、Anchor Boxから変換されたquery_posは位置情報を提供し、コンテンツ情報はオール0または前のDecoderLayerの出力結果に初期化して提供され、位置情報とコンテンツ情報も追加されます。たとえば、結合します。q = q_content + q_pos
以下については、DETR とまったく同じで、q、k、v を入力するだけで操作に参加できます。
if not self.rm_self_attn_decoder:
# Apply projections here
# shape: num_queries x batch_size x 256
q_content = self.sa_qcontent_proj(tgt) # target is the input of the first decoder layer. zero by default.
q_pos = self.sa_qpos_proj(query_pos)
k_content = self.sa_kcontent_proj(tgt)
k_pos = self.sa_kpos_proj(query_pos)
v = self.sa_v_proj(tgt)
num_queries, bs, n_model = q_content.shape
hw, _, _ = k_content.shape
q = q_content + q_pos
k = k_content + k_pos
tgt2 = self.self_attn(q, k, value=v, attn_mask=tgt_mask,
key_padding_mask=tgt_key_padding_mask)[0]
#tgt2为Attention计算结果,torch.Size([300, 2, 256])
# ========== End of Self-Attention =============
tgt = tgt + self.dropout1(tgt2)
tgt = self.norm1(tgt)
最終的に self-attention の出力 tgt が得られ、その形状は torch.Size([300, 2, 256]) となり、上記のコードは下枠の部分を実行します。
cross-attention
これで計算に入力できるようになります
クロスアテンション
1 つ目はq k v
初期化プロセスで、q はセルフアテンションの出力から来ており、線形層の後、k と v はエンコーダーの出力から来ていることがわかります。メモリの次元は torch.Size([456, 2, 256])
q_content = self.ca_qcontent_proj(tgt)#torch.Size([300, 2, 256])
k_content = self.ca_kcontent_proj(memory)#torch.Size([456, 2, 256])
v = self.ca_v_proj(memory)#torch.Size([456, 2, 256])
k_pos = self.ca_kpos_proj(pos)#对K进行位置编码,pos来自于Encoder。torch.Size([456, 2, 256])
これは最初の層であるため、次の操作を実行する必要があります。つまり、最初にquery_pos
[torch.Size([300, 2, 256])] を完全に接続された層に通過させます。寸法は変更されません。q_pos
生成のプロセス、
if is_first or self.keep_query_pos:#self.keep_query_pos默认为False
q_pos = self.ca_qpos_proj(query_pos)# query_pos:torch.Size([300, 2, 256])
q = q_content + q_pos
k = k_content + k_pos
else:
q = q_content
k = k_content
次のステップは、Cross_Attention
送信された Q、K、および V の初期化プロセスです。別個のアテンション操作は外部に配置されており、元々はアテンションの内部で完了していることに注意してください。
q = q.view(num_queries, bs, self.nhead, n_model//self.nhead)# q分头:torch.Size([300, 2, 8, 32])
query_sine_embed = self.ca_qpos_sine_proj(query_sine_embed)#query_sine_embed即
query_sine_embed = query_sine_embed.view(num_queries, bs, self.nhead, n_model//self.nhead)
q = torch.cat([q, query_sine_embed], dim=3).view(num_queries, bs, n_model * 2)
#q经过拼接变为torch.Size([300, 2, 512])
k = k.view(hw, bs, self.nhead, n_model//self.nhead)#torch.Size([456, 2, 8, 32])
k_pos = k_pos.view(hw, bs, self.nhead, n_model//self.nhead)#torch.Size([456, 2, 8, 32])
k = torch.cat([k, k_pos], dim=3).view(hw, bs, n_model * 2)#torch.Size([456, 2, 512])
次にQ,K,V
、計算のために Cross_Attend に送信されます。合計すると、 q: torch.Size([300, 2, 512])、k: torch.Size([456, 2, 512])、v: torch.Size( [456、2、256])
tgt2 = self.cross_attn(query=q, key=k, value=v, attn_mask=memory_mask, key_padding_mask=memory_key_padding_mask)[0]
具体的には以下の処理を実行します:QKVの異次元化
return multi_head_attention_forward(
query, key, value, self.embed_dim, self.num_heads,
self.in_proj_weight, self.in_proj_bias,
self.bias_k, self.bias_v, self.add_zero_attn,
self.dropout, self.out_proj.weight, self.out_proj.bias,
training=self.training,
key_padding_mask=key_padding_mask, need_weights=need_weights,
attn_mask=attn_mask, out_dim=self.vdim)
Cross_attention の計算が完了すると、tgt2 のディメンションは torch.Size([300, 2, 256]) になります。ディメンションの変更については、アテンションの計算式を参照してください。
次に、一連の残りの接続の後、バッチ正規化操作によって結果が出力されますが、最終結果は依然として torch.Size([300, 2, 256]) です。
アンカー更新戦略
このモジュールは、DAB-DETR の革新ポイント、つまりアンカー更新戦略 Anchor Update でもあります。
つまり、DecoderLayer のcross_attention 計算の後、出力値は DecoderLayer の次の層に渡され、MLP ネットワークを使用して x、y、w、h、および のオフセットを取得するアンカー ポイントの更新にも使用されます。形状はトーチです。サイズ ([300, 2, 4])。それを初期化された参照点の座標に追加しますreference_points
(つまり、アンカー ボックス、形状は torch.Size([300, 2, 4]))。これはアンカー ポイント更新戦略であり、以前の DETR モデルの初期化アンカーは常に変更されません。
if self.bbox_embed is not None:
if self.bbox_embed_diff_each_layer:#是否共享参数:false
tmp = self.bbox_embed[layer_id](output)
else:
tmp = self.bbox_embed(output)#经过MLP获得output偏移量x,y,w,h torch.Size([300, 2, 4])
# import ipdb; ipdb.set_trace()
tmp[..., :self.query_dim] += inverse_sigmoid(reference_points)
new_reference_points = tmp[..., :self.query_dim].sigmoid()
if layer_id != self.num_layers - 1:
ref_points.append(new_reference_points)
reference_points = new_reference_points.detach()
if self.return_intermediate:
intermediate.append(self.norm(output))
上記のコードから、reference_points が継続的に更新されること、つまり Anchor update 戦略であることがわかります。自動
微分を達成するために、PyTorch はテンソルを含むすべての操作を追跡し、それらの勾配を計算する必要がある場合があります (つまり、require_gradは真です)。これらの操作は有向グラフとして記録されます。detach() メソッドは、勾配を必要としないことが宣言されたテンソル上に新しいビューを構築します。
上記のコードが実行するのは、次のフレーム化プロセスです。
2 層目の DecoderLayer モジュール
DecoderLayer の最初の層と比較すると、2 番目の層の構造は最初の層と同じですが、最初の層の Decoder-Embedding の初期化 tgt がすべて 0 であり、2 番目の層が の出力になる点が異なります。さらに、アンカー更新戦略により、2 番目の層のアンカー ボックスも、最初の層のアンカー ボックスに xywh のオフセットを加えたものになります。
reference_points
1 つ目は、アンカー ボックス (つまり、アンカー ボックス) の変更です。Decoderlayer の前の層を通過した後、値が更新され、再度高周波位置エンコードが行われた後、MLP 層はデータの次元を torch に変更します。サイズ([300, 2, 256])
obj_center = reference_points[..., :self.query_dim]
query_sine_embed = gen_sineembed_for_position(obj_center)
query_pos = self.ref_point_head(query_sine_embed)
その直後の差分がここで強調表示されています まず、このときのquery_scale_type
変更点cond_elewise
ですが、2層目なので出力(つまり前の層の出力結果)が通ります。
self.query_scale = MLP(d_model, d_model, d_model, 2)
pos_transformation
寸法を取得するためのエンコーディングは torch.Size([300, 2, 256]) です。
次にquery_sine_embed[...,:self.d_model] * pos_transformation
、ここでの query_sine_embed は torch.Size([300, 2, 512]) で、前の 256 次元を取得します。つまり、対応する取得は x、y です。pos_transformation を掛ける、pos_transformation
つまりXref,Yref
、ここで行われるのは次の操作です。
最初の層に乗算がないのではなくPE(Xref),PE(Yref)
、その値が 1 であることがわかります。
以降の処理はDecodeLayerの1層目と全く同じです。
デコーダモジュール
DecoderLayer のループ終了直後に実行します。intermediate
各レイヤーの結果を保存します。これは 6 つの値を含む List であり、各値の形状は torch.Size([300, 2, 256]) であり、6 番目は破棄されます。レイヤー (ポップ操作) を作成し、最終的な出力値を追加します。
if self.norm is not None:
output = self.norm(output)
if self.return_intermediate:
intermediate.pop()
intermediate.append(output)
次に、bbox_embed (MLP ボックス予測ヘッダー) が None であり、torch.stack がスプライシング操作であるかどうかを判断します。
if self.return_intermediate:
if self.bbox_embed is not None:
return [
torch.stack(intermediate).transpose(1, 2),
torch.stack(ref_points).transpose(1, 2),
]
else:
return [
torch.stack(intermediate).transpose(1, 2),
reference_points.unsqueeze(0).transpose(1, 2)
]
Transformer のデコーダ モジュールは最終的に結果を返します。
hs, references = self.decoder(tgt, memory, memory_key_padding_mask=mask,
pos=pos_embed, refpoints_unsigmoid=refpoint_embed)
この内、torch.Size([6, 2, 300, 4])を参照、hsはtorch.Size([6, 2, 300, 256])、結果もDecoderの戻り結果、参照はそれぞれの後ボックスのアップデートが完了しました。hs は意味特徴情報とみなされます。
DAB-DETR一体型モジュール
Transformer の次のステップは、分類ヘッドと回帰ヘッドを設計することです。
1 つ目は、Decoder モジュールの参照 (Anchor Box) の値を非正規化し、hs (Decoder の出力値、DETR の出力に相当) に対して回帰頭部予測を実行して、形状が torch.Size( [6, 2, 300, 4])、この値を処理された参照に追加します。(self.query_dim は 4、つまりすべて加算されます)、最後に tmp が正規化されます。
if not self.bbox_embed_diff_each_layer:#是否权值共享
reference_before_sigmoid = inverse_sigmoid(reference)#反归一化
tmp = self.bbox_embed(hs)#torch.Size([6, 2, 300, 4])
tmp[..., :self.query_dim] += reference_before_sigmoid
outputs_coord = tmp.sigmoid()
Output_coord 値は予測ボックスの xywh で、
最終的な戻り結果は次のとおりです。
pred_logits はカテゴリ予測 (ここでは 91 カテゴリ) torch.Size([2, 300, 91])
pred_boxes はボックス ボックス予測 torch.Size( [2, 300, 4 ])
aux_outputs は、DecoderLayer の最初の 5 層の結果です。5 つの値を含むリストです。