yolo系列之yolo v3【深度解析】


yolo系列之yolo v3【深度解析】

 

版權申明:轉載和引用圖片,都必須經過書面同意。獲得留言同意即可
本文使用圖片多為本人所畫,需要高清圖片可以留言聯系我,先點贊后取圖
這篇博文比較推薦的yolo v3代碼是qwe的keras版本,復現比較容易,代碼相對來說比較容易理解。同學們可以結合代碼和博文共同理解v3的精髓。
github地址:https://github.com/qqwweee/keras-yolo3


前言

前言就是嘮嘮嗑,想直接看干貨可以跳過前言,直接看Yolo v3。
yolo_v3是我最近一段時間主攻的算法,寫下博客,以作分享交流。
看過yolov3論文的應該都知道,這篇論文寫得很隨意,很多亮點都被作者都是草草描述。很多騷年入手yolo算法都是從v3才開始,這是不可能掌握yolo精髓的,因為v3很多東西是保留v2甚至v1的東西,而且v3的論文寫得很隨心。想深入了解yolo_v3算法,是有必要先了解v1和v2的。以下是我關於v1和v2算法解析所寫的文章:
v1算法解析:《yolo系列之yolo v1
v2算法解析:《yolo系列之yolo v2
yolo_v3作為yolo系列目前最新的算法,對之前的算法既有保留又有改進。先分析一下yolo_v3上保留的東西:

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

yolo每一代的提升很大一部分決定於backbone網絡的提升,從v2的darknet-19到v3的darknet-53。yolo_v3還提供替換backbone——tiny darknet。要想性能牛叉,backbone可以用Darknet-53,要想輕量高速,可以用tiny-darknet。總之,yolo就是天生“靈活”,所以特別適合作為工程算法。
當然,yolo_v3在之前的算法上保留的點不可能只有上述幾點。由於本文章主要針對yolo_v3進行剖析,不便跑題,下面切入正題。


YOLO v3

網上關於yolo v3算法分析的文章一大堆,但大部分看着都不爽,為什么呢?因為他們沒有這個玩意兒:

圖1. yolo_v3結構圖

yolo系列里面,作者只在v1的論文里給出了結構圖,而v2和v3的論文里都沒有結構圖,這使得讀者對后兩代yolo結構的理解變得比較難。but,對於yolo學習者來說,腦子里沒有一個清晰的結構圖,就別說自己懂yolo了。上圖是我根據官方代碼和官方論文以及模型結構可視化工具等經過好幾個小時畫出來的,修訂過幾個版本。所以,上圖的准確性是可以保證的

這里推薦的模型結構可視化工具是:Netron
netron方便好用,可以直觀看到yolo_v3的實際計算結構,精細到卷積層。But,要進一步在人性化的角度分析v3的結構圖,還需要結合論文和代碼。對此,我是下了不少功夫。
上圖表示了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

表0. yolo_v3_layers

根據表0可以得出,對於代碼層面的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^51/25,即1/32。輸入為416x416,則輸出為13x13(416/32=13)。
yolo_v3也和v2一樣,backbone都會將輸出特征圖縮小到輸入的1/32。所以,通常都要求輸入圖片是32的倍數。可以對比v2和v3的backbone看看:(DarkNet-19 與 DarkNet-53)

圖2. darknet-19 vs 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,看下表:

這里寫圖片描述

表1. backbone對比圖

從上表也可以看出,darknet-19在速度上仍然占據很大的優勢。其實在其他細節也可以看出(比如bounding box prior采用k=9),yolo_v3並沒有那么追求速度,而是在保證實時性(fps>36)的基礎上追求performance。不過前面也說了,你要想更快,還有一個tiny-darknet作為backbone可以替代darknet-53,在官方代碼里用一行代碼就可以實現切換backbone。搭用tiny-darknet的yolo,也就是tiny-yolo在輕量和高速兩個特點上,顯然是state of the art級別,tiny-darknet是和squeezeNet正面剛的網絡,詳情可以看下表: 

表2. 輕量級對比圖

所以,有了yolo v3,就真的用不着yolo v2了,更用不着yolo v1了。這也是[yolo官方網站](https://pjreddie.com/darknet/),在v3出來以后,就沒提供v1和v2代碼下載鏈接的原因了。


2. Output

對於圖1而言,更值得關注的是輸出張量:

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比起來就像老人機和iphoneX一樣)
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中心點相對於網格單元左上角的相對坐標。

公式1

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

10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
  • 1

每個anchor prior(名字叫anchor prior,但並不是用anchor機制)就是兩個數字組成的,一個代表高度另一個代表寬度。
v3對b-box進行預測的時候,采用了logistic regression。這一波操作sao得就像RPN中的線性回歸調整b-box。v3每次對b-box進行predict時,輸出和v2一樣都是(txt_xtxtyt_ytytwt_wtwtht_hthtot_oto),然后通過公式1計算出絕對的(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映射關系的線性建模。
疑問解答和說明:

在評論里有同學問我關於輸出的問題,看來我在這里沒有說的很清楚。了解v3輸出的輸出是至關重要的。

第一點, 9個anchor會被三個輸出張量平分的。根據大中小三種size各自取自己的anchor。

第二點,每個輸出y在每個自己的網格都會輸出3個預測框,這3個框是9除以3得到的,這是作者設置
的,我們可以從輸出張量的維度來看,13x13x255。255是怎么來的呢,3*(5+80)。80表示80個種類,5表
示位置信息和置信度,3表示要輸出3個prediction。在代碼上來看,3*(5+80)中的3是直接由
num_anchors//3得到的。

第三點,作者使用了logistic回歸來對每個anchor包圍的內容進行了一個目標性評分(objectness score)。
根據目標性評分來選擇anchor prior進行predict,而不是所有anchor prior都會有輸出。

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(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又是個什么玩意兒呢?就是一個最簡單的交叉熵而已,一般用於二分類,這里的兩種二分類類別可以理解為"對和不對"這兩種。關於binary_crossentropy的公式詳情可參考博文《常見的損失函數》。


總結

v3毫無疑問現在成為了工程界首選的檢測算法之一了,結構清晰,實時性好。這是我十分安利的目標檢測算法,更值得贊揚的是,yolo_v3給了近乎白痴的復現教程,這種氣量在頂層算法研究者中並不常見。你可以很快的用上v3,但你不能很快地懂v3,我花了近一個月的時間才對v3有一個清晰的了解(可能是我悟性不夠哈哈哈)。在算法學習的過程中,去一些浮躁,好好理解算法比只會用算法難得很多。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM