【多尺度 + 間隔注意】Transformer CrossFormer: A Versatile Vision Transformer Based On Cross-Scale Attention
主要結構和創新點
以往 Vision Transformer 結構在將圖片轉換成序列時會切成提前預設好的大小,將統一大小的小塊輸入網路中,但是這種方法往往忽略了圖片中包含的尺度特徵。本文提出了一種多尺度的轉換結構,並提出間隔選取形式的 Attention 模組節約視訊記憶體。
首先作者在對一張圖片進行嵌入 Embedding 操作時,會選取四個不同大小的卷積核以及輸出維度,具體可見上面封面圖。越小的尺度具有更大的維度,之後,將這四個尺度的特徵圖按特徵維度疊加在一起,具體程式碼可供參考:
class
PatchEmbed
(
nn
。
Module
):
‘’‘
img_size (int): Image size。 Default: 224。
patch_size (int): Patch token size。 Default: [4, 8, 16, 32]。
in_chans (int): Number of input image channels。 Default: 3。
embed_dim (int): Number of linear projection output channels。 Default: 96。
norm_layer (nn。Module, optional): Normalization layer。 Default: None
’‘’
def
__init__
(
self
,
img_size
=
224
,
patch_size
=
[
4
,
8
,
16
,
32
],
in_chans
=
3
,
embed_dim
=
96
,
norm_layer
=
None
):
self
。
projs
=
nn
。
ModuleList
()
for
i
,
ps
in
enumerate
(
patch_size
):
if
i
==
len
(
patch_size
)
-
1
:
dim
=
embed_dim
//
2
**
I
#最大一層維度和上一層一樣
else
:
dim
=
embed_dim
//
2
**
(
i
+
1
)
#可以參照圖片中給的資料
stride
=
patch_size
[
0
]
padding
=
(
ps
-
patch_size
[
0
])
//
2
self
。
projs
。
append
(
nn
。
Conv2d
(
in_chans
,
dim
,
kernel_size
=
ps
,
stride
=
stride
,
padding
=
padding
))
def
forward
(
self
,
x
):
B
,
C
,
H
,
W
=
x
。
shape
xs
=
[]
for
i
in
range
(
len
(
self
。
projs
)):
tx
=
self
。
projs
[
i
](
x
)
。
flatten
(
2
)
。
transpose
(
1
,
2
)
#根據提前設定的不同大小核卷積
xs
。
append
(
tx
)
# B Ph*Pw C
x
=
torch
。
cat
(
xs
,
dim
=
2
)
#以特徵為維度合併
return
x
(a) 文章整體模型網路結構;(b) 採用的低視訊記憶體間隔選取注意力模組
從文章的整體結構來看,本文不但在原圖上進行了多尺度操作,在之後每一層也會進行兩種尺度的合併計算。
class
PatchMerging
(
nn
。
Module
):
‘’‘
input_resolution (tuple[int]): Resolution of input feature。
dim (int): Number of input channels。
norm_layer (nn。Module, optional): Normalization layer。 Default: nn。LayerNorm
’‘’
def
__init__
(
self
,
input_resolution
,
dim
,
norm_layer
=
nn
。
LayerNorm
,
patch_size
=
[
2
,
4
],
num_input_patch_size
=
1
):
self
。
reductions
=
nn
。
ModuleList
()
self
。
patch_size
=
patch_size
self
。
norm
=
norm_layer
(
dim
)
for
i
,
ps
in
enumerate
(
patch_size
):
if
i
==
len
(
patch_size
)
-
1
:
out_dim
=
2
*
dim
//
2
**
i
else
:
out_dim
=
2
*
dim
//
2
**
(
i
+
1
)
stride
=
2
padding
=
(
ps
-
stride
)
//
2
self
。
reductions
。
append
(
nn
。
Conv2d
(
dim
,
out_dim
,
kernel_size
=
ps
,
stride
=
stride
,
padding
=
padding
))
def
forward
(
self
,
x
):
#size: B, H*W, C
H
,
W
=
self
。
input_resolution
B
,
L
,
C
=
x
。
shape
x
=
self
。
norm
(
x
)
x
=
x
。
view
(
B
,
H
,
W
,
C
)
。
permute
(
0
,
3
,
1
,
2
)
#為了方便卷積
xs
=
[]
for
i
in
range
(
len
(
self
。
reductions
)):
tmp_x
=
self
。
reductions
[
i
](
x
)
。
flatten
(
2
)
。
transpose
(
1
,
2
)
#其實和 Embedding 一樣啦
xs
。
append
(
tmp_x
)
x
=
torch
。
cat
(
xs
,
dim
=
2
)
return
x
而對於每一個模組,作者共設計了兩種機制,短距離注意力(SDA: Short distance attention)和長距離注意力(LDA: Long distance attention)模組,都將視訊記憶體和計算成本從
縮減為
,其中 G 要遠小於 S。
直觀上可以理解為分批次輸入注意力模組,短距離是使用鄰接模組作為一個批次,長距離則是間隔選取,程式碼中寫的很清楚,實際操作也很簡單:
x
=
x
。
view
(
B
,
H
,
W
,
C
)
G
=
self
。
group_size
#文中設為 7
if
self
。
lsda_flag
==
0
:
# 0 for SDA
x
=
x
。
reshape
(
B
,
H
//
G
,
G
,
W
//
G
,
G
,
C
)
。
permute
(
0
,
1
,
3
,
2
,
4
,
5
)
#變形啦
else
:
# 1 for LDA
x
=
x
。
reshape
(
B
,
G
,
H
//
G
,
G
,
W
//
G
,
C
)
。
permute
(
0
,
2
,
4
,
1
,
3
,
5
)
#變形啦
x
=
x
。
reshape
(
B
*
H
*
W
//
G
**
2
,
G
**
2
,
C
)
#形成分割好的結構
# multi-head self-attention
x
=
self
。
attn
(
x
,
mask
=
self
。
attn_mask
)
# nW*B, G*G, C
# ungroup embeddings
x
=
x
。
reshape
(
B
,
H
//
G
,
W
//
G
,
G
,
G
,
C
)
if
self
。
lsda_flag
==
0
:
x
=
x
。
permute
(
0
,
1
,
3
,
2
,
4
,
5
)
。
reshape
(
B
,
H
,
W
,
C
)
else
:
x
=
x
。
permute
(
0
,
3
,
1
,
4
,
2
,
5
)
。
reshape
(
B
,
H
,
W
,
C
)
x
=
x
。
view
(
B
,
H
*
W
,
C
)
在模組中,SDA 和 LDA 輪換使用。
同時,本文網路還使用了動態位置偏差結構,使注意力模組中加入一個動態位置偏差。位置偏差本身不是創新點,在之前文章中已被提到:
d 為一個常量約束;B 為位置偏差
但先前 B 一般為一個固定大小的引數矩陣,輸入圖片大小會被限制,防止超過 B 的大小範圍。作者提出了動態的位置偏差結構(DPB: Dynamic position bias),即依靠多層感知機訓練得到具體位置的引數。
DPB 結構
因為使用了長短距離注意力機制,因此輸入注意力模組的組別大小為 G*G,計算成本為
。
具體程式碼如下:
class
DynamicPosBias
(
nn
。
Module
):
def
__init__
(
self
,
dim
,
num_heads
):
self
。
num_heads
=
num_heads
self
。
pos_dim
=
dim
//
4
self
。
pos_proj
=
nn
。
Linear
(
2
,
self
。
pos_dim
)
self
。
pos1
=
nn
。
Sequential
(
nn
。
LayerNorm
(
self
。
pos_dim
),
nn
。
ReLU
(
inplace
=
True
),
nn
。
Linear
(
self
。
pos_dim
,
self
。
pos_dim
))
self
。
pos2
=
nn
。
Sequential
(
nn
。
LayerNorm
(
self
。
pos_dim
),
nn
。
ReLU
(
inplace
=
True
),
nn
。
Linear
(
self
。
pos_dim
,
self
。
pos_dim
))
self
。
pos3
=
nn
。
Sequential
(
nn
。
LayerNorm
(
self
。
pos_dim
),
nn
。
ReLU
(
inplace
=
True
),
nn
。
Linear
(
self
。
pos_dim
,
self
。
num_heads
))
def
forward
(
self
,
biases
):
pos
=
self
。
pos3
(
self
。
pos2
(
self
。
pos1
(
self
。
pos_proj
(
biases
))))
return
pos
# 然後是在 Attention 模組裡的具體使用部分
def
__init__
(
self
,
dim
,
group_size
,
num_heads
):
self
。
pos
=
DynamicPosBias
(
self
。
dim
//
4
,
self
。
num_heads
)
#DPB模組
# 這部分是創造所有位置相互之間的差值
position_bias_h
=
torch
。
arange
(
1
-
self
。
group_size
[
0
],
self
。
group_size
[
0
])
position_bias_w
=
torch
。
arange
(
1
-
self
。
group_size
[
1
],
self
。
group_size
[
1
])
biases
=
torch
。
stack
(
torch
。
meshgrid
([
position_bias_h
,
position_bias_w
]))
#size: 2, 2G-1, 2G-1
biases
=
biases
。
flatten
(
1
)
。
transpose
(
0
,
1
)
。
float
()
#size: (2G-1)*(2G-1), 2
self
。
biases
=
biases
# 這部分是創造對應關係的索引值
coords_h
=
torch
。
arange
(
self
。
group_size
[
0
])
coords_w
=
torch
。
arange
(
self
。
group_size
[
1
])
coords
=
torch
。
stack
(
torch
。
meshgrid
([
coords_h
,
coords_w
]))
#size: 2, G, G
coords_flatten
=
torch
。
flatten
(
coords
,
1
)
#size: 2, G*G
relative_coords
=
coords_flatten
[:,
:,
None
]
-
coords_flatten
[:,
None
,
:]
#size: 2, G*G, G*G 即以行或列為單位的 delta
relative_coords
=
relative_coords
。
permute
(
1
,
2
,
0
)
。
contiguous
()
#size: G*G, G*G, 2
relative_coords
[:,
:,
0
]
+=
self
。
group_size
[
0
]
-
1
#將負值取非負,為了索引
relative_coords
[:,
:,
1
]
+=
self
。
group_size
[
1
]
-
1
#同上
relative_coords
[:,
:,
0
]
*=
2
*
self
。
group_size
[
1
]
-
1
relative_position_index
=
relative_coords
。
sum
(
-
1
)
#和上面一起,即行索引*列數+列數,計算最終對應索引值
self
。
relative_position_index
=
relative_position_index
def
forward
(
self
,
x
,
mask
=
None
):
pos
=
self
。
pos
(
self
。
biases
)
#size: (2G-1)*(2G-1), heads
relative_position_bias
=
pos
[
self
。
relative_position_index
。
view
(
-
1
)]
。
view
(
self
。
group_size
[
0
]
*
self
。
group_size
[
1
],
self
。
group_size
[
0
]
*
self
。
group_size
[
1
],
-
1
)
#size: G*G, G*G, heads
relative_position_bias
=
relative_position_bias
。
permute
(
2
,
0
,
1
)
。
contiguous
()
# heads, G*G, G*G
attn
=
attn
+
relative_position_bias
。
unsqueeze
(
0
)
在測試階段,引數不需要回傳,因此當 G 不變時,矩陣 B 也不會改變,在初始計算一次就夠了。
四個不同大小的網路具體內部引數
實驗結果
在 ImageNet 上的實驗結果,網路使用了相似的引數量獲得了更高的精度
COCO 資料集上檢測和分割任務的實驗結果
論文資訊
Transformer CrossFormer: A Versatile Vision Transformer Based On Cross-Scale Attention
https://
arxiv。org/pdf/2108。0015
4。pdf