Fast R-CNN: 我变快了,也变强了!
点击上方“AI有道”,选择“置顶”公众号
重磅干货,第一时间送达
本文经作者授权转载,禁二次转载
来源 | @知乎 Uno Whoiam
原文 | https://zhuanlan.zhihu.com/p/62273673
Fast R-CNN 即 Fast Region-based Convolutional Network,你的直觉没错,它就是R-CNN的升级版。
论文链接:https://arxiv.org/abs/1504.08083
在细说 Fast R-CNN 之前,不妨先看看 R-CNN 有什么令人诟病的地方:
1. 慢,实在是慢,别说实时检测了,47s的等待让坐在电脑前的记几仿佛是一只智障。
2. 训练麻烦,AlexNet、SVMs 以及 bounding-box regression 得一个接一个地训练。
3. 训练占用大量时间和空间(硬盘),除开训练三个模型的时间,SVMs 和 bounding-box regression 的训练样本得用 AlexNet 一次又一次地前向传播提取特征、标注样本数据、保存在硬盘里的哟,每一张图片的每一个proposal都得跑一次哟,想想都觉得恶心。而作者而说明了,需要GPU花2.5天的时间才能处理完5K张VOC07trainval里的图片,产生的训练样本占用的空间得好几百个GB。想想都觉得恶心\呕。顺便温馨提示一下,每张图生成的样本最好单独生成一个文件夹保存,别把这这个数量级的样本放在同一个文件夹里哟,即使是最好的SSD也招架不住这样的文件夹,当你幡然醒悟想要rm -r -f dir 重新来过时,漫长的等待足够让您好好睡一觉了,别问我为什么知道这么多~泪目。
随着 Fast R-CNN 的到来,以上问题也就不复存在辣!相比 R-CNN,除了各种快(见下段原论文引用)Fast R-CNN 有以下几个特性:
Fast R-CNN trains the very deep VGG16 network 9× faster than R-CNN, is 213× faster at test-time, and achieves a higher mAP on PASCAL VOC 2012. Compared to SPPnet, Fast R-CNN trains VGG16 3× faster, tests 10× faster, and is more accurate.
1. 更高的mAP。
2. 不用分段训练,整个网络可以通过使用多任务损失函数,一次性训练好。
3. 训练可以更新网络层中的所有权重。
4. 无需苦逼生成训练样本缓存在硬盘上,节省了空间。
Fast R-CNN 的整体网络如下图所示:
接下来按照物体检测的大框架:候选框->特征提取->分类->精调位置。一步步来说吧。
一、提出候选框
和R-CNN一样,候选框的提出使用 selective search 方法。
selective search:
http://www.huppelen.nl/publications/selectiveSearchDraft.pdf https://blog.csdn.net/mao_kun/article/details/50576003
二、特征提取
使用深度卷积神经网络进行特征提取,在论文中作者分别使用了从小到大三种网络进行实验:
1. S: CaffeNet 即小型版的 AlexNet
2. M: VGG_CNN_ M_ 1024 一看名字就知道是小型一点的 VGG
3. L: VGG-16
值得注意的是,以上网络的全连接层都被去掉了,这意味着:输入的尺寸大小劳资不用管辣哈哈哈哈哈!!!(想起R-CNN里RBG大神辛辛苦苦想了四种办法将Proposal区域变成 227 x 227 再喂给 AlexNet 就觉得熏疼)
也就是说,特征提取网络最终输出的是 C 层的 Feature Maps 。
等等,好像有什么不对?我想要得到的是图片中某个Proposal区域的特征向量啊,没特征向量我怎么判断这个Proposal区域到底有没有物体嘛!
这就需要用到 ROI max-pooling 了。对于这个细节网上很少有可以把它说清楚的,本文将结合 Pytorch 实现代码,保证让您看得明明白白的。
首先,啥是Proposal区域?在Fast R-CNN中,Proposal区域就是用 selective search 在图片上生成的可能含有被检测物体的框框们,而这个框框,可以用左上角和右下角的坐标表示,即: (x1,y1,x2,y2) ,它们都是 [0,1] 范围内的 float 数,表示框框在图片中的相对位置。
现在我们的目标是:对于一个Proposal框,在神经网络输出的C 层 Feature Maps找到对应的部分。具体该怎么做呢?如果整张图片的经过特征提取网络生成的 Feature Maps 尺寸是 (C,W,H) ,那么框框对应的坐标是:
其中 Floor 表示下取整, Ceil 表示上取整。这个框出来的部分就代表着Proposal区域经过神经网络提取到的特征,另外 C 保持不变,将其平展开成一维向量后就表示Proposal区域的特征向量辣。
新的问题来了,Proposal框大小不同也就意味着对应的 Feature Maps 上的大小不同,大小不同平铺出来的一维特征向量也不同,那怎么办?
这就是 ROI max-pooling 需要做的事情了:将不同尺寸Proposal区域所对应的 Feature Maps 变成相同尺寸的,在 Pytorch 中可以使用 torch.nn.AdaptiveAvgPool2d 来实现,无论输入的 Feature Maps 是什么尺寸,它都可以通过调整stride、padding等参数给你输出成统一大小的尺寸。下面是 Pytorch 代码:
class SlowROIPool(nn.Module):
def __init__(self, output_size):
super().__init__()
self.maxpool = nn.AdaptiveMaxPool2d(output_size)
self.size = output_size
def forward(self, images, rois, roi_idx):
n = rois.shape[0]
h = images.size(2)
w = images.size(3)
x1 = rois[:,0]
y1 = rois[:,1]
x2 = rois[:,2]
y2 = rois[:,3]
x1 = np.floor(x1 * w).astype(int)
x2 = np.ceil(x2 * w).astype(int)
y1 = np.floor(y1 * h).astype(int)
y2 = np.ceil(y2 * h).astype(int)
res = []
for i in range(n):
img = images[roi_idx[i]].unsqueeze(0)
img = img[:, :, y1[i]:y2[i], x1[i]:x2[i]]
img = self.maxpool(img)
res.append(img)
res = torch.cat(res, dim=0)
return res
注:
代码来源:https://github.com/GitHberChen/Fast-RCNN-Object-Detection-Pytorch/blob/master/README.ipynb
images:shape为[N,C,H,W],为N张图片经VGG输出的 Feature Maps
rois:单张图片中包含N组Proposal框的 (x1,y1,x2,y2)
roi_idx:rois对应的图片序号
三、分类以及边框回归
简单,分类通过将上面提取出的特征向量使用全连接网络输出 N+1 类的置信度即可,而边框回归也是通过全连接网络输出 (N+1) x 4 个数。
另外一个细节是,论文中采用了 SVD 对这两个全连接层的计算进行了加速:
(这篇博客写得不错,推荐阅读)
图像分类任务中,用于卷积层计算的时间比用于全连接层计算的时间多,而在目标检测任务中,selective search算法提取的建议框比较多【约2k】,几乎有一半的前向计算时间被花费于全连接层,就Fast R-CNN而言,RoI池化层后的全连接层需要进行约2k次【每个建议框都要计算】,因此在Fast R-CNN中可以采用SVD分解加速全连接层计算;
具体如何实现呢?
物体分类和窗口回归都是通过全连接层实现的,假设全连接层输入数据为 x ,输出数据为 y ,全连接层参数为 W ,尺寸为 u×v ,那么该层全连接计算为: y=Wx,计算复杂度为 u×v ;
若将W进行SVD分解,并用前t个特征值近似代替,即:
那么原来的前向传播分解成两步:
计算复杂度为 u×t+v×t ,若 t<min(u,v)t<min(u,v) ,则这种分解会大大减少计算量;
在实现时,相当于把一个全连接层拆分为两个全连接层,第一个全连接层不含偏置,第二个全连接层含偏置;实验表明,SVD分解全连接层能使mAP只下降0.3%的情况下提升30%的速度,同时该方法也不必再执行额外的微调操作。
作者:WoPawn
来源:CSDN
原文:https://blog.csdn.net/WoPawn/article/details/52463853
四、损失函数
Fast R-CNN 虽是 two-stage 算法,但可以通过 one-stage 训练好,这意味着,损失函数包含多个任务目标:
最后附上 Fast R-CNN 结构图和具体细节:
RCNN (
(seq): Sequential (
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True)
(2): ReLU (inplace)
(3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True)
(5): ReLU (inplace)
(6): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(7): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True)
(9): ReLU (inplace)
(10): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True)
(12): ReLU (inplace)
(13): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(14): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True)
(16): ReLU (inplace)
(17): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True)
(19): ReLU (inplace)
(20): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(21): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True)
(22): ReLU (inplace)
(23): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(24): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(26): ReLU (inplace)
(27): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(28): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(29): ReLU (inplace)
(30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(31): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(32): ReLU (inplace)
(33): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
(34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(35): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(36): ReLU (inplace)
(37): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(38): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(39): ReLU (inplace)
(40): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(41): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True)
(42): ReLU (inplace)
)
(roipool): SlowROIPool (
(maxpool): AdaptiveMaxPool2d (output_size=(7, 7))
)
(feature): Sequential (
(0): Linear (25088 -> 4096)
(1): ReLU (inplace)
(2): Dropout (p = 0.5)
(3): Linear (4096 -> 4096)
(4): ReLU (inplace)
(5): Dropout (p = 0.5)
)
(cls_score): Linear (4096 -> 21)
(bbox): Linear (4096 -> 84)
(cel): CrossEntropyLoss (
)
(sl1): SmoothL1Loss (
)
)
参考链接:
https://github.com/GitHberChen/Fast-RCNN-Object-Detection-Pytorch/blob/master/README.ipynb
推荐阅读
(点击标题可跳转阅读)
觉得这篇文章有帮助?请转发给更多人
关注 AI有道 加星标,获取最新 AI 干货
最新 AI 干货,我在看❤️