0%

ASFF

YOLOV3-ASFF

1. 简介

YOLOV3-ASFF为了解决FPN不同特征尺度之间的不一致问题,提出自适应空间特征融合策略。它通过设置可学习权重因子对不同尺度的特征进行自适应(可学习)融合,通过在空间上过滤冲突信息从而抑制梯度反传时的不一致问题,从而改善了特征的比例不变性,也降低了时间开销。

image-20211228151839649

除了自适应空间特征融合,YOLOv3-ASFF在YOLOv3基础上博采众长,集合了MixUp数据增强,学习率cosine衰减策略,异步BN,Guided Anchoring,回归loss改为IoU loss等一系列tricks。 其strong yolov3-608 在COCO2017上达到了 38.8AP + 50fps的效果, 超过了原始YOLOv3-608 : 33.0AP + 53fps。

论文 https://arxiv.org/pdf/1911.09516v2.pdf

code: https://github.com/GOATmessi7/ASFF

2.自适应空间特征融合(ASFF)

image-20211228152822280

在上图中, 绿色框里代表一个ASFF模块,X1, X2, X3代表level1, level2, level3三层的特征图, 则为可学习的权重参数。 加权融合后特征最为Head层的输入。

image-20211228153257070

以ASFF-2举例说明:

  1. 对Level1的特整图(1,512,10,10)压缩通道数, 然后插值后变为(1,256,20,20)的特征向量。
  2. 对Level3的特征图进行卷积,输出特征图(1,256,20,20)
  3. Level2层级上特征图不需改变,然后对三个层级的特征图进行的卷积,得到三个层级的空间权重向量(1,16*3, 20,20)
  4. 对空间特征降维,并沿通道方向进行softmax操作,得到(1,3,20,20)的权重向量。
  5. 特征融合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import torch
import torch.nn as nn
import torch.nn.functional as F

class ASFF(nn.Module):
def __init__(self, level, rfb=False, vis=False):
super(ASFF, self).__init__()
self.level = level
self.dim = [512, 256, 256]
self.inter_dim = self.dim[self.level]
if level==0:
self.stride_level_1 = add_conv(256, self.inter_dim, 3, 2)
self.stride_level_2 = add_conv(256, self.inter_dim, 3, 2)
self.expand = add_conv(self.inter_dim, 1024, 3, 1)
elif level==1:
self.compress_level_0 = add_conv(512, self.inter_dim, 1, 1)
self.stride_level_2 = add_conv(256, self.inter_dim, 3, 2)
self.expand = add_conv(self.inter_dim, 512, 3, 1)
elif level==2:
self.compress_level_0 = add_conv(512, self.inter_dim, 1, 1)
self.expand = add_conv(self.inter_dim, 256, 3, 1)

compress_c = 8 if rfb else 16 #when adding rfb, we use half number of channels to save memory

self.weight_level_0 = add_conv(self.inter_dim, compress_c, 1, 1)
self.weight_level_1 = add_conv(self.inter_dim, compress_c, 1, 1)
self.weight_level_2 = add_conv(self.inter_dim, compress_c, 1, 1)

self.weight_levels = nn.Conv2d(compress_c*3, 3, kernel_size=1, stride=1, padding=0)
self.vis= vis


def forward(self, x_level_0, x_level_1, x_level_2):
if self.level==0:
level_0_resized = x_level_0
level_1_resized = self.stride_level_1(x_level_1)

level_2_downsampled_inter =F.max_pool2d(x_level_2, 3, stride=2, padding=1)
level_2_resized = self.stride_level_2(level_2_downsampled_inter)

elif self.level==1:
level_0_compressed = self.compress_level_0(x_level_0) # [1,512,10,10] --> [1,256,10,10]
level_0_resized =F.interpolate(level_0_compressed, scale_factor=2, mode='nearest') # [1,256,20,20]
level_1_resized =x_level_1 # [1,256,20,20]
level_2_resized =self.stride_level_2(x_level_2) #[1,256,40,40] --> [1,256, 20,20]
elif self.level==2:
level_0_compressed = self.compress_level_0(x_level_0)
level_0_resized =F.interpolate(level_0_compressed, scale_factor=4, mode='nearest')
level_1_resized =F.interpolate(x_level_1, scale_factor=2, mode='nearest')
level_2_resized =x_level_2

level_0_weight_v = self.weight_level_0(level_0_resized) #[1,16,20,20]
level_1_weight_v = self.weight_level_1(level_1_resized) #[1,16,20,20]
level_2_weight_v = self.weight_level_2(level_2_resized) #[1,16,20,20]
levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v),1) #[1,48,20,20]
levels_weight = self.weight_levels(levels_weight_v) # [1,3,20,20]

levels_weight = F.softmax(levels_weight, dim=1)

#softmax激活函数对三个通道的各位置进行运算,对不同通道的特征赋予不同的权重(归一化后),以达到自适应(可学习)的特征融合
fused_out_reduced = level_0_resized * levels_weight[:,0:1,:,:]+\
level_1_resized * levels_weight[:,1:2,:,:]+\
level_2_resized * levels_weight[:,2:,:,:]

out = self.expand(fused_out_reduced)

if self.vis:
return out, levels_weight, fused_out_reduced.sum(dim=1)
else:
return out

# test
if __name__ == '__main__':
model = ASFF(level=1)
l1 = torch.ones(1,512,10,10) # FPN --> L1
l2 = torch.ones(1,256,20,20) # FPN --> L2
l3 = torch.ones(1,256,40,40) # FPN --> L3

out = model(l1,l2,l3)
print(out.shape) #[1,256,20,20]

3. ASFF的可解释性

以YOLOv3为例,加入FPN后通过链式法则在backward的时候梯度是这样计算的:

image-20211228154404525

通常情况下,特征图尺寸增大通过插值(interpolation)实现,特征图尺寸缩小通过pooling来实现,假设:

image-20211228154629987

对于融合运算(add, concat),相当于对输出特征的activation操作,其导数也将为固定值,可以将它的值同样简化为1。

image-20211228154848675image-20211228154856380

最终简化为:

image-20211228155128091

若Level1上的某位置上为正样本特征,那如果Level2, Level3上对应位置却为负样本,那么在反向传播的梯度中既包含正样本信息也包含负样本信息,就会造成信息的不一致性,从而降低Level1上各个特征的学习效率。而通过ASFF的方式,反向传播的梯度表达式就变成了:

image-20211228155556739

因此可以动态地学习权重参数,进行更优的特征融合。

image-20211228155840045

上图可视化的结果进一步阐明了ASFF的有效性。比如大尺度的目标,可以看到斑马实际上是在level1这个特征图上被检测到的,并且观察level1这一层的权重信息可以发现,对于图中斑马这种大目标更容易被高层的特征捕捉到,因为对于大物体需要更大的感受野和高级语义特征。而对于小目标的检测,可以看到羊更多的是被level2和level3检测到,这也说明了对于小物体更需要底层特征中的细粒度特征来检测。

Reference

  1. https://qiyuan-z.github.io/2020/04/20/%E8%87%AA%E9%80%82%E5%BA%94%E7%A9%BA%E9%97%B4%E7%89%B9%E5%BE%81%E8%9E%8D%E5%90%88-(ASFF)/
  2. https://www.cnblogs.com/wujianming-110117/p/12921308.html
  3. https://zhuanlan.zhihu.com/p/129363523
  4. https://zhuanlan.zhihu.com/p/138816612