源碼地址:https://github.com/weiliu89/caffe/tree/ssd
本文詳細版本
YOLO V3 學習筆記
一. 算法概述
本文提出的SSD算法是一種直接預測目標類別和 bounding box 的多目標檢測算法。與 faster rcnn 相比,該算法沒有生成 proposal 的過程,這就極大提高了檢測速度。針對不同大小的目標檢測,傳統的做法是先將圖像轉換成不同大小(圖像金字塔),然后分別檢測,最后將結果綜合起來(NMS)。而SSD算法則利用不同卷積層的 feature map 進行綜合也能達到同樣的效果。算法的主網絡結構是VGG16,將最后兩個全連接層改成卷積層,並隨后增加了4個卷積層來構造網絡結構。對其中 5 種不同尺度的feature map 輸出分別用兩個不同的 3×3 的卷積核進行卷積,一個輸出分類用的 confidence,每個 default box 生成21個類別confidence;一個輸出回歸用的 localization,每個 default box 生成4個坐標值(x, y, w, h)。此外,這 5 個 feature map 還經過 PriorBox 層生成 prior box(相當於 faster rcnn 的 Anchor)。上述5個 feature map 中每一層的 default box的數量是給定的(8732個)。最后將前面三個計算結果分別合並然后傳給loss層。
二. Default box
文章的核心之一是作者同時采用 lower 和 upper 的 feature map 做檢測。如圖Fig 1 所示,這里假定有 8×8 和 4×4 兩種不同尺度的 feature map。第一個概念是feature map cell,feature map cell 是指 feature map 中每一個小格子,如圖中分別有 64 和 16 個 cell。另外有一個概念:default box,是指在feature map的每個小格(cell)上都有一系列固定大小的box,如下圖有4個(下圖中的虛線框,仔細看格子的中間有比格子還小的一個box)。假設每個 feature map cell 有 k 個 default box,那么對於每個default box都需要預測 c 個類別 score 和 4 個 offset,那么如果一個 feature map的大小是 m×n,也就是有 m*n 個 feature map cell,那么這個 feature map就一共有(c+4)*k * m*n 個輸出。這些輸出個數的含義是:采用3×3的卷積核對該層的feature map卷積時卷積核的個數,包含兩部分:數量 c*k*m*n 是 confidence 輸出,表示每個 default box 的 是某一類別的 confidence;數量4*k*m*n是 localization 輸出,表示每個default box 回歸后的坐標)。訓練中還有一個東西:prior box,是指實際中選擇的 default box(你可以認為 default box 是抽象類,而 prior box 是具體實例)。訓練中對於每個圖像樣本,需要先將 prior box 與 ground truth box 做匹配,匹配成功說明這個 prior box 所包含的是個目標,但離完整目標的 ground truth box 還有段距離,訓練的目的是保證default box 的分類 confidence 的同時將 prior box 盡可能回歸到 ground truth box。 舉個列子:假設一個訓練樣本中有2個ground truth box,所有的feature map中獲取的prior box一共有8732個。那個可能分別有10、20個prior box能分別與這2個ground truth box匹配上,匹配成功的將會作為正樣本參與分類和回歸訓練,而未能匹配的則只會參加分類(負樣本)訓練。訓練的損失包含定位損失和回歸損失兩部分。
作者的實驗表明default box的shape數量越多,效果越好。
這里用到的 default box 和 Faster RCNN 中的 Anchor 很像,在 Faster RCNN 中 anchor 只用在最后一個卷積層,但是在本文中,default box 是應用在多個不同層的 feature map 上,這一點倒是很像 FPN 了。
那么default box的 scale(大小)和aspect ratio(橫縱比)要怎么定呢?假設我們用 m 個 feature maps 做預測,那么對於每個featuer map而言其 default box 的 scale 是按以下公式計算的:
$\vee$
$S_k=S_{min} + \frac{S_{max} - S_{min}}{m-1}(k-1), k\in{[1, m]}$
這里smin是0.2,表示最底層的scale是0.2;smax是0.9,表示最高層的scale是0.9。
至於aspect ratio,用$a_r$表示為下式:注意這里一共有5種aspect ratio
$a_r = \{1, 2, 3, 1/2, 1/3\}$
因此每個default box的寬的計算公式為:
$w_k^a=s_k\sqrt{a_r}$
高的計算公式為:(很容易理解寬和高的乘積是 scale 的平方)
$h_k^a=s_k/\sqrt{a_r}$
另外當 aspect ratio 為1時,作者還增加一種 scale 的 default box:
$s_k^{'}=\sqrt{s_{k}s_{k+1}}$
因此,對於每個 feature map cell 而言,一共有 6 種 default box。
可以看出這種 default box 在不同的 feature 層有不同的 scale,在同一個 feature 層又有不同的 aspect ratio,因此基本上可以覆蓋輸入圖像中的各種形狀和大小的 object!
(訓練自己的樣本的時候可以在 FindMatch() 之后檢查是否能覆蓋了所有的 ground truth box,實際上是全覆蓋了,因為會至少找一個最大匹配)
源代碼中的 ssd_pascal.py 設計了上面幾個參數值,caffe 源碼 prior_box_layer.cpp 中Forward_cpu()實現。
最后會得到(38*38*4 + 19*19*6 + 10*10*6 + 5*5*6 + 3*3*4 + 1*1*4)= 8732 個 prior box。
Fig.2 SSD 框架
三. 正負樣本
將 prior box 和 grount truth box 按照 IOU(JaccardOverlap)進行匹配,匹配成功則這個 prior box 就是 positive example(正樣本),如果匹配不上,就是 negative example(負樣本),顯然這樣產生的負樣本的數量要遠遠多於正樣本。這里默認做了難例挖掘:簡單描述起來就是,將所有的匹配不上的 negative prior box 按照前向 loss 進行排序,選擇最高的 num_sel 個 prior box 序號集合作為最終的負樣本集。這里就可以利用 num_sel 來控制最后正、負樣本的比例在 1 : 3 左右。
Fig.3 positive and negtive sample VS ground_truth box
1.正樣本獲得
我們已經在圖上畫出了prior box,同時也有了 ground truth,那么下一步就是將 prior box 匹配到 ground truth上,這是在 src/caffe/utlis/bbox_util.cpp
的 FindMatches 以及子函數MatchBBox函數里完成的。具體的:先從 groudtruth box 出發,為每個 groudtruth box 找到最匹配的一個 prior box 放入候選正樣本集;然后再嘗試從剩下的每個 prior box 出發,尋找與 groundtruth box 滿足 $IoU \ge 0.5$ 的最大匹配,如果找到了這樣的一個匹配結果就放入候選正樣本集。這個做的目的是保證每個 groundtruth box 都至少有一個匹配正樣本。
2.負樣本獲
在生成一系列的 prior boxes 之后,會產生很多個符合 ground truth box 的 positive boxes(候選正樣本集),但同時,不符合 ground truth boxes 也很多,而且這個 negative boxes(候選負樣本集),遠多於 positive boxes。這會造成 negative boxes、positive boxes 之間的不均衡。訓練時難以收斂。
因此,本文采取,先將每一個物體位置上對應 predictions(prior boxes)loss 進行排序。 對於候選正樣本集:選擇 loss 最高的幾個prior box集合與候選正樣本集進行匹配(box索引同時存在於這兩個集合里則匹配成功),匹配不成功則刪除這個正樣本(因為這個正樣本不在難例里已經很接近ground truth box了,不需要再訓練了);對於候選負樣本集:選擇 loss 最高的幾個 prior box 與候選負樣本集匹配,匹配成功則作為負樣本。這就是一個難例挖掘的過程,舉個例子,假設在這8732個prior box里,經過 FindMatches 后得到候選正樣本 $P$ 個,候選負樣本那就有 $8732-P$ 個。將 prior box 的 prediction loss 按照從大到小順序排列后選擇最高的 $M$ 個prior box。如果這 $P$ 個候選正樣本里有 $a$ 個box不在這 $M$ 個prior box里,將這 $a$ 個box從候選正樣本集中踢出去。如果這 $8732-P$ 個候選負樣本集中包含的$ 8732-P$ 有 $b$ 個在這 $M$ 個prior box,則將這 $b$ 個候選負樣本作為最終的負樣本。總歸一句話就是:選擇 loss 值高的難例作為最后的負樣本參與 loss 反傳計算。SSD算法中通過這種方式來保證 positives、negatives 的比例。實際代碼中有三種負樣本挖掘方式:
如果選擇HARD_EXAMPLE方式(源於論文Training Region-based Object Detectors with Online Hard Example Mining),則默認$M = 64$,由於無法控制正樣本數量,這種方式就有點類似於分類、回歸按比重不同交替訓練了。
如果選擇MAX_NEGATIVE方式,則$M = P*neg\_pos\_ratio$,這里當$neg\_pos\_ratio = 3$的時候,就是論文中的正負樣本比例1:3了。
enum MultiBoxLossParameter_MiningType {
MultiBoxLossParameter_MiningType_NONE = 0, MultiBoxLossParameter_MiningType_MAX_NEGATIVE = 1, MultiBoxLossParameter_MiningType_HARD_EXAMPLE = 2 };
3.Data augmentation
本文同時對訓練數據做了 data augmentation,數據增廣。
每一張訓練圖像,隨機的進行如下幾種選擇:
- 使用原始的圖像
- 隨機采樣多個 patch(CropImage),與物體之間最小的 jaccard overlap 為:0.1,0.3,0.5,0.7 與 0.9
采樣的 patch 是原始圖像大小比例是 [0.3,1.0],aspect ratio 在 0.5 或 2。
當 groundtruth box 的 中心(center)在采樣的 patch 中且在采樣的 patch中 groundtruth box面積大於0時,我們保留CropImage。
在這些采樣步驟之后,每一個采樣的 patch 被 resize 到固定的大小,並且以 0.5 的概率隨機的 水平翻轉(horizontally flipped,翻轉不翻轉看prototxt,默認不翻轉)
這樣一個樣本被諸多batch_sampler采樣器采樣后會生成多個候選樣本,然后從中隨機選一個樣本送人網絡訓練。
四. 網絡結構
SSD的結構在VGG16網絡的基礎上進行修改,訓練時同樣為conv1_1,conv1_2,conv2_1,conv2_2,conv3_1,conv3_2,conv3_3,conv4_1,conv4_2,conv4_3,conv5_1,conv5_2,conv5_3(512),fc6經過3*3*1024的卷積(原來VGG16中的fc6是全連接層,這里變成卷積層,下面的fc7層同理),fc7經過1*1*1024的卷積,conv6_1,conv6_2(對應上圖的conv8_2),conv7_1,conv7_2,conv,8_1,conv8_2,conv9_1,conv9_2,loss。然后一方面:針對conv4_3(4),fc7(6),conv6_2(6),conv7_2(6),conv8_2(4),conv9_2(4)(括號里數字是每一層選取的default box種類)中的每一個再分別采用兩個3*3大小的卷積核進行卷積,這兩個卷積核是並列的(括號里的數字代表prior box的數量,可以參考Caffe代碼,所以上圖中SSD結構的倒數第二列的數字8732表示的是所有prior box的數量,是這么來的38*38*4+19*19*6+10*10*6+5*5*6+3*3*4+1*1*4=8732),這兩個3*3的卷積核一個是用來做localization的(回歸用,如果prior box是6個,那么就有6*4=24個這樣的卷積核,卷積后map的大小和卷積前一樣,因為pad=1,下同),另一個是用來做confidence的(分類用,如果prior box是6個,VOC的object類別有20個,那么就有6*(20+1)=126個這樣的卷積核)。如下圖是conv6_2的localizaiton的3*3卷積核操作,卷積核個數是24(6*4=24,由於pad=1,所以卷積結果的map大小不變,下同):這里的permute層就是交換的作用,比如你卷積后的維度是32×24×19×19,那么經過交換層后就變成32×19×19×24,順序變了而已。而flatten層的作用就是將32×19×19×24變成32*8664,32是batchsize的大小。另一方面結合conv4_3(4),fc7(6),conv6_2(6),conv7_2(6),conv8_2(4),conv9_2(4)中的每一個和數據層(ground truth boxes)經過priorBox層生成prior box。
經過上述兩個操作后,對每一層feature的處理就結束了。對前面所列的5個卷積層輸出都執行上述的操作后,就將得到的結果合並:采用Concat,類似googleNet的Inception操作,是通道合並而不是數值相加。
Fig.5 SSD 流程
損失函數方面:和Faster RCNN的基本一樣,由分類和回歸兩部分組成,可以參考Faster RCNN,這里不細講。總之,回歸部分的loss是希望預測的box和prior box的差距盡可能跟ground truth和prior box的差距接近,這樣預測的box就能盡量和ground truth一樣。
上面得到的8732個目標框經過Jaccard Overlap篩選剩下幾個了;其中不滿足的框標記為負數,其余留下的標為正數框。緊隨其后:
訓練過程中的 prior boxes 和 ground truth boxes 的匹配,基本思路是:讓每一個 prior box 回歸並且到 ground truth box,這個過程的調控我們需要損失層的幫助,他會計算真實值和預測值之間的誤差,從而指導學習的走向。
SSD 訓練的目標函數(training objective)源自於 MultiBox 的目標函數,但是本文將其拓展,使其可以處理多個目標類別。具體過程是我們會讓每一個 prior box 經過Jaccard系數計算和真實框的相似度,閾值只有大於 0.5 的才可以列為候選名單;假設選擇出來的是N個匹配度高於百分之五十的框吧,我們令 i 表示第 i 個默認框,j 表示第 j 個真實框,p表示第p個類。那么$x_{ij}^p$表示 第 i 個 prior box 與 類別 p 的 第 j 個 ground truth box 相匹配的Jaccard系數,若不匹配的話,則$x_{ij}^p=0$。總的目標損失函數(objective loss function)就由 localization loss(loc) 與 confidence loss(conf) 的加權求和:
- N 是與 ground truth box 相匹配的 prior boxes 個數
- localization loss(loc) 是 Fast R-CNN 中 Smooth L1 Loss,用在 predict box(l) 與 ground truth box(g) 參數(即中心坐標位置,width、height)中,回歸 bounding boxes 的中心位置,
以及 width、height
- confidence loss(conf) 是 Softmax Loss,輸入為每一類的置信度 c
- 權重項 α,可在protxt中設置 loc_weight,默認設置為 1