比較老的論文,但是是one stage的代表。
-
主要貢獻
1)提出SSD檢測架構。具有比YOLO更快更準的單階段檢測器,具有和較慢的Faster RCNN一樣的準確率。
2)SSD的核心思想是使用一組固定的default bounding box通過小的卷積核作用於特徵圖上對類別和邊框的偏移(offset)進行預測。
3)爲了獲得高的檢測準確率,在不同尺度特徵圖上進行預測。
4)簡單的端到端(end to end)訓練並獲得高的準確率,提升了speed和accuracy兩者的折中(trade-off)。
5)在PASCAL VOC,COCO和ILSVRC數據集上和近期的state of the art算法作出了timing和accuracy的對比。
-
SSD架構
-
Multi-scale feature maps for detection
在截斷的主幹網絡後添加捲積層。不同於YOLO、FasterRCNN等,這些網絡僅僅使用了最後一層特徵圖進行預測,SSD充分利用了主幹網絡提取特徵形成的多尺度卷積特徵圖,在不同特徵圖上分別預測。
論文給出SSD300結構如下:
使用VGG16的卷積層作爲骨幹網絡提取特徵並且在多尺度特徵圖上進行預測。
爲了細緻分析其結構,首先看VGG16網絡結構:
我們使用上圖D結構的VGG16的卷積層修改進行特徵提取,網絡結構如下:
idx | operation | feature map size | prediction |
---|---|---|---|
0 | Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | 300 | |
1 | ReLU(inplace=True) | ||
2 | Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
3 | ReLU(inplace=True), | ||
4 | MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) | 150 | |
5 | Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
6 | ReLU(inplace=True) | ||
7 | Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
8 | ReLU(inplace=True) | ||
9 | MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) | 75 | |
10 | Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
11 | ReLU(inplace=True), | ||
12 | Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
13 | ReLU(inplace=True) | ||
14 | Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
15 | ReLU(inplace=True) | ||
16 | MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=True) | 38 | |
17 | Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
18 | ReLU(inplace=True) | ||
19 | Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1),padding=(1, 1)) | ||
20 | ReLU(inplace=True) | ||
21 | Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | Y | |
22 | ReLU(inplace=True), | ||
23 | MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) | 19 | |
24 | Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
25 | ReLU(inplace=True), | ||
26 | Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
27 | ReLU(inplace=True), | ||
28 | Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) | ||
29 | ReLU(inplace=True), | ||
30 | MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=1, ceil_mode=False) | ||
31 | Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(6, 6), dilation=(6, 6)) | ||
32 | ReLU(inplace=True) | ||
33 | Conv2d(1024, 1024, kernel_size=(1, 1), stride=(1, 1)) | Y | |
34 | ReLU(inplace=True) | 19 |
在上面的基礎上,SSD增加了額外層進行特徵提取和預測,增加的額外層結構如下:
Conv8_1 | Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1)) | 19 | |
---|---|---|---|
Conv8_2 | Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)) | 10 | Y |
Conv9_1 | Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1)) | ||
Conv9_2 | Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2),padding=(1, 1)) | 5 | Y |
Conv10_1 | Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1)) | ||
Conv10_2 | Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1)) | 3 | Y |
Conv11_1 | Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1)) | ||
Conv11_2 | Conv2d(128, 256, kernel_size=(3, 3),stride=(1, 1)) | 1 |
Y |
【注意】論文中的SSD添加的額外層畫的和具體實現有所不同,我們看一下作者在自己的Caffe源碼上的實現:
# Add extra layers on top of a "base" network (e.g. VGGNet or Inception).
def AddExtraLayers(net, use_batchnorm=True, lr_mult=1):
use_relu = True
# Add additional convolutional layers.
# 19 x 19
from_layer = net.keys()[-1]
# TODO(weiliu89): Construct the name using the last layer to avoid duplication.
# 10 x 10
out_layer = "conv6_1"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 1, 0, 1,
lr_mult=lr_mult)
from_layer = out_layer
out_layer = "conv6_2"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 512, 3, 1, 2,
lr_mult=lr_mult)
# 5 x 5
from_layer = out_layer
out_layer = "conv7_1"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,
lr_mult=lr_mult)
from_layer = out_layer
out_layer = "conv7_2"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 1, 2,
lr_mult=lr_mult)
# 3 x 3
from_layer = out_layer
out_layer = "conv8_1"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,
lr_mult=lr_mult)
from_layer = out_layer
out_layer = "conv8_2"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 0, 1,
lr_mult=lr_mult)
# 1 x 1
from_layer = out_layer
out_layer = "conv9_1"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1,
lr_mult=lr_mult)
from_layer = out_layer
out_layer = "conv9_2"
ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 0, 1,
lr_mult=lr_mult)
return net
[注意]可以看到在代碼中的"conv8_2"、"conv9_2"實際上是沒有做stride=2,pading=1的卷積的,這一點和論文結構圖下方的文字相符:
事實上,Conv10_2和Conv11_2的卷積操並沒有進行跨步stride=2,應該都是5x5x256。猜測特徵圖太小可能並不利於檢測,因爲損失的細節可能比較多,畢竟在YOLO v1和FasterRCNN中最後都是7x7的特徵圖,這裏雖然最後兩個預測特徵圖都是5x5,但是通過不同scale的default box依然可以檢測不同尺度的目標。真是情況可能還是以實驗結果爲準,這裏我們還是以caffe源碼爲準。不過值得一提是,在SSD512中,最後的一層預測特徵圖是1x1x256.
具體細節可以參看原論文。
這裏我們可以修改VGG主幹網絡成19年出的EfficientNet。
EfficientNet-b4和Vgg16具有相當的參數:
model | Top-1Acc. Top-5Acc. | #Params |
---|---|---|
VGG-16 | 71.93% 90.67% | 14.7M |
ResNet-50 | 76.0% 93.0% | 26M |
EfficientNet-B4 | 83.0% 96.3% | 19M |
其中VGG16我們僅僅計算卷積層參數大約:138M - [(1000×4096+1000)+ (4096×4096+4096)+(224/2^5)^2 *512 *4096]/10^6=14.7M
借用EfficientDet論文中的圖,可以看到P3相當於SSD300中Conv4_3。鑑於EfficientNet本身結構比較深,上圖一直到P5都是EfficientNet主幹網絡。仿照SSD300結構,我們還是在最後添加類似Conv9-Conv11共6層,保持SSD300結構一致性。
下一章節介紹SSD的Pytorch代碼實現