上節,我們學習了如何通過卷積網絡實現滑動窗口對象檢測算法,但效率很低。這節我們講講如何在卷積層上應用這個算法。
為了構建滑動窗口的卷積應用,首先要知道如何把神經網絡的全連接層轉化成卷積層。我們先講解這部分內容,並演示卷積的應用過程。
一 卷積的滑動窗口實現

假設對象檢測算法輸入一個 14×14×3 的圖像,圖像很小,不過演示起來方便。在這里過濾器大小為 5×5,數量是 16, 14×14×3 的圖像在過濾器處理之后映射為 10×10×16。然后通過參數為 2×2 的最大池化操作,圖像減小到 5×5×16。然后添加一個連接 400 個單元的全連接層,接着再添加一個全連接層,最后通過 softmax 單元輸出y。為了跟下圖區分開,我先做一點改動,用 4 個數字來表示y,它們分別對應 softmax 單元所輸出的 4 個分類出現的概率。這 4 個分類可以是行人、汽車、摩托車和背景或其它對象。

現在我要演示的就是如何把這些全連接層轉化為卷積層,畫一個這樣的卷積網絡,它的前幾層和之前的一樣,而對於下一層,也就是這個全連接層,我們可以用 5×5 的過濾器來實現,數量是 400 個(編號 1 所示),輸入圖像大小為 5×5×16,用 5×5 的過濾器對它進行卷積操作,過濾器實際上是 5×5×16,因為在卷積過程中,過濾器會遍歷這 16 個通道,所以這兩處的通道數量必須保持一致,輸出結果為 1×1。假設應用 400 個這樣的 5×5×16 過濾器,輸出維度就是 1×1×400,我們不再把它看作一個含有 400 個節點的集合,而是一個 1×1×400的輸出層。從數學角度看,它和全連接層是一樣的,因為這 400 個節點中每個節點都有一個5×5×16 維度的過濾器,所以每個值都是上一層這些 5×5×16 激活值經過某個任意線性函數的輸出結果。
我們再添加另外一個卷積層(編號 2 所示),這里用的是 1×1 卷積,假設有 400 個 1×1的過濾器,在這 400 個過濾器的作用下,下一層的維度是 1×1×400,它其實就是上個網絡中的這一全連接層。最后經由 1×1 過濾器的處理,得到一個 softmax 激活值,通過卷積網絡,我們最終得到這個 1×1×4 的輸出層,而不是這 4 個數字(編號 3 所示)。
以上就是用卷積層代替全連接層的過程,結果這幾個單元集變成了 1×1×400 和 1×1×4 的維度。
掌握了卷積知識,我們再看看如何通過卷積實現滑動窗口對象檢測算法。

假設向滑動窗口卷積網絡輸入 14×14×3 的圖片,為了簡化演示和計算過程,這里我們依然用 14×14的小圖片。和前面一樣,神經網絡最后的輸出層,即 softmax單元的輸出是 1×1×4,我畫得比較簡單,嚴格來說, 14×14×3 應該是一個長方體,第二個 10×10×16 也是一個長方體,但為了方便,我只畫了正面。所以,對於 1×1×400 的這個輸出層,我也只畫了它 1×1 的那一面,所以這里顯示的都是平面圖,而不是 3D 圖像。

假設輸入給卷積網絡的圖片大小是 14×14×3,測試集圖片是 16×16×3,現在給這個輸入圖片加上黃色條塊,在最初的滑動窗口算法中,你會把這片藍色區域輸入卷積網絡(紅色筆標記)生成 0 或 1 分類。接着滑動窗口,步幅為 2 個像素,向右滑動 2 個像素,將這個綠框區域輸入給卷積網絡, 運行整個卷積網絡,得到另外一個標簽 0 或 1。繼續將這個橘色區域輸入給卷積網絡,卷積后得到另一個標簽,最后對右下方的紫色區域進行最后一次卷積操作。我們在這個 16×16×3 的小圖像上滑動窗口,卷積網絡運行了 4 次,於是輸出了了 4 個標簽。

結果發現,這 4 次卷積操作中很多計算都是重復的。所以執行滑動窗口的卷積時使得卷積網絡在這 4 次前向傳播過程中共享很多計算,尤其是在這一步操作中(編號 1),卷積網絡運行同樣的參數,使得相同的 5×5×16 過濾器進行卷積操作,得到 12×12×16 的輸出層。然后執行同樣的最大池化(編號 2) ,輸出結果 6×6×16。照舊應用 400 個 5×5 的過濾器(編號3),得到一個 2×2×400 的輸出層,現在輸出層為 2×2×400,而不是 1×1×400。應用 1×1 過濾器(編號 4)得到另一個 2×2×400 的輸出層。再做一次全連接的操作(編號 5),最終得到2×2×4 的輸出層,而不是 1×1×4。最終,在輸出層這 4 個子方塊中,藍色的是圖像左上部分14×14 的輸出(紅色箭頭標識),右上角方塊是圖像右上部分(綠色箭頭標識)的對應輸出,左下角方塊是輸入層左下角(橘色箭頭標識),也就是這個 14×14 區域經過卷積網絡處理后的結果,同樣,右下角這個方塊是卷積網絡處理輸入層右下角 14×14 區域(紫色箭頭標識)的結果。

如果你想了解具體的計算步驟,以綠色方塊為例,假設你剪切出這塊區域(編號 1),傳遞給卷積網絡,第一層的激活值就是這塊區域(編號 2),最大池化后的下一層的激活值是這塊區域(編號 3),這塊區域對應着后面幾層輸出的右上角方塊(編號 4, 5, 6)。
所以該卷積操作的原理是我們不需要把輸入圖像分割成四個子集,分別執行前向傳播,而是把它們作為一張圖片輸入給卷積網絡進行計算,其中的公共區域可以共享很多計算,就像這里我們看到的這個 4 個 14×14 的方塊一樣。

下面我們再看一個更大的圖片樣本,假如對一個 28×28×3 的圖片應用滑動窗口操作,如果以同樣的方式運行前向傳播,最后得到 8×8×4 的結果。跟上一個范例一樣,以 14×14 區域滑動窗口,首先在這個區域應用滑動窗口,其結果對應輸出層的左上角部分。接着以大小為2 的步幅不斷地向右移動窗口,直到第 8 個單元格,得到輸出層的第一行。然后向圖片下方移動,最終輸出這個 8×8×4 的結果。因為最大池化參數為 2,相當於以大小為 2 的步幅在原始圖片上應用神經網絡。

總結一下滑動窗口的實現過程,在圖片上剪切出一塊區域,假設它的大小是 14×14,把它輸入到卷積網絡。繼續輸入下一塊區域,大小同樣是 14×14,重復操作,直到某個區域識別到汽車。
但是正如在前一頁所看到的,我們不能依靠連續的卷積操作來識別圖片中的汽車,比如,我們可以對大小為 28×28 的整張圖片進行卷積操作,一次得到所有預測值,如果足夠幸運,神經網絡便可以識別出汽車的位置。

以上就是在卷積層上應用滑動窗口算法的內容,它提高了整個算法的效率。不過這種算法仍然存在一個缺點,就是邊界框的位置可能不夠准確。下面,我們將學習如何解決這個問題。
二 Bounding Box 預測
上面,你們學到了滑動窗口法的卷積實現,這個算法效率更高,但仍然存在問題,不能輸出最精准的邊界框。在這里,我們看看如何得到更精准的邊界框。

在滑動窗口法中,你取這些離散的位置集合,然后在它們上運行分類器,在這種情況下,這些邊界框沒有一個能完美匹配汽車位置,也許這個框(編號 1)是最匹配的了。還有看起來這個真實值,最完美的邊界框甚至不是方形,稍微有點長方形(紅色方框所示),長寬比有點向水平方向延伸,有沒有辦法讓這個算法輸出更精准的邊界框呢?

其中一個能得到更精准邊界框的算法是 YOLO 算法, YOLO(You only look once)意思是你只看一次,這是由 Joseph Redmon, Santosh Divvala, Ross Girshick 和 Ali Farhadi 提出的算法。
是這么做的,比如你的輸入圖像是 100×100 的,然后在圖像上放一個網格。為了介紹起來簡單一些,我用 3×3 網格,實際實現時會用更精細的網格,可能是 19×19。基本思路是使用圖像分類和定位算法,前面介紹過的,然后將算法應用到 9 個格子上。(基本思路是,采用圖像分類和定位算法,逐一應用在圖像的 9 個格子中。)更具體一點,你需要這樣定義訓練標簽,所以對於 9 個格子中的每一個指定一個標簽y,y是 8 維的,和你之前看到的一樣。

pc等於 0 或 1 取決於這個綠色格子中是否有圖像。然后bx、by、bℎ和bw作用就是,如果那個格子里有對象,那么就給出邊界框坐標。然后c1、c2和c3就是你想要識別的三個類別,背景類別不算,所以你嘗試在背景類別中識別行人、汽車和摩托車,那么1、c2和c3可以是行人、汽車和摩托車類別。這張圖里有 9 個格子,所以對於每個格子都有這么一個向量。

我們看看左上方格子,這里這個(編號 1),里面什么也沒有,所以左上格子的標簽向量y是:

然后這個格子(編號 2)的輸出標簽y也是一樣,這個格子(編號 3),還有其他什么也沒有的格子都一樣。
現在這個格子呢?講的更具體一點,這張圖有兩個對象, YOLO 算法做的就是,取兩個對象的中點,然后將這個對象分配給包含對象中點的格子。所以左邊的汽車就分配到這個格子上(編號 4),然后這輛 Condor(車型:神鷹)中點在這里,分配給這個格子(編號 6)。所以即使中心格子(編號 5)同時有兩輛車的一部分,我們就假裝中心格子沒有任何我們感興趣的對象,所以對於中心格子,分類標簽 y 和這個向量類似,和這個沒有對象的向量類似,即:

而對於這個格子,這個用綠色框起來的格子(編號 4),目標標簽就是這樣的,這里有一個對象,pc= 1,然后你寫出bx、by、bℎ和bw來指定邊界框位置,然后還有類別 1是行人,那么c1 = 0,類別 2 是汽車,所以c2 = 1,類別3是摩托車,則數值c3 = 0,即y為:
![]()
右邊這個格子(編號 6)也是類似的,因為這里確實有一個對象,它的向量應該是這個樣子的:

所以對於這里 9 個格子中任何一個,你都會得到一個 8 維輸出向量,因為這里是 3×3 的網格,所以有 9 個格子,總的輸出尺寸是 3×3×8,所以目標輸出是 3×3×8。因為這里有 3×3格子,然后對於每個格子,你都有一個 8 維向量y,所以目標輸出尺寸是 3×3×8。

對於這個例子中,左上格子是 1×1×8,對應的是 9 個格子中左上格子的輸出向量。所以對於這 3×3 中每一個位置而言,對於這 9 個格子,每個都對應一個 8 維輸出目標向量y,其中一些值可以是 dont care-s(即?),如果這里沒有對象的話。所以總的目標輸出,這個圖片的輸出標簽尺寸就是 3×3×8。

如果你現在要訓練一個輸入為 100×100×3 的神經網絡,現在這是輸入圖像,然后你有一個普通的卷積網絡,卷積層,最大池化層等等,最后你會有這個,選擇卷積層和最大池化層,這樣最后就映射到一個 3×3×8 輸出尺寸。所以你要做的是,有一個輸入x,就是這樣的輸入圖像,然后你有這些 3×3×8 的目標標簽y。當你用反向傳播訓練神經網絡時,將任意輸入x映射到這類輸出向量y。

所以這個算法的優點在於神經網絡可以輸出精確的邊界框,所以測試的時候,你做的是喂入輸入圖像x,然后跑正向傳播,直到你得到這個輸出y。然后對於這里 3×3 位置對應的 9個輸出,我們在輸出中展示過的,你就可以讀出 1 或 0(編號 1 位置),你就知道 9 個位置之一有個對象。如果那里有個對象,那個對象是什么(編號 3 位置),還有格子中這個對象的邊界框是什么(編號 2 位置)。只要每個格子中對象數目沒有超過 1 個,這個算法應該是沒問題的。一個格子中存在多個對象的問題,我們稍后再討論。但實踐中,我們這里用的是比較小的 3×3 網格,實踐中你可能會使用更精細的 19×19 網格,所以輸出就是 19×19×8。這樣的網格精細得多,那么多個對象分配到同一個格子得概率就小得多。
重申一下,把對象分配到一個格子的過程是,你觀察對象的中點,然后將這個對象分配到其中點所在的格子,所以即使對象可以橫跨多個格子,也只會被分配到 9 個格子其中之一,就是 3×3 網絡的其中一個格子,或者 19×19 網絡的其中一個格子。在 19×19 網格中,兩個對象的中點(圖中藍色點所示)處於同一個格子的概率就會更低。

所以要注意,首先這和圖像分類和定位算法非常像,我們在之前講過的,就是它顯式地輸出邊界框坐標,所以這能讓神經網絡輸出邊界框,可以具有任意寬高比,並且能輸出更精確的坐標,不會受到滑動窗口分類器的步長大小限制。其次,這是一個卷積實現,你並沒有在 3×3 網格上跑 9 次算法,或者,如果你用的是 19×19 的網格, 19 平方是 361 次,所以你不需要讓同一個算法跑 361 次。相反,這是單次卷積實現,但你使用了一個卷積網絡,有很多共享計算步驟,在處理這 3×3 計算中很多計算步驟是共享的,或者你的 19×19 的網格,所以這個算法效率很高。

事實上 YOLO 算法有一個好處,也是它受歡迎的原因,因為這是一個卷積實現,實際上它的運行速度非常快,可以達到實時識別。在結束之前我還想給你們分享一個小細節,如何編碼這些邊界框bx、by、bℎ和bw,我們在后面討論。
這里有兩輛車,我們有個 3×3 網格,我們以右邊的車為例(編號 1),紅色格子里有個對象,所以目標標簽y就是,pc= 1,然后bx、by、bℎ和bw,然后c1 = 0,c2 = 1,c3 = 0,即


在 YOLO 算法中,對於這個方框(編號 1 所示),我們約定左上這個點是(0,0),然后右下這個點是(1,1),要指定橙色中點的位置,bx大概是 0.4,因為它的位置大概是水平長度的0.4,然后by大概是 0.3,然后邊界框的高度用格子總體寬度的比例表示,所以這個紅框的寬度可能是藍線(編號 2 所示的藍線)的 90%,所以bh是 0.9,它的高度也許是格子總體高度的一半,這樣的話bw就是 0.5。換句話說, bx、by、bℎ和bw單位是相對於格子尺寸的比列,所以bx和by必須在 0 和 1 之間,因為從定義上看,橙色點位於對象分配到格子的范圍內,如果它不在 0 和 1 之間,如果它在方塊外,那么這個對象就應該分配到另一個格子上。這個值(bh和bw)可能會大於 1,特別是如果有一輛汽車的邊界框是這樣的(編號 3 所示),那么邊界框的寬度和高度有可能大於 1。
指定邊界框的方式有很多,但這種約定是比較合理的,如果你去讀 YOLO 的研究論文,YOLO 的研究工作有其他參數化的方式,可能效果會更好,我這里就只給出了一個合理的約定,用起來應該沒問題。不過還有其他更復雜的參數化方式,涉及到 sigmoid 函數,確保這個值(bx和by)介於0和1之間,然后使用指數參數化來確保這些(bh和bw)都是非負數,這個必須大於等於 0。還有其他更高級的參數化方式,可能效果要更好一點,但我這里講的辦法應該是管用的。
