從零開始實現SSD目標檢測(pytorch)
特別說明:
- 本系列文章是Pytorch目標檢測手冊的翻譯+總結
- 知其然知其所以然,光看論文不夠,得親自實現
第一章 相關概念概述
1.1 檢測框表示
邊界寬(bounding box)是包圍一個物體(objective)的框,用來表示這個物體的位置、形狀、大小等信息。不是最小外接矩形,僅僅是一個轉動角度為 0 的框。如下圖1-1
所示:

表示框的方法有很多(不贅述),圖1-1
的方式為框的邊界四個極值坐標\(x_{min},y_{min},x_{max},y_{max}\)。
但是這樣做的有點缺點:
- 知道\(x_{min},y_{min},x_{max},y_{max}\),我們無法知道這個目標的更多信息(比例),必須畫出來才有感官
- 如果沒有圖像的寬高,像素值毫無用處(其實還是無法知道比例信息)
改進方式如下圖1-2
所示

圖1-2
使用比例的方式,很直觀的知道目標更多的信息
但是還有一個缺點:
- 直接看這個信息,我們不知道目標長寬信息(當然你自己可以另外計算)
- 也不知道中心位置(相對於邊界,我們更關心中心)
再次改進的方式如下圖1-3
所示:

\(c_x,c_y,w,h\),中心點+寬高的方式,滿足視覺信息最大化。
1.2 交並比
如何用來判斷一個框檢測的好與壞?
- 使用交集\(A \cap B\)
直接使用交集的大小去判斷好壞,大尺度和小尺度不對等
比如:A和B大小都為100,交集50. C和D大小都為10,交集5.
如何說明這兩組哪個好壞?
通過上述的例子,我們發現少了一個比例問題。。。
- 使用 \(\frac{A{\cap}B}{A{\cup}B}\)交集除以並集
完全解決上訴問題
注意:這里還存在一個關於LOSS的問題,具體可參考GIOU

第二章 基礎網絡
注釋:這里實現的是SSD300,並非SSD512.
2.1 基礎網絡
當前都是使用特征提取的基礎架構,VGG、ResNet、DenseNet。。。等,原作者使用VGG-16架構作為基礎網絡。如下圖2-1
所示

原始VGG-16是基於ImageNet訓練的,參數優異。但是為了符合我們的設計,需要對基礎網絡做一定程度的修改。
- 原始輸入300*300圖像
- 對於池化后非整數feature map進行像上取整,比如con3_3==>>75*75,進行池化之后的大小為38 * 38,而不是37 * 37。
- 修改maxpooling_5==>>(size=3 * 3,stride=1),作用是不再將feature map的大小減半。
- 我們不需要全連接層,對 FC6 和 FC7 進行修改為conv6和conv7。直接去掉FC8
FC層修改為CONV層
說明:原作者對此進行了詳細的描述,筆者這里默認大家有基礎網絡架構。
全連接和卷積的區別之處在於reshape!!!
也就是說卷積之后進行reshape(nuns,1)和全連接結果一樣




總結:
- 大家不妨試一下,對VGG的最后全連接改為卷積,然后最后做一個reshape看看效果。
- 想一下效果一樣的,CNN就是學習參數,與最后的形式無關
2.2 附加網絡
我們當前已經學會如何從FC轉換為CONV,以下對VGG-16進行轉換。
VGG-16輸入圖像為224 * 224 * 3,那么conv5_3的輸出也就是7 * 7 * 512。
- FC6的輸入是7 * 7 * 512的一行向量,輸出是4096 * 1的向量,那么參數數量為:4096 * 7 * 7 * 512,也就等於kernel=512 * 7 * 7 * 4046
- FC7的輸入4096 * 1,輸出為4096 * 1,也就等於kernel = 4096 * 1 * 1 * 4096
雖然轉化到卷積了,但是channel太大了,512都挺大,別說4096了。。。
作者對其maxpooling和卷積進行了調整,如下圖2-5
所示
個人對其原因進行分析:
- 按照轉換,Conv6的輸入是7 * 7的feature,卷積也是7 * 7的大小。不符合常理,feature太小,卷積核太大。
- Conv7的channel=4096,那么深的通道完全沒有必要(網絡沒大到那種地步)
- 考慮到后面金字塔采樣(FPN),feature得有層次感

我們添加了額外的四個卷積塊,feature map的變化是通過卷積的stride=2來實現的,並非maxpooling
注意是每隔一個stride=1之后接一個stride=2,這樣做的感覺是過度一下
就像maxpooling不能緊接着來maxpooling一樣

第三章 先驗框設計
3.1 引言
在介紹先驗框(prior box)的設計之前,我們先確認一下目的:檢測出目標種類和位置
我們先從種類檢測入手:
以VGG-16為例,輸出是1000 * 1.代表1000個種類的預測
我們事先把標簽(label)編碼成one-hot形式,訓練結果接一個softmax,loss就可以使用交叉熵返回梯度。
沒毛病,完全可以
問題一:一個圖像中有兩個目標呢?
我們可以讓輸出為1000 * 2,其他情況類似直接為1000 * nums
問題二:如何知道輸出對應哪個呢?
假設輸入圖像為5 * 5,我們就讓輸出為5 * 5 * 1000,這樣就可以知道每個像素屬於什么label
好像這是語義分割了。。。
我們再從位置入手:
我們之前定義了bounding box的形式, $ c_x, c_y, w, h $
跟着上面VGG-16的思路走,一個目標就輸出4 * 1即可
問題三:同種類一樣,存在多個目標呢?
類似種類,輸出4 * 1 * nums即可
問題四:同種類一樣,確定對應的種類?
假設輸入圖像為5 * 5,我們就讓輸出為5 * 5 * 2,注意這里沒必要輸出 \(c_x, c_y\) ,因為每個像素也就知道位置了
問題五:一個像素點多個目標問題?
這種情況很少很少,可以輸出5 * 5 * nums * 2,nums是一個冗余,極少情況的冗余
這里就不上圖了,自己想一下即可明白,而且后面會有SSD的分類說明!
3.2 先驗框設計
在3.1節中,我們知道直接輸出每個像素點的框W和H即可,為什么現在又來個先驗框呢?
注意:這種方式是完全可行的,大家可以移步EAST和 AdvanceEAST
我們先來說直接輸出W和H的缺點:
- 深度學習學到的東西是有能力限制的,而不是說什么都可以學到(理想情況可以),比如ResNet多加了一個原始數據(ResNet模塊)就能更好的學習?
- 按理說RPN模塊應該過時了,為什么現在還有很多人使用?比如SiamMask目標跟蹤領域
- W和H的波動幅度都很大,直接去學習較為困難,我們能不能降低學習代價?
直接拋出一個話題,對比下圖3-1
兩種學習目標,哪個更容易學習?

毋容置疑,肯定第二章方式更容易學習,具體如何設計,且看SSD大神作者的方案
以這里為出發點,我們稱事先定義的bounding box為先驗框
- 我們在
conv4_3
,conv7
,conv8_2
,conv9_2
,conv10_2
,conv11_2
.這些feature map上定義先驗框 ,采用FPN(特征金字塔)進行多尺度檢測的方案。 - 定義一個參數 \(s\) ,\(w * h = s^2\),先驗框最大存在於Conv4_3上,設置為0.1,也就是圖像的10%大小。
- 為了進一步降低學習成本,定義不同比例的先驗框。1:1、1:2、2:1等,其中每一層額外加一個1:1的框,其他的框面積都是s,而這個額外的框\(s = \sqrt{s_k*s_{k+1}}\) ,這樣做的目的是為了銜接上下兩個feature
Feature Map From | Feature Map Dimensions | Prior Scale | Aspect Ratios | Number of Priors per Position | Total Number of Priors on this Feature Map |
---|---|---|---|---|---|
conv4_3 |
38, 38 | 0.1 | 1:1, 2:1, 1:2 + an extra prior | 4 | 5776 |
conv7 |
19, 19 | 0.2 | 1:1, 2:1, 1:2, 3:1, 1:3 + an extra prior | 6 | 2166 |
conv8_2 |
10, 10 | 0.375 | 1:1, 2:1, 1:2, 3:1, 1:3 + an extra prior | 6 | 600 |
conv9_2 |
5, 5 | 0.55 | 1:1, 2:1, 1:2, 3:1, 1:3 + an extra prior | 6 | 150 |
conv10_2 |
3, 3 | 0.725 | 1:1, 2:1, 1:2 + an extra prior | 4 | 36 |
conv11_2 |
1, 1 | 0.9 | 1:1, 2:1, 1:2 + an extra prior | 4 | 4 |
Grand Total | – | – | – | – | 8732 priors |
3.3 先驗框可視化
我們先看一下定義s之后的基本公式:
下面對conv9_2進行可視化:


con9_2總共有6個框,五個框面積是0.55,一個框面積是0.63
注意:文章全部參數都是歸一化之后的,請移步bounding box定義查看
大於邊界直接裁斷,其余feature map照部就搬即可,不再贅述
3.4 學習參數定義
在3.1節,我們對比了學習代價問題,具體的學習參數這節進行詳細說明

觀察上圖3-3
,是先驗框和實際框之間的偏差圖,具體我們學習哪些參數?
下面直接看作者的設計:

按照3.1節
說的,為什么不直接定義成\(c_x - c_{x1}\) (帽子用x1代替),而作者定義了除以\(w_{x1}\)
因為較大的先驗對應較小的偏差時,這個偏差很難學習,會導致大目標邊界不准確。
下圖3-5
情況一和情況二哪個學習的更好?沒有歸一化,根本無法使用

第四章 網絡輸出定義
在3.1節中已經進行了初步說明,作者的思路也是按照這個進行的
種類的輸出,這個多定義個背景(background)類,也就是nums = nums + 1,當然也可以沒有背景類,那么負樣本就無法進行學習,會大大降低網絡的魯棒性!!!

如上圖4-1
所示
- 位置輸出:采用3 * 3的卷積輸出4個通道(這里沒考慮單個像素多個目標情況),對應之前的
(g_c_x, g_c_y, g_w, g_h)
- 種類輸出:采用3*3的卷積輸出nums個通道(這里沒考慮單個像素多個目標情況),對應之前VGG-16的輸出。
下面以具體的Con9_2為例,如下圖4-2
所示:

定位的24 = 4 * 6===>>4:輸出參數,6:先驗框的數量
種類的6*n_class===>>同上
更為具體的如下圖5-4
所示:

作者還怕讀者不懂,特意舉了個例子,假設就兩個種類:貓和狗,一個背景
網絡輸出如下圖5-5
:

將位置和種類對應上,輸出一個完整的矩陣,如下圖5-6
所示:

第五章 LOSS設計
5.1 目標框匹配
在計算LOSS之前,我們得先把框對應上(輸出框和目標框的對應關系)
-
假設一張圖有N個目標,那就需要找到N和8732(這是疊加了輸出的偏差信息)個框的交集
-
為每個目標匹配一個最大交集的框
-
如果一個先驗框與目標的交集小於0.5,那么就當這個先驗框為負樣本
-
如果一個先驗框和目標的交集大於0.5,那么這個先驗框為正樣本,並且最大的交集為其匹配的種類
-
匹配的label都存在種類的標簽
插曲: 有些人搞不懂正樣本和負樣本的概念
正樣本:這個先驗框匹配到目標框,並且用於計算位置和種類的LOSS,其中位置loss為了更好的框住目標,種類loss為了提高置信度。
負樣本:這個先驗框沒有匹配到目標框,或者匹配到的目標框小於閾值(0.5等),並且位置不用計算loss(因為background沒有框位置),種類需要計算loss,提高背景那一類的置信度。


上圖5-2
和5-3
給出了具體的例子,容易理解不在贅述。
5.2 LOSS計算
定位LOSS
定位的loss使用SmoothL1,沒什么好說的

種類LOSS
現在遇到一個問題,我們直接使用交叉熵的LOSS的話,8732個框,基本百分之八十都是負樣本,而負樣本學習的結果是使當前的框種類置信度更高(背景)。
這樣導致的結果就是類間不平衡情況,導致背景檢測很准確,可能部分目標都會檢測成背景
作者采用的解決方案是:舍棄簡單的負樣本,重點學習困難的負樣本

假設綠色為負樣本框(實際背景不存在框),情況一相較於情況二更容易學習,那么我們舍棄情況一,使用情況二進行訓練。

總的LOSS

其中a也是一個可以作為學習的參數(筆者沒試過),原文中a=1
第六章 非極大值抑制
這一章比較簡單,直接看后面代碼即可