這才是目標檢測YOLOv3的真實面目

大寫的ZDQ發表於2019-02-22

之前的兩篇YOLO的發展歷史
YOLOv1 https://blog.csdn.net/u010712012/article/details/85116365
YOLOv2 https://blog.csdn.net/u010712012/article/details/85274711

2018年又出現了YOLOv3,相比於SSD,FasterRCNN,RetinaNet,速度都是更快的,作者很皮的把YOLOv3的五角星打在了沒有橫座標的第二象限,藐視其他目標檢測演算法。
在這裡插入圖片描述
yolo_v3作為yolo系列目前最新的演算法,對之前的演算法既有保留又有改進。先分析一下yolo_v3上保留的東西:

  • “分而治之”,從yolo_v1開始,yolo演算法就是通過劃分單元格來做檢測,只是劃分的數量不一樣。
  • 採用"leaky ReLU"作為啟用函式。
  • 端到端進行訓練。一個loss function搞定訓練,只需關注輸入端和輸出端。
  • 從yolo_v2開始,yolo就用batch normalization作為正則化、加速收斂和避免過擬合的方法,把BN層和leaky relu層接到每一層卷積層之後。
  • 多尺度訓練。在速度和準確率之間tradeoff。想速度快點,可以犧牲準確率;想準確率高點兒,可以犧牲一點速度。

YOLOV3模型

這裡借鑑了一位大佬的部落格:https://blog.csdn.net/leviopku/article/details/82660381

在這裡插入圖片描述
上圖表示了yolo_v3整個yolo_body的結構,沒有包括把輸出解析整理成我們要的[box, class, score]。對於把輸出張量包裝成[box, class, score]那種形式,還需要寫一些指令碼,但這已經在神經網路結構之外了。

為了讓yolo_v3結構圖更好理解,對圖1做一些補充解釋:

DBL: 如圖1左下角所示,也就是程式碼中的Darknetconv2d_BN_Leaky,是yolo_v3的基本元件。就是卷積+BN+Leaky relu。對於v3來說,BN和leaky relu已經是和卷積層不可分離的部分了(最後一層卷積除外),共同構成了最小元件。

resn:n代表數字,有res1,res2, … ,res8等等,表示這個res_block裡含有多少個res_unit。這是yolo_v3的大元件,yolo_v3開始借鑑了ResNet的殘差結構,使用這種結構可以讓網路結構更深(從v2的darknet-19上升到v3的darknet-53,前者沒有殘差結構)。對於res_block的解釋,可以在圖1的右下角直觀看到,其基本元件也是DBL。

concat:張量拼接。將darknet中間層和後面的某一層的上取樣進行拼接。拼接的操作和殘差層add的操作是不一樣的,拼接會擴充張量的維度,而add只是直接相加不會導致張量維度的改變。

可以借鑑netron來分析網路層,整個yolo_v3_body包含252層,組成如下:
在這裡插入圖片描述
根據上表可以得出,對於程式碼層面的layers數量一共有252層,包括add層23層(主要用於res_block的構成,每個res_unit需要一個add層,一共有1+2+8+8+4=23層)。除此之外,BN層和LeakyReLU層數量完全一樣(72層),在網路結構中的表現為:每一層BN後面都會接一層LeakyReLU。卷積層一共有75層,其中有72層後面都會接BN+LeakyReLU的組合構成基本元件DBL。看結構圖,可以發現上取樣和concat都有2次,和表格分析中對應上。每個res_block都會用上一個零填充,一共有5個res_block。

1. backbone

整個v3結構裡面,是沒有池化層和全連線層的。前向傳播過程中,張量的尺寸變換是通過改變卷積核的步長來實現的,比如stride=(2, 2),這就等於將影像邊長縮小了一半(即面積縮小到原來的1/4)。在yolo_v2中,要經歷5次縮小,會將特徵圖縮小到原輸入尺寸的1/251/2^5 ,即1/32。輸入為416x416,則輸出為13x13(416/32=13)。yolo_v3也和v2一樣,backbone都會將輸出特徵圖縮小到輸入的1/32。所以,通常都要求輸入圖片是32的倍數。可以對比v2和v3的backbone看看:(DarkNet-19 與 DarkNet-53)
在這裡插入圖片描述
yolo_v2中對於前向過程中張量尺寸變換,都是通過最大池化來進行,一共有5次。而v3是通過卷積核增大步長來進行,也是5次。(darknet-53最後面有一個全域性平均池化,在yolo-v3裡面沒有這一層,所以張量維度變化只考慮前面那5次)。
這也是416x416輸入得到13x13輸出的原因。從圖2可以看出,darknet-19是不存在殘差結構(resblock,從resnet上借鑑過來)的,和VGG是同型別的backbone(屬於上一代CNN結構),而darknet-53是可以和resnet-152正面剛的backbone,看下錶:
在這裡插入圖片描述
從上表也可以看出,darknet-19在速度上仍然佔據很大的優勢。其實在其他細節也可以看出(比如bounding box prior採用k=9),yolo_v3並沒有那麼追求速度,而是在保證實時性(fps>60)的基礎上追求performance。不過前面也說了,你要想更快,還有一個tiny-darknet作為backbone可以替代darknet-53,在官方程式碼裡用一行程式碼就可以實現切換backbone。搭用tiny-darknet的yolo,也就是tiny-yolo在輕量和高速兩個特點上,顯然是state of the art級別,tiny-darknet是和squeezeNet正面剛的網路,詳情可以看下錶:
在這裡插入圖片描述

2.輸出Output

網路結構放大版本:
在這裡插入圖片描述
三種不同的scale如下:
在這裡插入圖片描述
yolo v3輸出了3個不同尺度的feature map,如上圖所示的y1, y2, y3。這也是v3論文中提到的為數不多的改進點:predictions across scales,這個借鑑了FPN(feature pyramid networks),採用多尺度來對不同size的目標進行檢測,越精細的grid cell就可以檢測出越精細的物體。

y1,y2和y3的深度都是255,邊長的規律是13:26:52。對於COCO類別而言,有80個種類,所以每個box應該對每個種類都輸出一個概率。yolo v3設定的是每個網格單元預測3個box,所以每個box需要有(x, y, w, h, confidence)五個基本引數,然後還要有80個類別的概率。所以3*(5 + 80) = 255。這個255就是這麼來的。(還記得yolo v1的輸出張量嗎? 7x7x30,只能識別20類物體,而且每個cell只能預測2個box,和v3比起來就像老人機和Mate X一樣)

v3用上取樣的方法來實現這種多尺度的feature map,可以結合圖1和圖2右邊來看,圖1中concat連線的兩個張量是具有一樣尺度的(兩處拼接分別是26x26尺度拼接和52x52尺度拼接,通過(2, 2)上取樣來保證concat拼接的張量尺度相同)。作者並沒有像SSD那樣直接採用backbone中間層的處理結果作為feature map的輸出,而是和後面網路層的上取樣結果進行一個拼接之後的處理結果作為feature map。為什麼這麼做呢? 我感覺是有點玄學在裡面,一方面避免和其他演算法做法重合,另一方面這也許是試驗之後並且結果證明更好的選擇,再者有可能就是因為這麼做比較節省模型size的。

3.some tricks

上文把yolo_v3的結構討論了一下,下文將對yolo v3的若干細節進行剖析。

Bounding Box Prediction b-box預測手段是v3論文中提到的又一個亮點。先回憶一下v2的b-box預測:想借鑑faster R-CNN RPN中的anchor機制,但不屑於手動設定anchor prior(模板框),於是用維度聚類的方法來確定anchor box prior(模板框),最後發現聚類之後確定的prior在k=5也能夠又不錯的表現,於是就選用k=5。後來呢,v2又嫌棄anchor機制線性迴歸的不穩定性(因為迴歸的offset可以使box偏移到圖片的任何地方),所以v2最後選用了自己的方法:直接預測相對位置。預測出b-box中心點相對於網格單元左上角的相對座標。

在這裡插入圖片描述
在這裡插入圖片描述
yolo v2直接predict出(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_c),並不像RPN中anchor機制那樣去遍歷每一個pixel。可以從上面的公式看出,b-box的位置大小和confidence都可以通過(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_c)計算得來,v2相當直接predict出了b-box的位置大小和confidence。box寬和高的預測是受prior影響的,對於v2而言,b-box prior數為5,在論文中並沒有說明拋棄anchor機制之後是否拋棄了聚類得到的prior(沒看程式碼,所以我不能確定),如果prior數繼續為5,那麼v2需要對不同prior預測出和對於v3而言,在prior這裡的處理有明確解釋:選用的b-box priors 的k=9,對於tiny-yolo的話,k=6.

每個anchor prior(名字叫anchor prior,但並不是用anchor機制)就是兩個數字組成的,一個代表高度另一個代表寬度。v3對b-box進行預測的時候,採用了logistic regression。這一波操作6得就像RPN中的線性迴歸調整b-box。v3每次對b-box進行predict時,輸出和v2一樣都是(tx,ty,tw,th,tc)(t_x,t_y,t_w,t_h,t_c),然後通過公式計算出絕對的(x, y, w, h, c)。logistic迴歸用於對anchor包圍的部分進行一個目標性評分(objectness score),即這塊位置是目標的可能性有多大。這一步是在predict之前進行的,可以去掉不必要anchor,可以減少計算量。作者在論文種的描述如下:

If the bounding box prior is not the best but does overlap a ground truth object by more than some threshold we ignore the prediction, following[17]. We use the threshold of 0.5. Unlike [17] our system only assigns one bounding box prior for each ground truth object.

如果模板框不是最佳的即使它超過我們設定的閾值,我們還是不會對它進行predict。

不同於faster R-CNN的是,yolo_v3只會對1個prior進行操作,也就是那個最佳prior。而logistic迴歸就是用來從9個anchor priors中找到objectness score(目標存在可能性得分)最高的那一個。logistic迴歸就是用曲線對prior相對於 objectness score對映關係的線性建模。

4.loss function

對掌握Yolo來講,loss function不可謂不重要。在v3的論文裡沒有明確提所用的損失函式,確切地說,yolo系列論文裡面只有yolo v1明確提了損失函式的公式。對於yolo這樣一種討喜的目標檢測演算法,就連損失函式都非常討喜。在v1中使用了一種叫sum-square error的損失計算方法,就是簡單的差方相加而已。想詳細瞭解的可以看我篇頭關於v1解釋的博文。我們知道,在目標檢測任務裡,有幾個關鍵資訊是需要確定的:

(x,y),(w,h),class,confidence(x,y),(w,h),class,confidence

根據關鍵資訊的特點可以分為上述四類,損失函式應該由各自特點確定。最後加到一起就可以組成最終的loss_function了,也就是一個loss_function搞定端到端的訓練。可以從程式碼分析出v3的損失函式,同樣也是對以上四類,不過相比於v1中簡單的總方誤差,還是有一些調整的:

xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2],
                                                                       from_logits=True)
wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])
confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + \
                          (1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5],
                                                                    from_logits=True) * ignore_mask
class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)

xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss + class_loss

以上是一段keras框架描述的yolo v3 的loss_function程式碼。忽略恆定係數不看,可以從上述程式碼看出:除了w, h的損失函式依然採用總方誤差之外,其他部分的損失函式用的是二值交叉熵。最後加到一起。那麼這個binary_crossentropy又是個什麼玩意兒呢?就是一個最簡單的交叉熵而已,一般用於二分類,這裡的兩種二分類類別可以理解為"對和不對"這兩種.

總結:

改進之處:

  • 多尺度預測 (類FPN)
  • 更好的基礎分類網路(類ResNet)和分類器

分類器-類別預測:

YOLOv3不使用Softmax對每個框進行分類,主要考慮因素有兩個:

  • Softmax使得每個框分配一個類別(score最大的一個),而對於Open Images這種資料集,目標可能有重疊的類別標籤,因此Softmax不適用於多標籤分類。
  • Softmax可被獨立的多個logistic分類器替代,且準確率不會下降。
  • 分類損失採用binary cross-entropy loss.

多尺度預測

每種尺度預測3個box, anchor的設計方式仍然使用聚類,得到9個聚類中心,將其按照大小均分給3中尺度.

  • 尺度1: 在基礎網路之後新增一些卷積層再輸出box資訊.
  • 尺度2: 從尺度1中的倒數第二層的卷積層上取樣(x2)再與最後一個16x16大小的特徵圖相加,再次通過多個卷積後輸出box資訊.相比尺度1變大兩倍.
  • 尺度3: 與尺度2類似,使用了32x32大小的特徵圖.

下一代YOLO長啥樣?

mAP會繼續提高。隨著模型訓練越來越高效,神經網路層級的不斷加深,資訊抽象能力的不斷提高,以及一些小的修修補補,未來的目標檢測應用mAP會不斷提升。

實時檢測會成為標配。目前所謂的“實時”,工業界是不認可的。為什麼呢,因為學術圈的人,驗證模型都是建立在TitanX或者Tesla這類強大的獨立顯示卡上,而實際的潛在應用場景中,例如無人機/掃地/服務機器人/視訊監控等,是不會配備這些“重型裝備”的。所以,在嵌入式裝置中,如FPGA,TX2,輕量級CPU上,能達到的實時,才是貨真價實的。

模型小型化成為重要分支。類似於tiny YOLO的模型分支會受到更多關注。模型的小型化是應用到嵌入式裝置的重要前提。而物聯網機器人無人機等領域還是以嵌入式裝置為主的。模型剪枝/二值化/權值共享等手段會更廣泛的使用。

說點題外話:

YOLO讓人聯想到龍珠裡的沙魯(cell),不斷吸收同化對手,進化自己,提升戰鬥力:YOLOv1吸收了SSD的長處(加了 BN 層,擴大輸入維度,使用了 Anchor,訓練的時候資料增強),進化到了YOLOv2;

吸收DSSD和FPN的長處, 仿ResNet的Darknet-53,仿SqueezeNet的縱橫交叉網路,又進化到YOLO第三形態。

但是,我相信這一定不是最終形態。。。讓我們拭目以待吧!

在這裡插入圖片描述

參考:https://blog.csdn.net/leviopku/article/details/82660381
https://www.cnblogs.com/makefile/p/YOLOv3.html
https://zhuanlan.zhihu.com/p/35394369

相關文章