目标检测 - Neck的设计 PAN(Path Aggregation Network)
flyfish
目标检测器的构成
1. Input:Image,Patches,ImagePyramid
2. Backbones:VGG16,ResNet(ResNet-18、ResNet-34、ResNet-50、ResNet-101、ResNet-152),SpineNet,EfficientNet-B0/B7,CSPResNeXt50,CSPDarknet53,MobileNet(v1、v2、v3),ShuffleNet(v1、v2) ,GhostNet
3. Neck:
Additional blocks:SPP,ASPP,RFB,SAM
Path-aggregation blocks:FPN,PAN,NAS-FPN,Fully-connectedFPN,BiFPN,ASFF,SFAM
4. Heads:
Dense Prediction(one-stage):
RPN,SSD,YOLO,RetinaNet(anchorbased)
CornerNet,CenterNet,MatrixNet,FCOS(FCOSv1、FCOSv2),ATSS,PAA(anchorfree)
SparsePrediction(two-stage):
FasterR-CNN,R-FCN,MaskR-CNN(anchorbased)
RepPoints(anchorfree)
Neck部分的设计是多种多样的
(a) FPN
(b) PANet
(c) NAS-FPN
(d) BiFPN
其中PANet就是本文说的
这里说明Neck-》Path-aggregation blocks-》PAN(Path Aggregation Network)
论文作者实现的代码
https://github.com/ShuLiu1993/PANet
看图
(a) FPN backbone
(b) Bottom-up path augmentation
(c) Adaptive feature pooling
(d) Box branch
(e) Fully-connected fusion
p5 -> p2是从上向下(Top-down),N2 -> N5是(Bottom-up)
原作者把backbone与FPN合并称为FPN backbone。而在实际写代码中,从上向下和从下向上这两条路径合并称为FAN
如下图
如何是轻量级模型则会只留下几层例如
PAN简单理解就是FPN多了一条Bottom-up path augmentation
FPN是从上向下,PAN包含了从上向下和从下向上的路径。
原版
PAN的原作者作者也是按照上图写代码将新增的模块直接加入到FPN代码中,区分是否使用GroupNorm
PANet/lib/modeling/FPN.py
# add for panet buttom-up path
if self.panet_buttomup:
self.panet_buttomup_conv1_modules = nn.ModuleList()
self.panet_buttomup_conv2_modules = nn.ModuleList()
for i in range(self.num_backbone_stages - 1):
if cfg.FPN.USE_GN:
self.panet_buttomup_conv1_modules.append(nn.Sequential(
nn.Conv2d(fpn_dim, fpn_dim, 3, 2, 1, bias=True),
nn.GroupNorm(net_utils.get_group_gn(fpn_dim), fpn_dim,
eps=cfg.GROUP_NORM.EPSILON),
nn.ReLU(inplace=True)
))
self.panet_buttomup_conv2_modules.append(nn.Sequential(
nn.Conv2d(fpn_dim, fpn_dim, 3, 1, 1, bias=True),
nn.GroupNorm(net_utils.get_group_gn(fpn_dim), fpn_dim,
eps=cfg.GROUP_NORM.EPSILON),
nn.ReLU(inplace=True)
))
else:
self.panet_buttomup_conv1_modules.append(
nn.Conv2d(fpn_dim, fpn_dim, 3, 2, 1)
)
self.panet_buttomup_conv2_modules.append(
nn.Conv2d(fpn_dim, fpn_dim, 3, 1, 1)
)
torch.cat例子
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 0)
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 1)
tensor([[ 0.6580, -1.0969, -0.4614, 0.6580, -1.0969, -0.4614, 0.6580,
-1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497, -0.1034, -0.5790, 0.1497, -0.1034,
-0.5790, 0.1497]])
mmdetection中yolo_neck版本
mmdetection中的yolo_neck是采样之后带cat运算
路径是mmdetection/mmdet/models/necks
def forward(self, feats):
assert len(feats) == self.num_scales
# processed from bottom (high-lvl) to top (low-lvl)
outs = []
out = self.detect1(feats[-1])
outs.append(out)
for i, x in enumerate(reversed(feats[:-1])):
conv = getattr(self, f'conv{i+1}')
tmp = conv(out)
# Cat with low-lvl feats
tmp = F.interpolate(tmp, scale_factor=2)
tmp = torch.cat((tmp, x), 1)
detect = getattr(self, f'detect{i+2}')
out = detect(tmp)
outs.append(out)
return tuple(outs)
nanodet版本
nanodet的作者的PAN没有卷积,使用interpolate进行上采样和下采样。按照top-down和bottom-up做的加法运算
def forward(self, inputs):
"""Forward function."""
assert len(inputs) == len(self.in_channels)
# build laterals
laterals = [
lateral_conv(inputs[i + self.start_level])
for i, lateral_conv in enumerate(self.lateral_convs)
]
# build top-down path
used_backbone_levels = len(laterals)
for i in range(used_backbone_levels - 1, 0, -1):
prev_shape = laterals[i - 1].shape[2:]
laterals[i - 1] += F.interpolate(
laterals[i], size=prev_shape, mode='bilinear')
# build outputs
# part 1: from original levels
inter_outs = [
laterals[i] for i in range(used_backbone_levels)
]
# part 2: add bottom-up path
for i in range(0, used_backbone_levels - 1):
prev_shape = inter_outs[i + 1].shape[2:]
inter_outs[i + 1] += F.interpolate(inter_outs[i], size=prev_shape, mode='bilinear')
outs = []
outs.append(inter_outs[0])
outs.extend([
inter_outs[i] for i in range(1, used_backbone_levels)
])
return tuple(outs)