DAB-DETR の作成者は、Deformable-DETR に基づいて、DAB-DETR のアイデアを Deformable-DETR に統合し、良好な結果を達成しました。今日、ブロガーはソース コードを通じて DAB-Deformable-DETR モデルを学習します。
まず、Deformable のイノベーションを見てみましょう。
Deformable-DETR イノベーション
マルチスケール融合
1 つ目は、よく話題になりますが言及しておかなければならないマルチスケール融合問題で、著者は ResNet50 の最後の 3 層を出力として取得し、それらを Transformer に融合させます。
変形可能な注意メカニズム
これは Deformable-DETR の主な革新、つまり変形可能な注意です。詳細な紹介については、ブロガーのブログ投稿をご覧ください。
次に、ソースコードからDAB-Deformable-DETRを学習していきます。
DAB-Deformable-DETR 全体モデル
まず、DAB-Deformable-DETR モデル構築ファイル DAB-Deformable-DETR.py の
前処理されたサンプル データにアクセスしますsamples
。
主に 2 つのデータ、つまり DAB-Deformable-DETR モデルに送信されるサンプル データであるテンソルを調べます。 .Size([2, 3, 608, 760])、それぞれバッチサイズ = 2、チャネル = 3、幅 = 608、高さ = 760 を表します。
2つ目はマスクで、画像のサイズを統一して塗りつぶした後のマスク情報で、どの部分が塗りつぶされているのか、どの部分が画像そのものなのかを示すもので、torch.Size([2, 608, 760])です。
次に、サンプル データはバックボーンに送信されます。バックボーンは元々は resnet50 でしたが、ここでブロガーはメモリの制限によりバックボーンを resnet18 に変更しました。
features, pos = self.backbone(samples)
得られた特徴量は 3 つあり、これは Resnet8 の最後の 3 層の出力結果であり、論文の最初の革新点であるマルチスケール特徴量情報に対応します。さらに、対応する位置コーディング情報 pos があります。(posデータのディメンション情報はフィーチャのディメンション情報と同じです)
フィーチャデータのディメンション情報は以下のとおりです。torch.Size([2, 256, 76, 95]) torch.Size([2, 256, 38, 48]) torch.Size([2, 256, 19, 24])
次に、フィーチャーが処理されて、Transformer モジュールに送信されるソースとマスクが取得されます。取得された srcs には 3 つのデータが含まれています。つまり、torch.Size([2, 256, 76, 95]) torch.Size([2, 256, 38, 48]) torch.Size([2, 256, 19, 24])
マスクのチャネル寸法はマスクよりも小さいだけです。
srcs = []
masks = []
for l, feat in enumerate(features):
src, mask = feat.decompose()
srcs.append(self.input_proj[l](src))
masks.append(mask)
assert mask is not None
次に、フィーチャの最後のレイヤーが処理されて src が生成され、対応するマスクも初めて生成されて srcs に追加されます。次の図に対応します。
if self.num_feature_levels > len(srcs):
_len_srcs = len(srcs)
for l in range(_len_srcs, self.num_feature_levels):
if l == _len_srcs:
src = self.input_proj[l](features[-1].tensors)
else:
src = self.input_proj[l](srcs[-1])
m = samples.mask
mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0]
pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype)
srcs.append(src)
masks.append(mask)
pos.append(pos_l)
このうちmask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0]
m は torch.Size([2, 608, 760]) で、m は torch.Size([2, 10, 12]) として src と同じサイズのマスクに変換されます。このプロジェクトでは、dual-ステージが有効になっていない場合は、
dab メソッドを使用し、その後次の手順を実行します。refanchor: torch.Size([300, 4]) tgt_embed: torch.Size([300, 256]) を取得し、それを結合して query_embeds torch.Size([300, 260])
を取得します(dim=1 に従ってステッチ)
寸法)
if self.num_patterns == 0:
tgt_embed = self.tgt_embed.weight # nq, 256
refanchor = self.refpoint_embed.weight # nq, 4
query_embeds = torch.cat((tgt_embed, refanchor), dim=1)
次に、データが Transformer モデルに送信され、出力結果は次のようになります。
hs, init_reference, inter_references, enc_outputs_class, enc_outputs_coord_unact = self.transformer(srcs, masks, pos, query_embeds)
hs: torch.Size([6, 2, 300, 256]) 意味特徴情報
init_reference: torch.Size([300, 4])
inter_references: torch.Size([6, 2, 300, 4]) 参照点ボックス
enc_outputs_class: なし
enc_outputs_coord_unact: なし
最終的に、hs は分類ヘッダーと回帰ヘッダーを通過する必要があり、回帰ヘッダーによって取得されたボックスが inter_references に追加されます。
outputs_classes = []#存放分类结果
outputs_coords = []#存放box结果
for lvl in range(hs.shape[0]):#6层
if lvl == 0:
reference = init_reference#初始化的,没变
else:
reference = inter_references[lvl - 1]
reference = inverse_sigmoid(reference)#反归一化
outputs_class = self.class_embed[lvl](hs[lvl])#6个分类头,各自预测各自的 torch.Size([2, 300, 91])
tmp = self.bbox_embed[lvl](hs[lvl])#6个回归头 torch.Size([2, 300, 4])
if reference.shape[-1] == 4:
tmp += reference#预测出box结果加上reference,默认为4
else:
assert reference.shape[-1] == 2
tmp[..., :2] += reference
outputs_coord = tmp.sigmoid()#获取输出box结果并进行归一化
outputs_classes.append(outputs_class)
outputs_coords.append(outputs_coord)
outputs_class = torch.stack(outputs_classes)#凭借输出结果
outputs_coord = torch.stack(outputs_coords)
reference = inverse_sigmoid(reference)
結果後:
outputs_coord = tmp.sigmoid()
出力ボックスの結果を取得して正規化する
最後に、outputs_classes:list の値が 6 つあり、それぞれは torch.Size([2, 300, 91]) です。
Outputs_class = torch.stack(outputs_classes) の後: torch.Size([6, 2, 300, 91])
Outputs_coord も同じことを望み、torch.Size([6, 2, 300, 4]) に変更して
、最後に結果を返します。DAB-Deformable-DETR の外部ロジックはあまり変わっていないことがわかりますが、Transformer の内部でどのように変化するかに注目してみましょう。
変圧器モジュール
まず、バックボーンネットワークモジュールにおけるマルチスケール機能の導入により、Encoder以前の処理も変更されました。
Transformer モジュールの入力はここから始まります。
hs, init_reference, inter_references, enc_outputs_class, enc_outputs_coord_unact = self.transformer(srcs, masks, pos, query_embeds)
渡されるパラメータは次のとおりです。
srcs: バックボーンを通じて取得された意味論的特徴情報。リスト形式で、合計 4 つの値がそれぞれ含まれます。
torch.Size([2, 256, 76, 95]) torch.Size ([2, 256, 38, 48])
torch.Size([2, 256, 19, 24])
torch.Size([2, 256, 10, 12])`
マスク: マスク値、4 つあり、それぞれのサイズは対応するレイヤーのソースと同じですが、マスク 0:torch.Size([2, 76, 95])、マスク 1:torch などのチャネル次元はありません。サイズ([2, 38, 48])
pos: 位置コード。次元は srcs と同じです。
query_embeds: torch.Size([300, 260])、これは次のコードを結合することによって取得されます。
tgt_embed = self.tgt_embed.weight # nq, 256
refanchor = self.refpoint_embed.weight # nq, 4
query_embeds = torch.cat((tgt_embed, refanchor), dim=1)
次に、Transformer モジュールを実行します。まず、入力バックボーン データを前処理し、変数を定義します。
src_flatten = []
mask_flatten = []
lvl_pos_embed_flatten = []
spatial_shapes = []
srcs、マスク、pos を変換します:
注: lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
(学習可能なパラメーター)
self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model))
self.level_embed[lvl] は Transformer モジュールのカスタム パラメータで、[4, 256] で、そのうちの 1 つは 256 ですが、ビューを通じて 1X1X256 に変更され、pos_embed (torch.Size( [2, 7220, 256 ])) が追加されます。つまり、最初の 2X7220 では、それぞれ 256 が追加されます。ディメンションを追加した後の lvl_pos_embed は pos_embed と同じになり、torch.Size([2, 7220, 256]) になります。
for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
bs, c, h, w = src.shape
spatial_shape = (h, w)
spatial_shapes.append(spatial_shape)
src = src.flatten(2).transpose(1, 2)# 由bs,c,h,w变为bs, hw, c
mask = mask.flatten(1)#由bs,h,w变为 bs, hw
pos_embed = pos_embed.flatten(2).transpose(1, 2)#由bs,c,h,w变为bs, hw, c
lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
lvl_pos_embed_flatten.append(lvl_pos_embed)
src_flatten.append(src)
mask_flatten.append(mask)
最後に、変換された src マスク pos が取得されます (実際には、これは次元変換です)。次のように:
次に src が 2 次元目で結合されます。つまり、すべての src (合計 4 層、リスト形式) が torch.Size([2, 9620, 256]) に変更されます。同様に、mask と pos も 2 番目の次元で変換されます
。同じ方法。
src_flatten = torch.cat(src_flatten, 1) # bs, \sum{hxw}, c
mask_flatten = torch.cat(mask_flatten, 1) # bs, \sum{hxw}
lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1)
spatial_shapes: [(76, 95), (38, 48), (19, 24), (10, 12)]、トゥプル形式が変換されます。
spatial_shapes = torch.as_tensor(spatial_shapes, dtype=torch.long, device=src_flatten.device)
テンソル形式を取得する
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
各レベルの初期インデックスを取得します (4 つのレベルの値を結合しました)
level_start_index = torch.cat((spatial_shapes.new_zeros((1, )), spatial_shapes.prod(1).cumsum(0)[:-1]))
取得: level_start_index は tensor([ 0, 7220, 9044, 9500], device='cuda:0')
valid_ratios = torch.stack([self.get_valid_ratio(m) for m in masks], 1)
get_valid_ratio メソッドは次のように定義されます。このメソッドの機能は実際には拡大率の値 (つまり、長さと幅の比率) を返すことであり、その寸法は torch.Size([2, 4, 2]) です。つまり、バッチ = 2、各レイヤーに 4、2 はアスペクト比です
def get_valid_ratio(self, mask):
_, H, W = mask.shape
valid_H = torch.sum(~mask[:, :, 0], 1)
valid_W = torch.sum(~mask[:, 0, :], 1)
valid_ratio_h = valid_H.float() / H
valid_ratio_w = valid_W.float() / W
valid_ratio = torch.stack([valid_ratio_w, valid_ratio_h], -1)
return valid_ratio
完全なコードは次のとおりです。
src_flatten = []
mask_flatten = []
lvl_pos_embed_flatten = []
spatial_shapes = []
for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
bs, c, h, w = src.shape
spatial_shape = (h, w)
spatial_shapes.append(spatial_shape)
src = src.flatten(2).transpose(1, 2) # bs, hw, c
mask = mask.flatten(1) # bs, hw
pos_embed = pos_embed.flatten(2).transpose(1, 2) # bs, hw, c
lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
lvl_pos_embed_flatten.append(lvl_pos_embed)
src_flatten.append(src)
mask_flatten.append(mask)
src_flatten = torch.cat(src_flatten, 1) # bs, \sum{hxw}, c
mask_flatten = torch.cat(mask_flatten, 1) # bs, \sum{hxw}
lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1)
spatial_shapes = torch.as_tensor(spatial_shapes, dtype=torch.long, device=src_flatten.device)
level_start_index = torch.cat((spatial_shapes.new_zeros((1, )), spatial_shapes.prod(1).cumsum(0)[:-1]))
valid_ratios = torch.stack([self.get_valid_ratio(m) for m in masks], 1)
エンコーダモジュール
次に、データをエンコーダ モジュールに送信します。
memory = self.encoder(src_flatten, spatial_shapes, level_start_index, valid_ratios, lvl_pos_embed_flatten, mask_flatten)
まず入力パラメータ値を確認します。
src_ flatten: torch.Size([2, 9620, 256]) 平坦化された src (バックボーンの抽出結果)
spatial_shapes: 各レイヤーのサイズ。
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
level_start_index: tensor([ 0, 7220, 9044, 9500], device='cuda:0')
valid_ratios: torch.Size([2, 4, 2]) 各レイヤーのスケーリング比:
tensor([[[1.0000, 1.0000],
[1.0000, 1.0000],
[1.0000, 1.0000],
[1.0000, 1.0000]],
[[0.8421, 0.8421],
[0.8542, 0.8421],
[0.8750, 0.8421],
[0.9167, 0.9000]]], device='cuda:0')
上記の比率の最初のものはすべて 1 で、比較的大きいことを示しています。2 番目のものは最初のものと同じサイズにパディングされています。
lvl_pos_embed_ flatten: torch.Size([2, 9620, 256]) 平坦化位置エンコード情報
Mask_ flatten: torch.Size([2, 9620]) 平坦化マスク情報
エンコーダーに入ると、モジュールの全体的な構造は変わりません。
基準点座標を生成します。
reference_points = self.get_reference_points(spatial_shapes, valid_ratios, device=src.device)
結果reference_points
は torch.Size([2, 9620, 4, 2]) です。各次元の意味は次のとおりです。batch=2
4 つの WH が加算されて 9620、4 は 4 層、2 は x、y 値です。
生成する際は、0.5から開始し、間隔を1とし、最後に0.5を引くことで格子点(基準点座標)を生成しますが、格子点が存在しないことを防ぐために、スケール倍率を乗じる必要があります。
meshgrid
この関数はグリッド行列を生成するために使用されます。これは 2 次元グリッド行列
linspace
関数にすることができます。均一な間隔を定義して値のシーケンスを作成します。間隔の開始点、終了点を指定し、分隔值总数
(開始点と終了点の両方) を指定すると、最後の関数は間隔クラスに均一に分布する一連の数値を返します。例を参照してください:
np.linspace(start = 0, stop = 100, num = 5)
def get_reference_points(spatial_shapes, valid_ratios, device):
reference_points_list = []
for lvl, (H_, W_) in enumerate(spatial_shapes):
ref_y, ref_x = torch.meshgrid(torch.linspace(0.5, H_ - 0.5, H_, dtype=torch.float32, device=device),
torch.linspace(0.5, W_ - 0.5, W_, dtype=torch.float32, device=device))
ref_y = ref_y.reshape(-1)[None] / (valid_ratios[:, None, lvl, 1] * H_)
ref_x = ref_x.reshape(-1)[None] / (valid_ratios[:, None, lvl, 0] * W_)
ref = torch.stack((ref_x, ref_y), -1)
reference_points_list.append(ref)
reference_points = torch.cat(reference_points_list, 1)
reference_points = reference_points[:, :, None] * valid_ratios[:, None]
return reference_points
部品に対応する格子点の基準座標。
EncoderLayerモジュール
ループを開いて EncoderLayer モジュールに送信します まず、送信されたパラメーター情報を確認します:
1 つのレイヤーでループする場合:
出力は torch.Size([2, 9620, 256]) で、これはバックボーンによって抽出された特徴情報です。
pos: 位置コーディング情報 torch.Size([2, 9620, 256])
Reference_points: 基準点 torch.Size([2, 9620, 4, 2])
spatial_shapes: torch.Size([4, 2]) その値は:
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
level_start_index:tensor([ 0, 7220, 9044, 9500], device='cuda:0')
padding_mask:torch.Size([2, 9620])
output = layer(output, pos, reference_points, spatial_shapes, level_start_index, padding_mask)
EncoderLayerに入るとセルフアテンションの計算が行われますが、ここではDeformable-Attend(変形可能なアテンション)を使用しているため、self.self_attn
この部分のアテンションの計算を書き換えます。
src2 = self.self_attn(self.with_pos_embed(src, pos), reference_points, src, spatial_shapes, level_start_index, padding_mask)
src = src + self.dropout1(src2)
src = self.norm1(src)
src = self.forward_ffn(src)
return src
すると、結果の src2 は torch.Size([2, 9620, 256]) となり、通常の EncoderLayer とまったく同じになります。最終的な戻り値src
は torch.Size([2, 9620, 256])
変形可能な注意モジュール
最後に、最も重要な部分、つまり変形可能な注意が来ます。
変形可能な注意のアイデアは単純です。つまり、各クエリは各頭の K 位置をサンプリングし、これらの位置の特徴と対話する必要があるだけです。detr とは異なり、各クエリはグローバル位置と対話する必要があります。なお、位置オフセットΔp mqx は全結合層のクエリにより取得され、注目重みも全結合層のクエリにより取得され、重みはK個のサンプリング点間で正規化されることに留意されたい。
src2 = self.self_attn(self.with_pos_embed(src, pos), reference_points, src, spatial_shapes, level_start_index, padding_mask)
self_attn に送信されるパラメータ情報を見てみましょう。
def forward(self, query, reference_points, input_flatten, input_spatial_shapes, input_level_start_index, input_padding_mask=None):
基準点座標
まず、バックボーンによって抽出された特徴情報に位置コーディング情報が追加されます。
with_pos_embed(src, pos)
reference_points: torch.Size([2, 9620, 4, 2]) ここで、reference_points は実際の意味情報やフィーチャに設定されるグリッドのない単なる座標値であることを思い出してください。で使用された地図。
spatial_shapes:torch.Size([4, 2])
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
src:torch.Size([2, 9620, 256])
padding_mask:torch.Size([2, 9620])
def with_pos_embed(tensor, pos):
return tensor if pos is None else tensor + pos
特徴マップの値
計算するには入力してくださいms_deform_attn.py
:
最初のクエリは torch.Size([2, 9620, 256])
input_ flatten は src で、ディメンションは torch.Size([2, 9620, 256]) です
value = self.value_proj(input_flatten)
以下の操作に相当します。
masked_fill
このメソッドにはマスクと値の 2 つのパラメータがあります。マスクは pytorch テンソル (Tensor)、要素はブール値、値は埋められる値です。埋め込みルールは、マスク内の値の値であるということです。値を埋めた self の対応する位置に対応します。
if input_padding_mask is not None:
value = value.masked_fill(input_padding_mask[..., None], float(0))
value の次元を torch.Size([2, 9620, 8, 32]) に変換します。
value = value.view(N, Len_in, self.n_heads, self.d_model // self.n_heads)
オフセットとウェイトの値
sampling_offsets = self.sampling_offsets(query).view(N, Len_q, self.n_heads, self.n_levels, self.n_points, 2)
attention_weights = self.attention_weights(query).view(N, Len_q, self.n_heads, self.n_levels * self.n_points)
attention_weights = F.softmax(attention_weights, -1).view(N, Len_q, self.n_heads, self.n_levels, self.n_points)
上記のコードは次の部分に対応します。つまり、クエリは線形を通じて取得されます:
offset testing_offsets (オフセットは特徴マップ上のオフセットです): torch.Size([2, 9620, 8, 4, 4, 2] )、バッチ=2、4 WH、8 ヘッド、4 フィーチャ レイヤー、4 サンプリング ポイント (図では 3 つが描かれています) の合計、2 はオフセット座標 (x, y)
リニアにより重量値を取得します。
注意_weights、最初は torch.Size([2, 9620, 8, 16])、16 は 4 つのフィーチャ レイヤーと 4 つのサンプリング ポイントの乗算を表し、その後 torch.Size([2, 9620, 8, 4, 4]) になります。
ここからわかるように、変形可能な atten は、attention_weight を取得するために K に Q point を乗算する必要がなく、そのattention_weight はオブジェクト クエリを通じて学習されます。
対応する全結合層:
self.sampling_offsets = nn.Linear(d_model, n_heads * n_levels * n_points * 2)
次に、サンプリング ポイントの最後の次元が 2 であるかどうかを判断します。ここで、reference_points は torch.Size([2, 9620, 4, 2])、sampling_offsets は torch.Size([2, 9620, 8, 4, 4, 2]) です。 ) 、 offset_normalizer は torch.Size([4, 2])
、つまり参照点の座標にオフセットを加えた値 (ここでのオフセットも特徴マップのサイズで除算されます) であり、次の位置を取得します。実際のサンプリングポイント。
if reference_points.shape[-1] == 2:
offset_normalizer = torch.stack([input_spatial_shapes[..., 1], input_spatial_shapes[..., 0]], -1)
sampling_locations = reference_points[:, :, None, :, None, :] \
+ sampling_offsets / offset_normalizer[None, None, None, :, None, :]
コードの最初の文は、元の文と同様に、input_spatial_shapes の幅と高さの位置を高さと幅に変更することです。
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
offset_normalizer になります。
tensor([[95, 76],
[48, 38],
[24, 19],
[12, 10]], device='cuda:0')
2 番目の文は、基準点の実際の座標を見つけることです。このうち[:, :, None, :, None, :]のNoneの役割はsampling_offsetsで追加する必要があるため次元を増やすことであり、torch.Size([2, 9620, 4, 2])、sampling_offsets が torch.Size([2, 9620, 8, 4, 4, 2])、追加時にアテンション ヘッドは考慮されず (すべてのヘッドが追加されます)、4 つのサンプリング ポイントすべてを追加する必要があります, そのため、展開するときは [:, :, None, :, None, :] を使用します。同様に、そのオフセット値はフィーチャマップ上の (src) であるため、変換された幅と高さの値で除算する必要があります (レイヤー数はレイヤー数に対応し、x、y は幅に対応します)と高さ)
最後に、取得した上記の値を CUDA の実装に送信します (これはアテンション値の計算とみなすことができます)。
output = MSDeformAttnFunction.apply(
value, input_spatial_shapes, input_level_start_index, sampling_locations, attention_weights, self.im2col_step)
出力される出力は torch.Size([2, 9620, 256]) です。
出力結果
最後に、計算結果は全結合層を介して出力されます。
output = self.output_proj(output)
変換結果は次のようになります: torch.Size([2, 9620, 256])
全結合層の定義:
self.output_proj = nn.Linear(d_model, d_model)
行われるのは次のプロセスです。
以上の一連の処理により、次の式の計算が完了します。
長時間注意の計算:
マルチスケールのマルチヘッド アテンションの計算:
2段式デュアルステージ
一連の操作後のメモリは、エンコーダトーチの出力結果です。Size([2, 9620, 256])
memory = self.encoder(src_flatten, spatial_shapes, level_start_index, valid_ratios, lvl_pos_embed_flatten, mask_flatten)
次に、Deformable-DETR の 2 番目の革新点であるダブルステージを開くかどうかを判断します。
メモリは torch.Size([2, 9620, 256])、
memory_padding_mask は torch.Size([2, 9620])、
spatial_shapes は
tensor([[76, 95],
[38, 48],
[19, 24],
[10, 12]], device='cuda:0')
gen_encoder_output_proposals
この方法は、エンコーダの出力値メモリ上で一連の処理を行い、最終的にデコーダ内の参照点の初期化に使用されます。
def gen_encoder_output_proposals(self, memory, memory_padding_mask, spatial_shapes):
N_, S_, C_ = memory.shape#batch_size ,长度,通道数
base_scale = 4.0 #多尺度数为4
proposals = []
_cur = 0
for lvl, (H_, W_) in enumerate(spatial_shapes):
mask_flatten_ = memory_padding_mask[:, _cur:(_cur + H_ * W_)].view(N_, H_, W_, 1) #按照尺度大小得出mask值 并转换维度为:torch.Size([2, 76, 95, 1])
valid_H = torch.sum(~mask_flatten_[:, :, 0, 0], 1) #计算高有多少为真 tensor([76, 64], device='cuda:0') 第一张图片最大,全部为真,第二张图片64为真
valid_W = torch.sum(~mask_flatten_[:, 0, :, 0], 1) #计算宽多少为真tensor([95, 80], device='cuda:0')
grid_y, grid_x = torch.meshgrid(torch.linspace(0, H_ - 1, H_, dtype=torch.float32, device=memory.device),
torch.linspace(0, W_ - 1, W_, dtype=torch.float32, device=memory.device))#生成矩阵网格点坐标
grid = torch.cat([grid_x.unsqueeze(-1), grid_y.unsqueeze(-1)], -1)
#unsqueeze(-1) 再加一层维度
scale = torch.cat([valid_W.unsqueeze(-1), valid_H.unsqueeze(-1)], 1).view(N_, 1, 1, 2)
grid = (grid.unsqueeze(0).expand(N_, -1, -1, -1) + 0.5) / scale
wh = torch.ones_like(grid) * 0.05 * (2.0 ** lvl)
proposal = torch.cat((grid, wh), -1).view(N_, -1, 4)
proposals.append(proposal)
_cur += (H_ * W_)
output_proposals = torch.cat(proposals, 1)
output_proposals_valid = ((output_proposals > 0.01) & (output_proposals < 0.99)).all(-1, keepdim=True)
output_proposals = torch.log(output_proposals / (1 - output_proposals))
output_proposals = output_proposals.masked_fill(memory_padding_mask.unsqueeze(-1), float('inf'))
output_proposals = output_proposals.masked_fill(~output_proposals_valid, float('inf'))
output_memory = memory
output_memory = output_memory.masked_fill(memory_padding_mask.unsqueeze(-1), float(0))
output_memory = output_memory.masked_fill(~output_proposals_valid, float(0))
output_memory = self.enc_output_norm(self.enc_output(output_memory))
return output_memory, output_proposals
最終的な出力値
output_memory: torch.Size([2, 9620, 256])
Output_proposals:torch.Size([2, 9620, 4])
第 2 段階の計算を開始し、分類ヘッダーと回帰ヘッダーを呼び出し、初期化中に 7 を初期化します。class_embed[self.decoder.num_layers] はちょうど 7 番目です。
enc_outputs_class = self.decoder.class_embed[self.decoder.num_layers](output_memory)#对encoder的结果进行分类预测 torch.Size([2, 9620, 91])
enc_outputs_coord_unact = self.decoder.bbox_embed[self.decoder.num_layers](output_memory) + output_proposals#对encoder进行回归并加上output_proposals
torch.Size([2, 9620, 4])
デコーダモジュール
疲れたので後で追加します