github地址:https://github.com/qqwweee/keras-yolo3
yolo_v3是我最近一段時間主攻的算法菠发,寫下博客杂彭,以作分享交流。
看過yolov3論文的應該都知道筐高,這篇論文寫得很隨意瓮孙,很多亮點都被作者都是草草描述卖漫。很多騷年入手yolo算法都是從v3才開始捞魁,這是不可能掌握yolo精髓的在张,因為v3很多東西是保留v2甚至v1的東西用含,而且v3的論文寫得很隨心。想深入了解yolo_v3算法帮匾,是有必要先了解v1和v2的啄骇。以下是我關(guān)于v1和v2算法解析所寫的文章:
v1算法解析:《yolo系列之yolo v1》
v2算法解析:《yolo系列之yolo v2》
yolo_v3作為yolo系列目前最新的算法,對之前的算法既有保留又有改進瘟斜。先分析一下yolo_v3上保留的東西:
- “分而治之”缸夹,從yolo_v1開始,yolo算法就是通過劃分單元格來做檢測螺句,只是劃分的數(shù)量不一樣虽惭。
- 采用"leaky ReLU"作為激活函數(shù)。
- 端到端進行訓練蛇尚。一個loss function搞定訓練芽唇,只需關(guān)注輸入端和輸出端。
- 從yolo_v2開始佣蓉,yolo就用batch normalization作為正則化披摄、加速收斂和避免過擬合的方法,把BN層和leaky relu層接到每一層卷積層之后勇凭。
- 多尺度訓練疚膊。在速度和準確率之間tradeoff。想速度快點虾标,可以犧牲準確率寓盗;想準確率高點兒,可以犧牲一點速度。
yolo每一代的提升很大一部分決定于backbone網(wǎng)絡的提升傀蚌,從v2的darknet-19到v3的darknet-53基显。yolo_v3還提供替換backbone——tiny darknet。要想性能牛叉善炫,backbone可以用Darknet-53撩幽,要想輕量高速,可以用tiny-darknet箩艺〈茏恚總之,yolo就是天生“靈活”艺谆,所以特別適合作為工程算法榨惰。
當然,yolo_v3在之前的算法上保留的點不可能只有上述幾點静汤。由于本文章主要針對yolo_v3進行剖析琅催,不便跑題,下面切入正題虫给。
YOLO v3
網(wǎng)上關(guān)于yolo v3算法分析的文章一大堆藤抡,但大部分看著都不爽,為什么呢狰右?因為他們沒有這個玩意兒:
yolo系列里面杰捂,作者只在v1的論文里給出了結(jié)構(gòu)圖,而v2和v3的論文里都沒有結(jié)構(gòu)圖棋蚌,這使得讀者對后兩代yolo結(jié)構(gòu)的理解變得比較難嫁佳。but,對于yolo學習者來說谷暮,腦子里沒有一個清晰的結(jié)構(gòu)圖蒿往,就別說自己懂yolo了。上圖是我根據(jù)官方代碼和官方論文以及模型結(jié)構(gòu)可視化工具等經(jīng)過好幾個小時畫出來的湿弦,修訂過幾個版本瓤漏。所以, 上圖的準確性是可以保證的颊埃。
這里推薦的模型結(jié)構(gòu)可視化工具是:Netron
netron方便好用蔬充,可以直觀看到y(tǒng)olo_v3的實際計算結(jié)構(gòu),精細到卷積層班利。But饥漫,要進一步在人性化的角度分析v3的結(jié)構(gòu)圖,還需要結(jié)合論文和代碼罗标。對此庸队,我是下了不少功夫积蜻。
上圖表示了yolo_v3整個yolo_body的結(jié)構(gòu),沒有包括把輸出解析整理成咱要的[box, class, score]彻消。對于把輸出張量包裝成[box, class, score]那種形式竿拆,還需要寫一些腳本,但這已經(jīng)在神經(jīng)網(wǎng)絡結(jié)構(gòu)之外了(我后面會詳細解釋這波操作)宾尚。
為了讓yolo_v3結(jié)構(gòu)圖更好理解丙笋,我對圖1做一些補充解釋:
DBL: 如圖1左下角所示,也就是代碼中的Darknetconv2d_BN_Leaky央勒,是yolo_v3的基本組件不见。就是卷積+BN+Leaky relu澳化。對于v3來說崔步,BN和leaky relu已經(jīng)是和卷積層不可分離的部分了(最后一層卷積除外),共同構(gòu)成了最小組件缎谷。
resn:n代表數(shù)字井濒,有res1,res2, … ,res8等等列林,表示這個res_block里含有多少個res_unit瑞你。這是yolo_v3的大組件,yolo_v3開始借鑒了ResNet的殘差結(jié)構(gòu)希痴,使用這種結(jié)構(gòu)可以讓網(wǎng)絡結(jié)構(gòu)更深(從v2的darknet-19上升到v3的darknet-53者甲,前者沒有殘差結(jié)構(gòu))。對于res_block的解釋砌创,可以在圖1的右下角直觀看到虏缸,其基本組件也是DBL。
concat:張量拼接嫩实。將darknet中間層和后面的某一層的上采樣進行拼接刽辙。拼接的操作和殘差層add的操作是不一樣的,拼接會擴充張量的維度甲献,而add只是直接相加不會導致張量維度的改變宰缤。
我們可以借鑒netron來分析網(wǎng)絡層,整個yolo_v3_body包含252層晃洒,組成如下:
根據(jù)表0可以得出慨灭,對于代碼層面的layers數(shù)量一共有252層,包括add層23層(主要用于res_block的構(gòu)成球及,每個res_unit需要一個add層氧骤,一共有1+2+8+8+4=23層)。
除此之外桶略,BN層和LeakyReLU層數(shù)量完全一樣(72層)语淘,在網(wǎng)絡結(jié)構(gòu)中的表現(xiàn)為: 每一層BN后面都會接一層LeakyReLU诲宇。卷積層一共有75層,其中有72層后面都會接BN+LeakyReLU的組合構(gòu)成基本組件DBL惶翻」美叮看結(jié)構(gòu)圖,可以發(fā)現(xiàn)上采樣和concat都有2次吕粗,和表格分析中對應上纺荧。每個res_block都會用上一個零填充,一共有5個res_block颅筋。
1. backbone
整個v3結(jié)構(gòu)里面宙暇,是沒有池化層和全連接層的。前向傳播過程中议泵,張量的尺寸變換是通過改變卷積核的步長來實現(xiàn)的占贫,比如stride=(2, 2),這就等于將圖像邊長縮小了一半(即面積縮小到原來的1/4)先口。在yolo_v2中型奥,要經(jīng)歷5次縮小,會將特征圖縮小到原輸入尺寸的1 / 2 5 1/2^51/25碉京,即1/32厢汹。輸入為416x416,則輸出為13x13(416/32=13)谐宙。
yolo_v3也和v2一樣烫葬,backbone都會將輸出特征圖縮小到輸入的1/32。所以凡蜻,通常都要求輸入圖片是32的倍數(shù)搭综。可以對比v2和v3的backbone看看:(DarkNet-19 與 DarkNet-53)
yolo_v2中對于前向過程中張量尺寸變換咽瓷,都是通過 最大池化來進行设凹,一共有5次。而v3是通過卷積核 增大步長來進行茅姜,也是5次闪朱。(darknet-53最后面有一個全局平均池化,在yolo-v3里面沒有這一層钻洒,所以張量維度變化只考慮前面那5次)奋姿。
這也是416x416輸入得到13x13輸出的原因。從圖2可以看出素标,darknet-19是不存在殘差結(jié)構(gòu)(resblock称诗,從resnet上借鑒過來)的,和VGG是同類型的backbone(屬于上一代CNN結(jié)構(gòu))头遭,而darknet-53是可以和resnet-152正面剛的backbone寓免,看下表:
從上表也可以看出癣诱,darknet-19在速度上仍然占據(jù)很大的優(yōu)勢。其實在其他細節(jié)也可以看出(比如bounding box prior采用k=9)袜香, yolo_v3并沒有那么追求速度撕予,而是在保證實時性(fps>36)的基礎上追求performance。不過前面也說了蜈首,你要想更快实抡,還有一個 tiny-darknet作為backbone可以替代darknet-53,在官方代碼里用一行代碼就可以實現(xiàn)切換backbone欢策。搭用tiny-darknet的yolo吆寨,也就是tiny-yolo在輕量和高速兩個特點上,顯然是state of the art級別踩寇,tiny-darknet是和squeezeNet正面剛的網(wǎng)絡啄清,詳情可以看下表:
所以,有了yolo v3姑荷,就真的用不著yolo v2了盒延,更用不著yolo v1了。這也是yolo官方網(wǎng)站鼠冕,在v3出來以后,就沒提供v1和v2代碼下載鏈接的原因了胯盯。
2. Output
對于圖1而言懈费,更值得關(guān)注的是輸出張量:
yolo v3輸出了3個不同尺度的feature map,如上圖所示的y1, y2, y3博脑。這也是v3論文中提到的為數(shù)不多的改進點:predictions across scales
這個借鑒了FPN(feature pyramid networks)憎乙,采用多尺度來對不同size的目標進行檢測,越精細的grid cell就可以檢測出越精細的物體叉趣。
y1,y2和y3的深度都是255泞边,邊長的規(guī)律是13:26:52
對于COCO類別而言,有80個種類疗杉,所以每個box應該對每個種類都輸出一個概率阵谚。
yolo v3設定的是每個網(wǎng)格單元預測3個box,所以每個box需要有(x, y, w, h, confidence)五個基本參數(shù)烟具,然后還要有80個類別的概率梢什。所以3*(5 + 80) = 255。這個255就是這么來的朝聋。(還記得yolo v1的輸出張量嗎嗡午? 7x7x30,只能識別20類物體冀痕,而且每個cell只能預測2個box荔睹,和v3比起來就像老人機和iphoneX一樣)
v3用上采樣的方法來實現(xiàn)這種多尺度的feature map狸演,可以結(jié)合圖1和圖2右邊來看,圖1中concat連接的兩個張量是具有一樣尺度的(兩處拼接分別是26x26尺度拼接和52x52尺度拼接僻他,通過(2, 2)上采樣來保證concat拼接的張量尺度相同)严沥。作者并沒有像SSD那樣直接采用backbone中間層的處理結(jié)果作為feature map的輸出,而是和后面網(wǎng)絡層的上采樣結(jié)果進行一個拼接之后的處理結(jié)果作為feature map中姜。為什么這么做呢消玄? 我感覺是有點玄學在里面,一方面避免和其他算法做法重合丢胚,另一方面這也許是試驗之后并且結(jié)果證明更好的選擇翩瓜,再者有可能就是因為這么做比較節(jié)省模型size的。這點的數(shù)學原理不用去管携龟,知道作者是這么做的就對了兔跌。
3. some tricks
上文把yolo_v3的結(jié)構(gòu)討論了一下,下文將對yolo v3的若干細節(jié)進行剖析峡蟋。
Bounding Box Prediction
b-box預測手段是v3論文中提到的又一個亮點坟桅。先回憶一下v2的b-box預測:想借鑒faster R-CNN RPN中的anchor機制,但不屑于手動設定anchor prior(模板框)蕊蝗,于是用維度聚類的方法來確定anchor box prior(模板框)仅乓,最后發(fā)現(xiàn)聚類之后確定的prior在k=5也能夠又不錯的表現(xiàn),于是就選用k=5蓬戚。后來呢夸楣,v2又嫌棄anchor機制線性回歸的不穩(wěn)定性(因為回歸的offset可以使box偏移到圖片的任何地方),所以v2最后選用了自己的方法:直接預測相對位置子漩。預測出b-box中心點相對于網(wǎng)格單元左上角的相對坐標豫喧。
yolo v2直接predict出(t x t_xtx, t y t_yty, t w t_wtw, t h t_hth, t o t_oto),并不像[RPN](https://blog.csdn.net/leviopku/article/details/80875368)中anchor機制那樣去遍歷每一個pixel幢泼〗粝裕可以從上面的公式看出,b-box的位置大小和confidence都可以通過(t x t_xtx, t y t_yty, t w t_wtw, t h t_hth, t o t_oto)計算得來缕棵,v2相當直接predict出了b-box的位置大小和confidence孵班。box寬和高的預測是受prior影響的,對于v2而言挥吵,b-box prior數(shù)為5重父,在論文中并沒有說明拋棄anchor機制之后是否拋棄了聚類得到的prior(沒看代碼,所以我不能確定)忽匈,如果prior數(shù)繼續(xù)為5房午,那么v2需要對不同prior預測出t w t_wtw和t h t_hth。
對于v3而言丹允,在prior這里的處理有明確解釋:選用的b-box priors 的k=9郭厌,對于tiny-yolo的話袋倔,k=6。priors都是在數(shù)據(jù)集上聚類得來的折柠,有確定的數(shù)值宾娜,如下:
10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
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映射關(guān)系的線性建模。
疑問解答和說明:
在評論里有同學問我關(guān)于輸出的問題困乒,看來我在這里沒有說的很清楚寂屏。了解v3輸出的輸出是至關(guān)重要的。
第一點娜搂, 9個anchor會被三個輸出張量平分的迁霎。根據(jù)大中小三種size各自取自己的anchor。
第二點百宇,每個輸出y在每個自己的網(wǎng)格都會輸出3個預測框考廉,這3個框是9除以3得到的,這是作者設置
的恳谎,我們可以從輸出張量的維度來看芝此,13x13x255。255是怎么來的呢因痛,3*(5+80)。80表示80個種類岸更,5表
示位置信息和置信度鸵膏,3表示要輸出3個prediction。在代碼上來看怎炊,3*(5+80)中的3是直接由
num_anchors//3得到的谭企。
第三點,作者使用了logistic回歸來對每個anchor包圍的內(nèi)容進行了一個目標性評分(objectness score)评肆。
根據(jù)目標性評分來選擇anchor prior進行predict债查,而不是所有anchor prior都會有輸出。
loss function
對掌握Yolo來講瓜挽,loss function不可謂不重要盹廷。在v3的論文里沒有明確提所用的損失函數(shù),確切地說久橙,yolo系列論文里面只有yolo v1明確提了損失函數(shù)的公式俄占。對于yolo這樣一種討喜的目標檢測算法管怠,就連損失函數(shù)都非常討喜。在v1中使用了一種叫sum-square error的損失計算方法缸榄,就是簡單的差方相加而已渤弛。想詳細了解的可以看我關(guān)于v1解釋的博文。我們知道甚带,在目標檢測任務里她肯,有幾個關(guān)鍵信息是需要確定的:
(x,y),(w,h),class,confidence
根據(jù)關(guān)鍵信息的特點可以分為上述四類,損失函數(shù)應該由各自特點確定鹰贵。最后加到一起就可以組成最終的loss_function了晴氨,也就是一個loss_function搞定端到端的訓練±常可以從代碼分析出v3的損失函數(shù)瑞筐,同樣也是對以上四類,不過相比于v1中簡單的總方誤差腊瑟,還是有一些調(diào)整的:
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代碼聚假。忽略恒定系數(shù)不看,可以從上述代碼看出:除了w, h的損失函數(shù)依然采用總方誤差之外闰非,其他部分的損失函數(shù)用的是二值交叉熵膘格。最后加到一起。那么這個binary_crossentropy又是個什么玩意兒呢财松?就是一個最簡單的交叉熵而已瘪贱,一般用于二分類,這里的兩種二分類類別可以理解為"對和不對"這兩種辆毡。關(guān)于binary_crossentropy的公式詳情可參考博文《常見的損失函數(shù)》菜秦。
總結(jié)
結(jié)構(gòu)清晰,實時性好舶掖。這是我十分安利的目標檢測算法球昨,更值得贊揚的是,yolo_v3給了近乎白癡的復現(xiàn)教程眨攘,這種氣量在頂層算法研究者中并不常見主慰。你可以很快的用上v3,但你不能很快地懂v3鲫售,在算法學習的過程中共螺,去一些浮躁,好好理解算法比只會用算法難得很多情竹。