Fast R-CNN: 我变快了,也变强了!

点击“小詹学Python”,选择“置顶”公众号

重磅干货,第一时间送达640?wx_fmt=jpeg

本文经作者授权转载,禁二次转载


来源 | @知乎 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 的整体网络如下图所示:


640?wx_fmt=jpeg


接下来按照物体检测的大框架:候选框->特征提取->分类->精调位置。一步步来说吧。


一、提出候选框 


和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) ,那么框框对应的坐标是:


640?wx_fmt=png


其中 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
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个特征值近似代替,即:


640?wx_fmt=png


那么原来的前向传播分解成两步:


640?wx_fmt=png


计算复杂度为 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 训练好,这意味着,损失函数包含多个任务目标:


640?wx_fmt=png

640?wx_fmt=jpeg


最后附上 Fast R-CNN 结构图和具体细节:


640?wx_fmt=jpeg


 
 
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 ( ))
(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



最新干货,我在看❤️

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值