從零開始用 PyTorch 實作 YOLO (v3) 是什麼體驗(一)
代碼基于 Python 3.5, 和 PyTorch 0.4. 代碼釋出在 Github repo 上。
本體驗分為5個部分:
- 第1部分(本文):了解 YOLO 的原理
- 第2部分:建立網絡結構
- 第3部分:實作網絡的前向傳遞
- 第4部分:目标分門檻值和非極大值抑制
- 第5部分:部落客有話說
YOLO是神馬?
YOLO 的全稱是 You Only Look Once。它是一種基于深度卷積神經網絡的目标檢測器。我們先了解 YOLO 的工作原理。
全卷積神經網絡 FCN
YOLO 僅僅使用卷積層,這種僅适用卷基層的網絡我們稱之為全卷積神經網絡(Fully Convolutional Network)。YOLO 擁有 75 個卷積層,還有 skip connections 和 上采樣 Upsampling 層。它使用步幅為 2 的卷積層對特征圖進行下采樣,而不是使用池化層,這有助于防止通常由池化導緻的低級特征丢失。
作為 FCN,YOLO 對于輸入圖像的大小并沒有要求。然而,在實踐中,我們可能想要固定輸入的大小,以防止後續一些問題的出現。這其中的一個重要原因是:如果我們希望按 batch 處理圖像(batch 由 GPU 并行處理,這樣可以提升速度),我們就需要固定所有圖像的高度和寬度。這就需要将多個圖像整合進一個大的 batch(将将許多 PyTorch Tensors 合并成一個)。
YOLO 通過 **stride(步幅)**對圖像進行上采樣。例如,如果網絡的步幅是 32,則大小為 416×416 的輸入圖像将産生 13×13 的輸出。
輸出
一般來講,卷積層所學習的特征會被傳遞到分類器/回歸器進行預測(邊界框的坐标、類别标簽等)。
在 YOLO 中,預測是通過 1 x 1 的卷積層完成的。
現在要注意的是,我們的輸出都是特征圖(feature map),因為使用 1 x 1 的卷基層,是以每次輸出的特征圖都和之前的特征圖是一樣大小的。在 YOLO v3上,預測圖就是每個可以預測固定數量邊界框的單元格。
對于網絡的深度,我們的特征圖包含 (B x (5+C)) 個條目, B 代表每個單元可以預測的邊界框數量。根據 YOLO 的論文,這些 B 邊界框中的每一個都可能專門用于檢測某種對象。每個邊界框都有 5+C 個屬性,分别描述每個邊界框的中心坐标、次元、objectness 分數和 C 類置信度。YOLO v3 在每個單元中預測 3 個邊界框。
如果對象的中心位于單元格的感受野内,你會希望特征圖的每個單元格都可以通過其中一個邊界框預測對象。(感受野是輸入圖像對于單元格可見的區域。)
這與 YOLO 是如何訓練的有關,隻有一個邊界框負責檢測任意給定對象。首先,我們必須确定這個邊界框屬于哪個單元格。
是以,我們需要切分輸入圖像,把它拆成次元等于最終特征圖的網格。
讓我們思考下面一個例子,其中輸入圖像大小是 416×416,網絡的步幅是 32。如之前所述,特征圖的次元會是 13×13。随後,我們将輸入圖像分為 13×13 個網格。
輸入圖像中包含了真值對象框中心的網格會作為負責預測對象的單元格。在圖像中,它是被标記為紅色的單元格,其中包含了真值框的中心(被标記為黃色)。
現在,紅色單元格是網格中第七行的第七個。我們現在使特征圖中第七行第七個單元格(特征圖中的對應單元格)作為檢測狗的單元。
現在,這個單元格可以預測三個邊界框。哪個将會配置設定給狗的真值标簽?為了了解這一點,我們必須了解錨點的概念。
請注意,我們在這裡讨論的單元格是預測特征圖上的單元格,我們将輸入圖像分隔成網格,以确定預測特征圖的哪個單元格負責預測對象。
錨點框(Anchor Box)
預測邊界框的寬度和高度看起來非常合理,但在實踐中,訓練會帶來不穩定的梯度。是以,現在大部分目标檢測器都是預測對數空間(log-space)變換,或者預測與預訓練預設邊界框(即錨點)之間的偏移。
然後,這些變換被應用到錨點框來獲得預測。YOLO v3 有三個錨點,是以每個單元格會預測 3 個邊界框。
回到前面的問題,負責檢測狗的邊界框的錨點有最高的 IoU,且有真值框。
預測
下面的公式描述了網絡輸出是如何轉換,以獲得邊界框預測結果的。
中心坐标
注意:我們使用 sigmoid 函數進行中心坐标預測。這使得輸出值在 0 和 1 之間。原因如下:
正常情況下,YOLO 不會預測邊界框中心的确切坐标。它預測:
- 與預測目标的網格單元左上角相關的偏移;
- 使用特征圖單元的次元(1)進行歸一化的偏移。
以我們的圖像為例。如果中心的預測是 (0.4, 0.7),則中心在 13 x 13 特征圖上的坐标是 (6.4, 6.7)(紅色單元的左上角坐标是 (6,6))。
但是,如果預測到的 x,y 坐标大于 1,比如 (1.2, 0.7)。那麼中心坐标是 (7.2, 6.7)。注意該中心在紅色單元右側的單元中,或第 7 行的第 8 個單元。這打破了 YOLO 背後的理論,因為如果我們假設紅色框負責預測目标狗,那麼狗的中心必須在紅色單元中,不應該在它旁邊的網格單元中。
是以,為了解決這個問題,我們對輸出執行 sigmoid 函數,将輸出壓縮到區間 0 到 1 之間,有效確定中心處于執行預測的網格單元中。
邊界框的次元
我們對輸出執行對數空間變換,然後乘錨點,來預測邊界框的次元。
得出的預測 bw 和 bh 使用圖像的高和寬進行歸一化。即,如果包含目标(狗)的框的預測 bx 和 by 是 (0.3, 0.8),那麼 13 x 13 特征圖的實際寬和高是 (13 x 0.3, 13 x 0.8)。
Objectness 分數
Object 分數表示目标在邊界框内的機率。紅色網格和相鄰網格的 Object 分數應該接近 1,而角落處的網格的 Object 分數可能接近 0。
objectness 分數的計算也使用 sigmoid 函數,是以它可以被了解為機率。
類别置信度
類别置信度表示檢測到的對象屬于某個類别的機率(如狗、貓、香蕉、汽車等)。在 v3 之前,YOLO 需要對類别分數執行 softmax 函數操作。
但是,YOLO v3 舍棄了這種設計,作者選擇使用 sigmoid 函數。因為對類别分數執行 softmax 操作的前提是類别是互斥的。簡言之,如果對象屬于一個類别,那麼必須確定其不屬于另一個類别。這在我們設定檢測器的 COCO 資料集上是正确的。但是,當出現類别「女性」(Women)和「人」(Person)時,該假設不可行。這就是作者選擇不使用 Softmax 激活函數的原因。
在不同尺度上的預測
YOLO v3 在 3 個不同尺度上進行預測。檢測層用于在三個不同大小的特征圖上執行預測,特征圖步幅分别是 32、16、8。這意味着,當輸入圖像大小是 416 x 416 時,我們在尺度 13 x 13、26 x 26 和 52 x 52 上執行檢測。
該網絡在第一個檢測層之前對輸入圖像執行下采樣,檢測層使用步幅為 32 的層的特征圖執行檢測。随後在執行因子為 2 的上采樣後,并與前一個層的特征圖(特征圖大小相同)拼接。另一個檢測在步幅為 16 的層中執行。重複同樣的上采樣步驟,最後一個檢測在步幅為 8 的層中執行。
在每個尺度上,每個單元使用 3 個錨點預測 3 個邊界框,錨點的總數為 9(不同尺度的錨點不同)。
作者稱這幫助 YOLO v3 在檢測較小目标時取得更好的性能,而這正是 YOLO 之前版本經常被抱怨的地方。上采樣可以幫助該網絡學習細粒度特征,幫助檢測較小目标。
輸出處理
對于大小為 416 x 416 的圖像,YOLO 預測 ((52 x 52) (26 x 26) 13 x 13)) x 3 = 10647 個邊界框。但是,我們的示例中隻有一個對象——一隻狗。那麼我們怎麼才能将檢測次數從 10647 減少到 1 呢?
目标置信度門檻值:首先,我們根據它們的 objectness 分數過濾邊界框。通常,分數低于門檻值的邊界框會被忽略。
非極大值抑制:非極大值抑制(NMS)可解決對同一個圖像的多次檢測的問題。例如,紅色網格單元的 3 個邊界框可以檢測一個框,或者臨近網格可檢測相同對象。
實作
YOLO 隻能檢測出屬于訓練所用資料集中類别的對象。我們的檢測器将使用官方權重檔案,這些權重通過在 COCO 資料集上訓練網絡而獲得,是以我們可以檢測 80 個對象類别。
第一部分到此結束。
原文請見:How to implement a YOLO (v3) object detector from scratch in PyTorch