RFBnet
之前学习一直是看CSDN和知识,感觉还是很多细节未能很好地了解,于是最近打算开始阅读原文,并记录自己的想法与代码解析。
可能有些错别字或者错误结论啥的哈。希望见谅。
Abstract
当前顶尖的目标检测网络(背景是在2018年)都是基于卷积神经网络作为特征提取网络(backbone),例如resnet-101与Inception,虽然这些特征提取网络有着强大的特征提取能力,但是却需要高昂的计算开销。与之相反,一些轻量化模型可以实现实时检测,但他们的精度却受到诟病。
在这篇文章中,作者探索出一个可替代的目标检测方法,通过人工特征加强这些轻量化模型的特征提取能力。作者的灵感来源于人类视觉系统的感受野(人眼关注目标物体的视野范围)结构。因此作者根据仿生学的原理提出了一个RFB模块,这个模块中以RFs的尺寸和偏心距之间的关系作为考量,以此提升网络提取特征的可变性与鲁棒性。
作者将这个模块集成与SSD网络的预测层之前,由此构成RFBnet目标检测网络的结构。为了评估作者提出网络的性能,实验在两个主要的基准线方法上(baseline)进行比较,结构表明RFBnet可以在拥有实时目标检测速度的前提下,依然有着卓越的检测性能。
Introduce
最近几年(2018年的背景喽)以RCNN,Fast-RCNN,Faster-RCNN为首的这三兄弟以及其牛逼的性能霸榜 Pascal VOC , MS COCO, and ILSVRC这三个数据集。 当然啦,他们都是二阶目标检测网络,大致思想为,一阶段进行前景与背景的区分,二阶段进行分类与回归。然后作者就扯了扯最近二阶段的一些成果什么的。
突然!一个however话锋一转,作者diss这些东东提取的特征都是来自深层次的网络,所以呐,计算机开销大,速度不太行呀。然后作者需要夸夸自己的滴工作了。
作者先介绍一阶目标检测网络速度上的优势,但是一阶目标检测网络需要牺牲性能。文中说大概比二阶目标检测网络的SOTA(最佳算法)低10% ~40%之间吧。这我一阶目标检测网络能服气,反正我是不服气。因为我就是搞一阶目标检测网络的(手动狗头)。
作者接着说最近呀,DSSD和Retinanet这两兄弟很争气,有效缓解了精度不足的问题。不幸的是,这两货使用的是resnet-101,这个backbone太慢了。
那么怎么办呢?作者提议不用这么深的特征提取网络,就用轻量化与RFB模块的特征提取网络来提取特征。而且作者说RFB模块对整个网络没什么制约,这个制约,个人猜测应该是不存在副作用吧,
随后作者列出了几点贡献
- 提出了RFB模块
- 将RFB模块置于检测头之前,计算开销在作者看来可以忽略不记。
- 与MobileNet进行实验验证RFB对于轻量化backbone的有效性
Related Work
- Inception
- ASPP
- Deformable Conv
作者将这几个结构的感受野与RFB模块的感受野进行对比,以证明RFB模块的优越性
- Inception是利用不同大小的卷积核来获取感受野,图上的颜色更为丰富
- ASPP是采取相同的卷积核,但是空洞数不一样,图上的颜色区域更大
- Deformable Conv是变换卷积核中的采样点以获取不同的感受野
于是作者将Inception与ASPP二者进行结合,提出了RFB模块
RFB模块介绍
作者将RFB模块分为两部分 多分支的卷积与空洞卷积
多分支的卷积主要是借鉴了Googlenet的思想
空洞卷积的设置主要是借鉴了ASPP的思想
于是作者提出了两种RFB模块分别应用于目标检测网络的不同位置
上图是RFB模块的解释图。可以观察到每个分支得到的特征图是不一样的,这里作者用蓝色圆,绿色圆,以及红色圆来表示。可以发现他们的大小与覆盖区域也是不一样的。相加与一张图上正好可以覆盖整张图。这也是为什么作者最后需要将不同通道上的特征图进行通道堆叠。
知道了模块长什么样子之后我们需要知道应该放到哪里,或者说那些地方可以用到。
作者考虑到浅层感受野的缺失,主要是将模块应用于38与19两个特征图的预测之前。
之后的层也用到空洞卷积。
实验部分
消融实验
Pascal VOC
COCO
class BasicRFB(nn.Module):
def __init__(self, in_planes, out_planes, stride=1, scale = 0.1, visual = 1):
super(BasicRFB, self).__init__()
self.scale = scale
self.out_channels = out_planes
inter_planes = in_planes // 8
self.branch0 = nn.Sequential(
BasicConv(in_planes, 2*inter_planes, kernel_size=1, stride=stride),
BasicConv(2*inter_planes, 2*inter_planes, kernel_size=3, stride=1, padding=visual, dilation=visual, relu=False)
)
self.branch1 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1),
BasicConv(inter_planes, 2*inter_planes, kernel_size=(3,3), stride=stride, padding=(1,1)),
BasicConv(2*inter_planes, 2*inter_planes, kernel_size=3, stride=1, padding=visual+1, dilation=visual+1, relu=False)
)
self.branch2 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1),
BasicConv(inter_planes, (inter_planes//2)*3, kernel_size=3, stride=1, padding=1),
BasicConv((inter_planes//2)*3, 2*inter_planes, kernel_size=3, stride=stride, padding=1),
BasicConv(2*inter_planes, 2*inter_planes, kernel_size=3, stride=1, padding=2*visual+1, dilation=2*visual+1, relu=False)
)
self.ConvLinear = BasicConv(6*inter_planes, out_planes, kernel_size=1, stride=1, relu=False)
self.shortcut = BasicConv(in_planes, out_planes, kernel_size=1, stride=stride, relu=False)
self.relu = nn.ReLU(inplace=False)
def forward(self,x):
x0 = self.branch0(x)
x1 = self.branch1(x)
x2 = self.branch2(x)
out = torch.cat((x0,x1,x2),1)
out = self.ConvLinear(out)
short = self.shortcut(x)
out = out*self.scale + short
out = self.relu(out)
return out
class BasicRFB_a(nn.Module):
def __init__(self, in_planes, out_planes, stride=1, scale = 0.1):
super(BasicRFB_a, self).__init__()
self.scale = scale
self.out_channels = out_planes
inter_planes = in_planes //4
self.branch0 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1),
BasicConv(inter_planes, inter_planes, kernel_size=3, stride=1, padding=1,relu=False)
)
self.branch1 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1),
BasicConv(inter_planes, inter_planes, kernel_size=(3,1), stride=1, padding=(1,0)),
BasicConv(inter_planes, inter_planes, kernel_size=3, stride=1, padding=3, dilation=3, relu=False)
)
self.branch2 = nn.Sequential(
BasicConv(in_planes, inter_planes, kernel_size=1, stride=1),
BasicConv(inter_planes, inter_planes, kernel_size=(1,3), stride=stride, padding=(0,1)),
BasicConv(inter_planes, inter_planes, kernel_size=3, stride=1, padding=3, dilation=3, relu=False)
)
self.branch3 = nn.Sequential(
BasicConv(in_planes, inter_planes//2, kernel_size=1, stride=1),
BasicConv(inter_planes//2, (inter_planes//4)*3, kernel_size=(1,3), stride=1, padding=(0,1)),
BasicConv((inter_planes//4)*3, inter_planes, kernel_size=(3,1), stride=stride, padding=(1,0)),
BasicConv(inter_planes, inter_planes, kernel_size=3, stride=1, padding=5, dilation=5, relu=False)
)
self.ConvLinear = BasicConv(4*inter_planes, out_planes, kernel_size=1, stride=1, relu=False)
self.shortcut = BasicConv(in_planes, out_planes, kernel_size=1, stride=stride, relu=False)
self.relu = nn.ReLU(inplace=False)
def forward(self,x):
x0 = self.branch0(x)
x1 = self.branch1(x)
x2 = self.branch2(x)
x3 = self.branch3(x)
out = torch.cat((x0,x1,x2,x3),1)
out = self.ConvLinear(out)
short = self.shortcut(x)
out = out*self.scale + short
out = self.relu(out)
return out
Liu, Songtao, and Di Huang. “Receptive field block net for accurate and fast object detection.” Proceedings of the European Conference on Computer Vision (ECCV). 2018.