深度学习中小知识点系列(七) 解读Backbone之FPN与代码复现

文章介绍了FeaturePyramidNetwork(FPN)如何通过融合不同层次的特征,解决小物体检测中的多尺度问题,通过自下而上和自上而下的路径以及横向连接优化特征图。FPN在物体检测中表现出色,特别是在小物体识别和实例分割任务中。
摘要由CSDN通过智能技术生成

背景

​ 为了增强语义性,传统的物体检测模型通常只在深度卷积网络的最后一个特征图上进行后续操作,而这一层对应的下采样率(图像缩小的倍数)通常又比较大,如16、32,造成小物体在特征图上的有效信息较少,小物体的检测性能会急剧下降,这个问题也被称为多尺度问题。 解决多尺度问题的关键在于如何提取多尺度的特征。传统的方法有图像金字塔(Image Pyramid),主要思路是将输入图片做成多个尺度,不同尺度的图像生成不同尺度的特征,这种方法简单而有效,大量使用在了COCO等竞赛上,但缺点是非常耗时,计算量也很大。 从前面几大主干网络的内容可以知道,卷积神经网络不同层的大小与语义信息不同,本身就类似一个金字塔结构。

img

如上图,金字塔底部可以较为浅层特征图,金字塔顶部可以较为深层特征图!

浅层的特征图感受野小,比较适合检测小目标(要检测大目标,则其只“看”到了大目标的一部分,有效信息不够);深层的特征图感受野大,适合检测大目标(要检测小目标,则其”看“到了太多的背景噪音,冗余噪音太多),因此FPN应运而生!!!

​ 2017年的FPN(Feature Pyramid Network)方法融合了不同层的特征,较好地改善了多尺度检测问题。

下图是论文中 特征金字塔的示意图。
在这里插入图片描述

原理和特点

从Bottom-up pathway 开始,这一个小模块其实可以简答的理解成是 CNN的前向传播过程,每一个卷积层都会输出一个特征图。输出相同的大小的特征图的卷积层作者称之为是一个阶段。使用每个阶段中的最后一个输出作为当前阶段的输出。和传统的网络的不同之处在于,每一个阶段的所输出的特征图会被保存下来在后续的操作当中进行使用。Bottom-up 最后一层的输出是有着高语义的特征图。这里需要注意的一点是bottom-up pathway当中,每一个特征图是按照步长为2进行下采样的。这意味着,从第二个阶段开始的步长是 4, 第三个阶段是8,第4个阶段是16,然后第5个阶段是32。这是因为在这里的上采样过程使用的是 邻近值法。

随着bottom-up过程的结束,top-down也随之开始。Top-down过程可以先简单的理解成是一个上采样的过程。说的具体一些,Top-down过程的首先将 bottom-up最后一个阶段得到的特征图上采样到和bottom-up的第四阶段输出的特征图一样大的尺寸,然后在将通过横向连接(lateral connection)将他们进行 element-wise addition。换句话说就是,横相连接将bottom-up 和 top-down 尺寸相同的特征图进行融合。然后这个过程一直重复,直到整个过程结束。这里也有一些关于top-down和lateral connection的小细节需要注意。每一次 lateral connection都会有一个1 * 1 的卷积,这样做的目的是在调整特征图通道的数量。除此之外,每一个经过横向连接产生的特征图还会被一个3*3的卷积核进行卷积。论文中提到的原因是使用这样的技术去处理在上采样操作中出现的混叠效应。那所谓的混叠效应是什么呢? 这里的混叠效应指的是一种失真的现象,在这里可以简单的理解成两个特征图叠加,造成的特征失真或者混乱的现象。

从上面的记录中可以可以看出,对于这样一个网络,他的输入是一张任意大小的图像,然后输出多个,尺寸不一但是通道数一致的特征图。

FPN网络结构:

img

img

FPN的总体架构如上图所示,主要包含自下而上网络、自上而下网络、横向连接与卷积融合4个部分。

自下而上:

​ 最左侧为普通的卷积网络,默认使用ResNet结构,**用作提取语义信息。**C1代表了ResNet的前几个卷积与池化层,而C2至C5分别为不同的ResNet卷积组,这些卷积组包含了多个Bottleneck结构,组内的特征图大小相同,组间大小递减。

自上而下:

​ 首先对C5进行1×1卷积降低通道数得到P5,然后依次进行上采样得到P4、P3和P2,目的是得到与C4、C3与C2长宽相同的特征,以方便下一步进行逐元素相加。这里采用2倍最邻近上采样即直接对临近元素进行复制,而非线性插值

横向连接(Lateral Connection):

​ 目的是为了将上采样后的高语义特征与浅层的定位细节特征进行融合。高语义特征经过上采样后,其长宽与对应的浅层特征相同,而通道数固定为256,因此需要对底层特征C2至C4进行11卷积使得其通道数变为256,然后两者进行逐元素相加得到P4、P3与P2。由于C1的特征图尺寸较大且语义信息不足,因此没有把C1放到横向连接中。

卷积融合:

​ 在得到相加后的特征后,利用3×3卷积对生成的P2至P4再进行融合,目的是消除上采样过程带来的重叠效应,以生成最终的特征图。

如何选择特征图:

​ 对于实际的物体检测算法,需要在特征图上进行**RoI(Region of Interests,感兴趣区域)**提取,而FPN有4个输出的特征图,选择哪一个特征图上面的特征也是个问题。FPN给出的解决方法是,对于不同大小的RoI,使用不同的特征图,大尺度的RoI在深层的特征图上进行提取,如P5,小尺度的RoI在浅层的特征图上进行提取,如P2。

FPN将深层的语义信息传到底层,来补充浅层的语义信息,从而获得了高分辨率、强语义的特征,在小物体检测、实例分割等领域有着非常不俗的表现。

几个问题与回答

Q1. 不同深度的 feature map 为什么可以经过 upsample 后直接相加?

A:作者解释说这个原因在于我们做了 end-to-end 的 training,因为不同层的参数不是固定的,不同层同时给监督做 end-to-end training,所以相加训练出来的东西能够更有效地融合浅层和深层的信息。

Q2. 为什么 FPN 相比去掉深层特征 upsample(bottom-up pyramid) 对于小物体检测提升明显?(RPN 步骤 AR 从 30.5 到 44.9,Fast RCNN 步骤 AP 从 24.9 到 33.9)

A:作者在 poster 里给出了这个问题的答案

img

对于小物体,一方面我们需要高分辨率的 feature map 更多关注小区域信息,另一方面,如图中的挎包一样,需要更全局的信息更准确判断挎包的存在及位置。

Q3. 如果不考虑时间情况下,image pyramid 是否可能会比 feature pyramid 的性能更高?

A:作者觉得经过精细调整训练是可能的,但是 image pyramid 主要的问题在于时间和空间占用太大,而 feature pyramid 可以在几乎不增加额外计算量情况下解决多尺度检测问题。

具体代码:

一个简单的实现

# -*- coding: utf-8 -*-
import torch
import torch.nn.functional as F
import torch.nn as nn
class FPN(nn.Module):
    def __init__(self,in_channel_list,out_channel):
        super(FPN, self).__init__()
        self.inner_layer=[]
        self.out_layer=[]
        for in_channel in in_channel_list:
            self.inner_layer.append(nn.Conv2d(in_channel,out_channel,1))
            self.out_layer.append(nn.Conv2d(out_channel,out_channel,kernel_size=3,padding=1))
        # self.upsample=nn.Upsample(size=, mode='nearest')
    def forward(self,x):
        head_output=[]
        corent_inner=self.inner_layer[-1](x[-1])
        head_output.append(self.out_layer[-1](corent_inner))
        for i in range(len(x)-2,-1,-1):
            pre_inner=corent_inner
            corent_inner=self.inner_layer[i](x[i])
            size=corent_inner.shape[2:]
            pre_top_down=F.interpolate(pre_inner,size=size)
            add_pre2corent=pre_top_down+corent_inner
            head_output.append(self.out_layer[i](add_pre2corent))
        return list(reversed(head_output))
if __name__ == '__main__':
    fpn=FPN([10,20,30],5)
    x=[]
    x.append(torch.rand(1, 10, 64, 64))
    x.append(torch.rand(1, 20, 16, 16))
    x.append(torch.rand(1, 30, 8, 8))
    c=fpn(x)
    print(c)

更加通用的代码

import torch.nn as nn
import torch.nn.functional as F
import math

##先定义ResNet基本类,或者可以说ResNet的基本砖块
class Bottleneck(nn.Module):
    expansion = 4   ##通道倍增数
    def __init__(self, in_planes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.bottleneck = nn.Sequential(
                nn.Conv2d(in_planes, planes, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, planes, 3, stride, 1, bias=False),
                nn.BatchNorm2d(planes),
                nn.ReLU(inplace=True),
                nn.Conv2d(planes, self.expansion * planes, 1, bias=False),
                nn.BatchNorm2d(self.expansion * planes),
            )
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
    def forward(self, x):
        identity = x
        out = self.bottleneck(x)
        if self.downsample is not None:
            identity = self.downsample(x)
        out += identity
        out = self.relu(out)
        return out
##FPN类
class FPN(nn.Module):
    def __init__(self, layers):
        super(FPN, self).__init__()
        self.inplanes = 64
        ###下面四句代码代表处理输入的C1模块--对应博客中的图
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(3, 2, 1)
        ###搭建自下而上的C2,C3,C4,C5
        self.layer1 = self._make_layer(64, layers[0])
        self.layer2 = self._make_layer(128, layers[1], 2)
        self.layer3 = self._make_layer(256, layers[2], 2)
        self.layer4 = self._make_layer(512, layers[3], 2)
        ###定义toplayer层,对C5减少通道数,得到P5
        self.toplayer = nn.Conv2d(2048, 256, 1, 1, 0) 
        ###代表3*3的卷积融合,目的是消除上采样过程带来的重叠效应,以生成最终的特征图。
        self.smooth1 = nn.Conv2d(256, 256, 3, 1, 1)
        self.smooth2 = nn.Conv2d(256, 256, 3, 1, 1)
        self.smooth3 = nn.Conv2d(256, 256, 3, 1, 1)
        ###横向连接,保证通道数目相同
        self.latlayer1 = nn.Conv2d(1024, 256, 1, 1, 0)
        self.latlayer2 = nn.Conv2d( 512, 256, 1, 1, 0)
        self.latlayer3 = nn.Conv2d( 256, 256, 1, 1, 0)
##作用:构建C2-C5砖块,注意stride为1和2的区别:得到C2没有经历下采样
    def _make_layer(self, planes, blocks, stride=1):
        downsample  = None
        if stride != 1 or self.inplanes != Bottleneck.expansion * planes:
            downsample  = nn.Sequential(
                nn.Conv2d(self.inplanes, Bottleneck.expansion * planes, 1, stride, bias=False),
                nn.BatchNorm2d(Bottleneck.expansion * planes)
            )
        ###初始化需要一个list,代表左侧网络ResNet每一个阶段的Bottleneck的数量
        layers = []
        layers.append(Bottleneck(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * Bottleneck.expansion
        for i in range(1, blocks):
            layers.append(Bottleneck(self.inplanes, planes))
        return nn.Sequential(*layers)
###自上而下的上采样模块
    def _upsample_add(self, x, y):
        _,_,H,W = y.shape
        return F.upsample(x, size=(H,W), mode='bilinear') + y
​
    def forward(self, x):
        ###自下而上
        c1 = self.maxpool(self.relu(self.bn1(self.conv1(x))))
        c2 = self.layer1(c1)
        c3 = self.layer2(c2)
        c4 = self.layer3(c3)
        c5 = self.layer4(c4)
        ###自上而下
        p5 = self.toplayer(c5)
        p4 = self._upsample_add(p5, self.latlayer1(c4))
        p3 = self._upsample_add(p4, self.latlayer2(c3))
        p2 = self._upsample_add(p3, self.latlayer3(c2))
        ###卷积融合,平滑处理
        p4 = self.smooth1(p4)
        p3 = self.smooth2(p3)
        p2 = self.smooth3(p2)
        return p2, p3, p4, p5
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小酒馆燃着灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值