1. 程式人生 > 實用技巧 >一文看懂YOLO v3

一文看懂YOLO v3

論文地址:https://pjreddie.com/media/files/papers/YOLOv3.pdf
論文:YOLOv3: An Incremental Improvement

YOLO系列的目標檢測演算法可以說是目標檢測史上的巨集篇鉅作,接下來我們來詳細介紹一下YOLO v3演算法內容,v3的演算法是在v1和v2的基礎上形成的,所以有必要先回憶:一文看懂YOLO v1一文看懂YOLO v2

網路結構

這兒盜了張圖,這張圖很好的總結了YOLOV3的結構,讓我們對YOLO有更加直觀的理解。
DBL:程式碼中的Darknetconv2d_BN_Leaky,是yolo_v3的基本元件。就是卷積+BN+Leaky relu。
resn:n代表數字,有res1,res2, … ,res8等等,表示這個res_block裡含有多少個res_unit。不懂resnet請

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

後面我們一起分析網路一些細節與難懂的地方

backbone:darknet-53

為了達到更好的分類效果,作者自己設計訓練了darknet-53。作者在ImageNet上實驗發現這個darknet-53,的確很強,相對於ResNet-152和ResNet-101,darknet-53不僅在分類精度上差不多,計算速度還比ResNet-152和ResNet-101強多了,網路層數也比他們少。

Yolo_v3使用了darknet-53的前面的52層(沒有全連線層),yolo_v3這個網路是一個全卷積網路,大量使用殘差的跳層連線,並且為了降低池化帶來的梯度負面效果,作者直接摒棄了POOLing,用conv的stride來實現降取樣。在這個網路結構中,使用的是步長為2的卷積來進行降取樣。

為了加強演算法對小目標檢測的精確度,YOLO v3中採用類似FPN的upsample和融合做法(最後融合了3個scale,其他兩個scale的大小分別是26×26和52×52),在多個scale的feature map上做檢測。

作者在3條預測支路採用的也是全卷積的結構,其中最後一個卷積層的卷積核個數是255,是針對COCO資料集的80類:3*(80+4+1)=255,3表示一個grid cell包含3個bounding box,4表示框的4個座標資訊,1表示objectness score。

output


所謂的多尺度就是來自這3條預測之路,y1,y2和y3的深度都是255,邊長的規律是13:26:52。yolo v3設定的是每個網格單元預測3個box,所以每個box需要有(x, y, w, h, confidence)五個基本引數,然後還要有80個類別的概率。所以3×(5 + 80) = 255。這個255就是這麼來的。

下面我們具體看看y1,y2,y3是如何而來的。
網路中作者進行了三次檢測,分別是在32倍降取樣,16倍降取樣,8倍降取樣時進行檢測,這樣在多尺度的feature map上檢測跟SSD有點像。在網路中使用up-sample(上取樣)的原因:網路越深的特徵表達效果越好,比如在進行16倍降取樣檢測,如果直接使用第四次下采樣的特徵來檢測,這樣就使用了淺層特徵,這樣效果一般並不好。如果想使用32倍降取樣後的特徵,但深層特徵的大小太小,因此yolo_v3使用了步長為2的up-sample(上取樣),把32倍降取樣得到的feature map的大小提升一倍,也就成了16倍降取樣後的維度。同理8倍取樣也是對16倍降取樣的特徵進行步長為2的上取樣,這樣就可以使用深層特徵進行detection。

作者通過上取樣將深層特徵提取,其維度是與將要融合的特徵層維度相同的(channel不同)。如下圖所示,85層將13×13×256的特徵上取樣得到26×26×256,再將其與61層的特徵拼接起來得到26×26×768。為了得到channel255,還需要進行一系列的3×3,1×1卷積操作,這樣既可以提高非線性程度增加泛化效能提高網路精度,又能減少引數提高實時性。52×52×255的特徵也是類似的過程。


從圖中,我們可以看出y1,y2,y3的由來。

Bounding Box

YOLO v3的Bounding Box由YOLOV2又做出了更好的改進。在yolo_v2和yolo_v3中,都採用了對影象中的object採用k-means聚類。 feature map中的每一個cell都會預測3個邊界框(bounding box) ,每個bounding box都會預測三個東西:(1)每個框的位置(4個值,中心座標tx和ty,,框的高度bh和寬度bw),(2)一個objectness prediction ,(3)N個類別,coco資料集80類,voc20類。

三次檢測,每次對應的感受野不同,32倍降取樣的感受野最大,適合檢測大的目標,所以在輸入為416×416時,每個cell的三個anchor box為(116 ,90); (156 ,198); (373 ,326)。16倍適合一般大小的物體,anchor box為(30,61); (62,45); (59,119)。8倍的感受野最小,適合檢測小目標,因此anchor box為(10,13); (16,30); (33,23)。所以當輸入為416×416時,實際總共有(52×52+26×26+13×13)×3=10647個proposal box。

感受一下9種先驗框的尺寸,下圖中藍色框為聚類得到的先驗框。黃色框式ground truth,紅框是物件中心點所在的網格。

這裡注意bounding box 與anchor box的區別:
Bounding box它輸出的是框的位置(中心座標與寬高),confidence以及N個類別。
anchor box只是一個尺度即只有寬高。

LOSS Function

YOLOv3重要改變之一:No more softmaxing the classes。
YOLO v3現在對影象中檢測到的物件執行多標籤分類。

logistic迴歸用於對anchor包圍的部分進行一個目標性評分(objectness score),即這塊位置是目標的可能性有多大。這一步是在predict之前進行的,可以去掉不必要anchor,可以減少計算量。

如果模板框不是最佳的即使它超過我們設定的閾值,我們還是不會對它進行predict。
不同於faster R-CNN的是,yolo_v3只會對1個prior進行操作,也就是那個最佳prior。而logistic迴歸就是用來從9個anchor priors中找到objectness score(目標存在可能性得分)最高的那一個。logistic迴歸就是用曲線對prior相對於 objectness score對映關係的線性建模。

    lxy, lwh, lcls, lconf = ft([0]), ft([0]), ft([0]), ft([0])
    txy, twh, tcls, indices = build_targets(model, targets)#在13 26 52維度中找到大於iou閾值最適合的anchor box 作為targets
    #txy[維度(0:2),(x,y)] twh[維度(0:2),(w,h)] indices=[0,anchor索引,gi,gj]

    # Define criteria
    MSE = nn.MSELoss()
    CE = nn.CrossEntropyLoss()
    BCE = nn.BCEWithLogitsLoss()

    # Compute losses
    h = model.hyp  # hyperparameters
    bs = p[0].shape[0]  # batch size
    k = h['k'] * bs  # loss gain
    for i, pi0 in enumerate(p):  # layer i predictions, i
        b, a, gj, gi = indices[i]  # image, anchor, gridx, gridy
        tconf = torch.zeros_like(pi0[..., 0])  # conf


        # Compute losses
        if len(b):  # number of targets
            pi = pi0[b, a, gj, gi]  # predictions closest to anchors 找到p中與targets對應的資料lxy
            tconf[b, a, gj, gi] = 1  # conf
            # pi[..., 2:4] = torch.sigmoid(pi[..., 2:4])  # wh power loss (uncomment)

            lxy += (k * h['xy']) * MSE(torch.sigmoid(pi[..., 0:2]),txy[i])  # xy loss
            lwh += (k * h['wh']) * MSE(pi[..., 2:4], twh[i])  # wh yolo loss
            lcls += (k * h['cls']) * CE(pi[..., 5:], tcls[i])  # class_conf loss

        # pos_weight = ft([gp[i] / min(gp) * 4.])
        # BCE = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
        lconf += (k * h['conf']) * BCE(pi0[..., 4], tconf)  # obj_conf loss
    loss = lxy + lwh + lconf + lcls

以上是一段pytorch框架描述的yolo v3 的loss_function程式碼。忽略恆定係數不看,以下我想著重說幾點:

  • 首先,yolov3要先build target,因為我們知道正樣本是label與anchor box iou大於0.5的組成,所以我們根據label找到對應的anchor box。如何找?label中存放著[image,class,x(歸一化),y,w(歸一化),h],我們可以用這些座標在對應13×13 Or 26×26 or 52×52的map中分別於9個anchor算出iou,找到符合要求的,把索引與位置記錄好。用記錄好的索引位置找到predict的anchor box。
  • xywh是由均方差來計算loss的,其中預測的xy進行sigmoid來與lable xy求差,label xy是grid cell中心點座標,其值在0-1之間,所以predict出的xy要sigmoid。
  • 分類用的多類別交叉熵,置信度用的二分類交叉熵。只有正樣本才參與class,xywh的loss計算,負樣本只參與置信度loss。

參考文章:
https://towardsdatascience.com/yolo-v3-object-detection-53fb7d3bfe6b
https://blog.csdn.net/yanzi6969/article/details/80505421
https://blog.csdn.net/chandanyan8568/article/details/81089083
https://blog.csdn.net/leviopku/article/details/82660381
https://blog.csdn.net/u014380165/article/details/80202337