Fast R-CNN

Fast R-CNN 是 Ross Girshick 于 2014 年提出的算法。 针对 R-CNN 速度慢的问题,提出了 Roi Pooling 算法,使卷积运算可以在不同的提议框之间进行共享,大幅提高了推理时算法的速度。 Fast R-CNN 还借助多任务学习(multi-task learning)的模式实现了模型的端到端训练,大幅提升了模型训练的速度。 受益于这两个创新点,Fast R-CNN 在训练速度和测试速度上提高了 10-100 倍。

R-CNN 的速度问题

从检测精度的提升来看,RCNN 是非常成功的。但 RCNN 在速度方面有着明显的劣势。 即便使用 GPU 加速,推理一张图片也需要几十秒的时间。

RCNN 中存在大量重复的计算。 在推理过程中,selective search 可以产生 2000 个左右的提议框。 这些提议框无论大小,都需要统一拉伸到 227x227 再送入卷积网络。 这些 proposal 之间必然有大量重叠,但由于图像拉伸操作的存在,卷积运算不能在不同提议框之间共享。 即便在一张图像上进行推理,我们也需要上千次完整的 AlexNet 卷积网络的前传计算。

RCNN 的训练也非常复杂。在 RCNN 算法中,训练一个检测模型需要四个步骤:

  1. 在 ImageNet 数据集上预训练一个图像分类网络

  2. 使用裁剪下来的图像区域,对 ImageNet 预训练的网络进行微调训练

  3. 用训练好的网络提取所有图像区域的特征

  4. 用提取好的特征训练 SVM 分类器,和边界框回归模型

上述步骤前后依赖,耗时较长,而且需要大量的存储空间存储中间结果。

卷积共享

Fast RCNN 改进的出发点就是减少重叠区域的卷积计算。在 RCNN 算法中,为了提取不同区域的图像特征, 我们需要将 selective search 产生的提议框全部剪裁下来,再拉伸到同样的大小。由于拉伸尺度不同,重叠的区域在拉伸后就变成不同的图像了。 即便有所重叠,重叠部分也需要分别进行卷积计算。

为了减少重叠区域重复的卷积计算,Fast RCNN 采用了另一种方法,直接用卷积核扫过整个图像,产生整张图像上特征图,再从特征图上裁剪下不同的提议框,作为这些提议框的特征。考虑大小为H×WH\times W的图像及图像上位置和大小分别为 (x,y,h,w)(x, y, h, w) 的提议框。一个空间采样率为 SS 的卷积网络可以将图像变为 HS×WS\frac{H}{S}\times \frac{W}{S} 大小的特征图。我们可以按比例提议框映射到特征图上的框 (xS,yS,hS,wS)(\frac{x}{S}, \frac{y}{S}, \frac{h}{S}, \frac{w}{S}) ,再将这个框内的特征图裁剪下来作为提议框的特征。

需要注意的是,先拉伸再计算特征,先计算特征再裁剪,两种方法得到的结果是不同的。但两种方法得到的都是语义级别的特征,可以作为后续分类和边界框回归模型的输入。此外,这种映射关系只是人为定义一种几何关系,与神经网络的感受野在并没有本质上的联系,但针对大多数主流的主干网络(如 ResNet、VGG 等),提议框的感受野的中心是 (x,y)(x, y)

在 RCNN 中,如果有 N 个提议框,我们需要进行 N 次完整的前传计算,以得到每个提议框的特征。 在 Fast RCNN 中,无论有多少提议框,我们都只需要对原图进行一次前传计算,即可得到全部提议框的特征。

RoI Pooling

接下来我们需要考虑另一个问题。不同的提议框大小不同,因而裁剪下来的特征图尺度大小也互不相同。但后续的全连接层要求输入特征具有固定的维度。因此我们就需要将不同大小的提议框特征图处理到一个固定的大小。为了达到这个目的,Faster RCNN 提出了 RoI Pooling 层,通过池化操作将提议框特征图映射到固定的大小。

RoI Pooling 在操作上与普通的池化有所区别。普通的池化层设置固定大小的池子,例如 2x2 大小,再将每个池子内的神经元响应通过平均或者最大化聚合成一个值,输出特征图的大小随输入变化。RoI Pooling 则是设置一个固定的输出尺寸,再根据这个尺寸和确定池子的大小,使得池化的输出恰好为期待的大小。

在下面的示例图中,整幅图像的特征图大小为 8x8,黑框表示提议框在特征图上的映射,目标大小为 2x2。RoI Pooling 将黑框划分成 2x2 个区域,再在每个区域内取最大值,使得池化后的大小为 2x2 。

图片来源

严格来讲,考虑在特征图上左上右下坐标分别为 (l,t,r,b)(l, t, r, b) 的框,如果我们希望将特征图池化到 H×WH\times W 的大小,那么池化结果的第 ii 行,第 jj 列(1iH1\le i \le H, 1jW1\le j \le W)的单元来自特征图上

(l+(j1)rlW,t+(i1)btH,l+jrlW,t+ibtH)\left( l+\left\lfloor(j-1)\frac{r-l}{W}\right\rfloor, t+\left\lfloor(i-1)\frac{b-t}{H}\right\rfloor, l+\left\lceil j\frac{r-l}{W}\right\rceil, t+\left\lceil i\frac{b-t}{H}\right\rceil \right)

范围内的所有响应值。

RoI Pooling 层输出的大小通常设置为所搭配的主干网络在 224x224 图像上输出的特征图的大小,例如对于 VGG 或 ResNet,RoI Pooling 输出大小设置为 7x7,对于 AlexNet 或 ZFNet,输出大小设置为 6x6。这种设置可以更好地利用与训练模型。如果我们把一个 VGG 分类网络最后的池化层直接换成 ROI Pooling,那么这个网络就可以在任意大小的图像及任意大小的框上,产生固定 7x7 大小的特征图,而 7x7 的特征图又恰好可以适配预训练模型中的全连接层输出分类结果。

RoI Pooling 与普通池化层一样,是可以传递梯度的,这就使得端到端训练整个检测模型成为可能。

端到端与多任务学习

Fast RCNN 将 RCNN 中的神经网络、SVM 分类器和边界框回归模型集合到了一起,形成了一个单一的神经网络模型。该模型可以根据单张图像和其上的一系列提议框,直接计算出每个提议框中物体的类别及边界框的修正值。由于输入的图像和输出的检测结果之间只有一个神经网络模型,因而我们说 Fast RCNN 是一个端到端(end-to-end)的模型。又由于一个模型可以同时学习分类和边界框回归两个任务,因而我们说它是一个多任务学习(multi-task learning)的模型。

Fast RCNN 模型的整体结构如下图所示:

在推理阶段,Fast RCNN 以单张图片和 selective search (或其他外部算法)产生的一系列提议框为输入,经由如下步骤预测图中出现的物体:

  1. 将图片输入主干网络,产生特征图(即上图中的 conv feature map)。这个过程只需要进行一次。

  2. 将全部提议框,按照主干网络的缩放比例映射到特征图上。

  3. 对于每一个映射框,通过 RoI Pooling 产生固定尺度(例如 7x7)的提议框特征图。

  4. 将每个提议框特征图输入两个共享的全连接层,得到一个特征向量,即上图中的 roi feature vector。

  5. 将每个特征向量输入分类和回归模型,得到物体的类别,以及边界框的修正值。

  6. 删除分类为背景的提议框,利用其它后处理算法如 NMS 除去重叠的检测框,得到最终检测结果。

由于 Fast RCNN 中所有计算都是可微分的,因而我们可以抛弃 RCNN 中分步骤的训练方法,直接对其进行端到端的训练。训练的整体流程与图像分类网络类似,输入图像、前传计算分类和边界框偏移量,与真值比较计算损失函数并回传,更新网络参数,重复迭代至收敛。但由于检测模型在一张图上有若干个提议框,所以前传的过程比图像分类复杂一些。Fast RCNN 的训练的大体流程如下:

  1. 输入一个 batch 的图像以及对应的提议框,使用主干网络前传计算得到一批特征图。

  2. 将每张图像上的提议框与真值框进行匹配,将所有的提议框分为正样本负样本丢弃样本三类。大体而言,正样本即为与某些真值框重合度较大的提议框,负样本为与所有真值框重合都比较小的提议框,一些不适合用于训练的提议框则丢弃。在 Fast RCNN 的原始论文中,与某个真值框 IoU 大于 0.5 的提议框会被归为正样本,与所有真值框的 IoU 的最大值介于 0.1-0.5 的归为负样本,其余丢弃。需要注意的是,匹配算法并不是单一的,可以根据需要设计或调整。注意,这里的正样本和负样本都是提议框。

  3. 对于正样本提议框,用与之 IoU 最大的真值框的类别、以及对应的边界框偏移量,作为这个样本预测的真值。对于负样本,类别标记为背景,不需要边界框偏移量。不难发现,提议框的真值是训练过程中动态生成的。

  4. 按照一定的比例(如 1:3)随机选取一部分正样本和负样本,通过 RoI Pooling 得到这些提议框的特征图,并组成 batch,继续前传计算,得到对应的物体类别和边界框偏移量的预测值,与真值比较,计算损失函数,并回传。

对于正样本提议框,我们希望神经网络可以预测出其中物体的类别,以及边界框偏移量。对于负样本,我们只希望神经网络可以将其预测为背景,边界框偏移量对于背景是没有意义的。因而,损失函数可以表达为如下形式:

L=i=1PLiL = \sum_{i=1}^{P} L_i
Li={LiCls负样本LiCls+λLiLoc正样本L_i=\begin{cases} L^{\mathrm{Cls}}_i & \text{负样本}\\ L^{\mathrm{Cls}}_i + \lambda L^{\mathrm{Loc}}_i& \text{正样本} \end{cases}

其中,LiClsL^{\mathrm{Cls}}_i 为分类损失函数,通常使用交叉熵。LiLocL^{\mathrm{Loc}}_i 为边界框回归的损失函数,通常使用 smooth L1 损失函数,且仅对于正样本计算这部分损失函数。

通过这种方式,我们实现了端到端的训练,也节省了中间结果需要的存储空间。

模型在 mmdetection 中的实现

mmdetection 在 configs/fast_rcnn/fast_rcnn_r50_fpn_1x_coco.py 中实现了 fast-rcnn 模型训练的配置文件。下面我们对这个配置文件进行简要分析,需要注意的是,mmdetection 中实现的 Fast RCNN 实现了许多 Fast RCNN 之后的技术,如 ResNet、FPN、RoI ALign 等等,许多内容与当年提出的算法有所区别。

数据

首先,Fast RCNN 对数据的加载流程进行了改写。我们可以看到,在定义 data 字段时,我们需要把外部算法已经生成好的提议框从预先生成的 pickle 文件中读取进来。在数据流水线中,需要增加一个 LoadProposals 操作,将图像对应的至多 2000 个提议框增加到数据的字典中。

train_pipeline = [
    # ... omit some lines ...
    dict(type='LoadProposals', num_max_proposals=2000),
    # ... omit some lines ...
]

data = dict(
    # ... omit some lines ...
    train=dict(
        proposal_file=data_root + 'proposals/rpn_r50_fpn_1x_train2017.pkl',
        pipeline=train_pipeline),
    # ... omit some lines ...

模型

Fast RCNN 的模型定义继承自 configs/_base_/models/fast_rcnn_r50_fpn.py

这个模型使用 ResNet 作为主干网络,并使用 FPN 输出了多尺度的特征图。我们会在后面的小节中讲解 FPN,这里可以先忽略之。不使用 FPN 的情况下,我们可以使用 ResNet 的第三个 block 的输出作为特征图,即指定 out_indices=(2, )

主干网络输出的特征图会输入 StandardRoIHead 模块,该模块会使用 SingleRoIExtractor 子模块,根据数据中提供的提议框和主干网络输出的特征图,通过 RoI Align 算法产生 proposal 框的特征图,这里 RoI Align 算法是 RoI Pooling 算法的升级版。我们可以就把它当作 RoI Pooling 来理解。这里 roi_feat_size=7 表明 RoI Pooling 的目标大小为 7x7 。

接下来的 Shared2FCBBoxHead 以 RoI Pooling 输出的 7x7 大小的特征图为输入,经过两个全连接层,产生一个特征向量,再分别通过各自的全连接层产生类别和边界框的预测。在训练过程中,分别使用CrossEntropyLossL1Loss作为分类和边界框回归的损失函数,二者的权重通过loss_weight指定。

RoI 采样

基础配置 configs/_base_/models/fast_rcnn_r50_fpn.py 中的 train_cfg 字段定义了模型在训练过程中的 RoI 采样规则。这份配置使用 MaxIoUAssigner 对提议框和真值框进行匹配,具体匹配规则为:

  1. 与所有真值框 IoU 都小于 neg_iou_thr=0.5 的提议框为负样本

  2. 与某个真值框 IoU 不低于 pos_iou_thr=0.5 的提议框为正样本

匹配完成后,使用 RandomSampler 从这些正负样本中采样,总共采样 num=512 个提议框,其中 pos_fraction=0.25 比例为正样本。同时,add_gt_as_proposals=True 把真值框也作为提议框加入导正样本中,提高正样本的基数。

Last updated