注:本文中的代碼基於https://github.com/ultralytics/yolov3
——————————————————————————————————
(1)首先將圖片以416*416的形式輸入系統,然后經過Darknet53網絡特征提取和計算后就會得到3個不同尺度大小的YOLO層結果(分別是13/26/52)。比如我所訓練的種類只有行人這一種,那么13*13的YOLO層輸出就一共會有3*13*13個預測結果,每個結果里面有6個數值分別是:
[x, y, w, h, obj, cls]。
(2)接下來就是要重點研究這些預測結果和標簽理論結果的損失函數怎么計算。
比如我設置的batch_size是8,那么在這8張圖片中一共有86個行人存在,也就是對應的有8個txt文件,里面一共有86行。每一行的格式為:
[0,0.6492187500000001,0.5364583333333334,0.2328125,0.48125]
即[類別序號, 中心點x, 中心點y, 目標w, 目標h]
疑問:為什么都是小於0的小數?
答:這些都是指把一張圖片的高和寬假設成1,每個參數對應的比例。
(3)一般訓練過程的常見解釋都是說:YOLO在訓練的時候把圖片分割成S*S個網格,然后判斷哪個網格里面有標簽中所包含的目標,對這個網格進行預測,然后根據預測結果和標簽理論結果求損失進行梯度下降。那么問題來了,網絡不是每個尺度下每個網格都有3個預測結果了嗎?怎么說是根據標簽來預測?
(4)首先 ,我們需要對標簽中所包含的目標進行處理,看看到底標簽對應的3個YOLO層輸出理論應該是怎樣的。也就是探討,這8張圖片里的86個行人,我們可以用哪些尺度(13/26/52)里面的哪些anchor(每個尺度對應3個anchor)進行預測。(注意,這個時候我們面向的對象就已經不是8張圖片了,而是8張圖片中的86個行人個體目標)。比如,86個行人,在13*13這個尺度下,並不是就可以對應86*3=258個理論預測結果。需要先進行一輪的IOU篩選:利用86個標簽的wh和13*13相應的3個anchor的wh進行IOU求解,當這個wh_IOU小於某個閾值iou_thre的時候,就說明這些anchor根本不適合對該目標進行預測。經過這個步驟處理,可以去掉很多不適合的預測。我覺得也可以這么理解:13*13這個尺度比較適合預測大尺度目標,anchor也比較大,只有少數行人適合用這個YOLO層進行預測。
經過實驗:86個行人在13*13中可以有12個預測,在26*26中可以有30個預測,在52*52中可以有125個預測。這12+30+125的預測就是86個行人標簽中可以利用YOLO層預測的理論結果。
(5)得到的理論結果用於和YOLO層輸出的真實結果進行損失計算。這個標簽目標處理過程的代碼:
tcls, tbox, indices, anchor_vec = build_targets(p, targets, model)
返回的就是86個行人可以在三個YOLO層進行預測的12+30+125個理論結果。其中,tcls表示這個行人對應的類別0;tbox的格式為[x, y, w, h],表示這個行人對應的寬高中心坐標;indices的格式為[圖片索引,對應可用來預測的anchor的索引,行人在這張圖片中網格的左上角坐標xy],圖片索引就是指這個行人是屬於8張圖片中的那張圖片。
因為是3個尺度13/26/52,在返回的數值中,tcls[0],tbox[0],indices[0], anchor_vec[0]就是對應13*13這個尺度的12個行人的理論結果。
(6)接下來取13*13尺度的理論結果,一共有12個行人。在u版代碼中:
b, a, gj, gi = indices[i]
其中b表示這12個行人所在圖片的索引(范圍為0-7),a表示這12個行人可以利用的anchor的索引(范圍為0-2),gj表示這12個行人所在網格的左上角橫坐標(范圍為0-12),gi表示這12個行人所在網格的左上角橫坐標(范圍為0-12)。
(7)在上面就是把13*13尺度下12個行人的標簽理論結果構造好一部分了,接下來就要構造置信度。
tobj = torch.zeros_like(pi[..., 0])
構造出一個和實際的YOLO層輸出一樣多的置信度矩陣全零,形狀為(bs, anchors, grid, grid),比如實際的YOLO層在13尺度下一共有8*13*13*3個置信度,所以一共是4056個。
(8)接下來要開始計算這12個行人對應的實際YOLO層預測出的結果,用於后面求損失值。
for i, pi in enumerate(p): ps = pi[b, a, gj, gi]
這里的p是指三個YOLO層輸出的所有結果,i就是0,1,2三個YOLO層索引,pi就是對應YOLO層的所有實際預測結果。利用之前根據12個行人標簽所計算好的b, a, gj, gi就可以找到pi層對應的結果。實際返回的ps格式為[x, y, w, h, obj, cls]
(9)
pxy = torch.sigmoid(ps[:, 0:2]) # pxy = pxy * s - (s - 1) / 2, s = 1.5 (scale_xy)
pwh = torch.exp(ps[:, 2:4]).clamp(max=1E3) * anchor_vec[i] pbox = torch.cat((pxy, pwh), 1) # predicted box
上面的代碼對應計算出預測的bbox的bx, by, bw, bh。
(10)接下來計算實際預測結果bbox和理論結果tbox的GIOU。
giou = bbox_iou(pbox.t(), tbox[i], x1y1x2y2=False, GIoU=True) # giou computation
lbox += (1.0 - giou).sum() if red == 'sum' else (1.0 - giou).mean() # giou loss
(11)接下來就要對這12個行人的置信度進行設置成1,因為前面我們是初始化成全0。
要注意,obj應該是obj和giou相乘后的數值,所以不再單純是1。
tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio
(12)接下來就是對所有的實際預測結果和理論預測結果求置信度損失。
lobj += BCEobj(pi[..., 4], tobj) # obj loss
因為只有12個行人對應的預測框的理論置信度被設置成1,其他的預測框的理論置信度都是0。在實際計算過程中,所有YOLO層的實際預測框都會去求置信度損失,但在這些預測框里面其實只有少數是真的含有目標的,這就會導致沒有目標的負樣本在這個損失部分里占了主導地位,容易使系統的訓練方向跑偏,系統變得不怎么敢去預測行人這個結果,所以才會有focal loss損失的出現解決這個問題,但是加入focal loss之后系統又變得過於大膽預測行人,會出現更多誤檢。
(13)因為我只有行人一個類別,所以類別損失函數就省略不講了。目標置信度損失函數為BCEWithLogisticLoss,該函數是sigmoid函數和二分類交叉熵損失函數的組合。目標定位損失函數采用的是MSELoss,均方誤差應用在目標框上並不是直接計算四個參數和標簽數據的均方誤差之和,而是計算兩框之間的GIOU數值。——————————————————————————————————————————————
以上就是對u版YOLOv3訓練過程代碼的理解。:D
文章屬於個人總結,如有錯誤之處,請評論指正,不勝感激。(ฅ>ω<*ฅ)