前言
yolo
算法是一種one-stage
的目標(biāo)檢測算法,與two-stage
目標(biāo)檢測算法最大區(qū)別在于運算速度上,YOLO
系列算法將圖片劃分成若干個網(wǎng)格,再基于anchor機(jī)制生成先驗框,只用一步就生成檢測框,這種方法大大提升了算法的預(yù)測速度,今天我們主要學(xué)習(xí)的是YOLOv3
算法的主要實現(xiàn)過程,YOLOv3
的論文于2018年發(fā)表在CVPR
上.
??論文名稱:YOLOv3: An Incremental Improvement
論文下載地址: https://arxiv.org/abs/1804.02767
一、YOLOv3網(wǎng)絡(luò)模型分析
YOLOv3網(wǎng)絡(luò)結(jié)構(gòu)大致可分為三個部分:Backbone
,PANet
,Yolo Head
,在YOLOv3-SPP結(jié)構(gòu)中還引入了SPP結(jié)構(gòu),但與SPPnet的SPP略有不同。
1.Backbone: Darknet-53
首先了解一下yolov3
的backbone
部分,Darknet-53主體與resnet
結(jié)構(gòu)相似,堆疊了多個 殘差模塊,殘差模塊之間間隔了一個kernel_size=3x3
,stride=2
卷積層,作用主要是downsample
,53代表了整個backbone
一共有52個卷積層和最后的connect
層(全連接層),一共53層結(jié)構(gòu),下圖是以輸入圖像256 x 256
進(jìn)行預(yù)訓(xùn)練來進(jìn)行介紹的,常用的尺寸是416 x 416
,都是32的倍數(shù)(經(jīng)過stride
后會變換特征圖大小一共有5次,每次都是2倍所以確定img_size大小要是
2
5
=
32
2^5=32
25=32)。
卷積的strides
默認(rèn)為1
,padding
默認(rèn)為same
(補全),當(dāng)strides
為2
時padding為valid
(丟棄).
第一個3x3
的卷積核主要用來增加通道數(shù),在不改變圖片尺寸的條件下獲得更多的有效特征圖,同時擴(kuò)大特征圖感受野,第二個3x3
的卷積核stride=2
主要作用是downsample
,減少計算過程中的參數(shù)量與計算量,再堆疊多個residual block
,最后對得到的feature map
進(jìn)行平均池化。
以上的Residual
模塊的殘差連接方式與Resnet
基本相同,輸入特征圖與輸出特征圖中間經(jīng)過卷積核大小為1x1
和3x3
大小的兩次卷積,再將輸出的兩個特征圖add
,Convolutional
模塊=conv
+bn
+leakrelu
,如下圖所示:
2.FPN
FPN特征金字塔融合是一種自下而上,自上而下,橫向連接和卷積融合。
自下而上指圖像通過卷積降維和提取特征的過程(特征圖尺寸從大到?。?,自上而下(小到大)通過上采樣與特征融合的方法融合多級特征圖的不同語義信息。這樣既擁有了高層的語義信息,又擁用了底層的輪廓信息,F(xiàn)PN是add,這里是concatenate。
3.Yolo Head
Yolo Head是一種decoder
,它的主要結(jié)構(gòu)是一個conv+bn+act
模塊與一個kernel_size =1x1
卷積分類層,用1x1
卷積代替全連接層進(jìn)行分類的主要原因有兩點:
(1) 全連接層輸入尺度被限定,而卷積層只用限定輸入和輸出channel
(2)全連接層輸出是一維或二維,特征圖輸入到全連接層時需要對其進(jìn)行Flatten
,這樣在一定程度上破環(huán)了特征圖的空間信息,而卷積層輸出為三維(c,w,h)
,選擇卷積層作為decoder
,極大保留了特征圖上對應(yīng)原圖的空間結(jié)構(gòu)信息,如下圖所示:
更便于匹配到正樣本時,輸出空間上對應(yīng)的channel
的值.
二、anchor網(wǎng)格偏移量預(yù)測
由前一部分的講解可知,Yolo Head
輸出是三維的w,h
的每一個點對應(yīng)原圖劃分的網(wǎng)格,channel
對應(yīng)的是該anchor
的預(yù)測值,那我們?nèi)绾斡嬎?code>anchor的具體位置呢?
yolo的思想是將一張圖片劃分為W*W
個網(wǎng)格,每一個網(wǎng)格負(fù)責(zé)中心點落在該網(wǎng)格的目標(biāo).每個網(wǎng)格可以看作一個感興趣區(qū)域,既然是區(qū)域,就需要計算預(yù)測anchor
的具體坐標(biāo)與bbox
的w
和h
.
c
h
a
n
n
e
l
=
t
x
+
t
y
+
t
w
+
t
h
+
o
b
j
+
n
u
m
c
l
a
s
s
e
s
channel = t_x+t_y+t_w+t_h+obj+num_{classes}
channel=tx?+ty?+tw?+th?+obj+numclasses?
其中預(yù)測值tx,ty
并不是anchor的坐標(biāo),而是anchor
的偏移量,同樣的
t
w
,
t
h
t_w,t_h
tw?,th?是先驗框的縮放因子,先驗框的大小由k-means
聚類xml
標(biāo)簽文件中保存的坐標(biāo)位置得到,每一個anchor
有三個不同大小的先驗框,預(yù)測層Yolohead
有三個,總共的先驗框數(shù)量為劃分總網(wǎng)格數(shù)的三倍.
上圖表示了bbox
的回歸過程
c
x
,
c
y
c_x,c_y
cx?,cy?是網(wǎng)格左上角坐標(biāo),anchor
向右下方偏移,為了防止anchor
偏移量超出網(wǎng)格導(dǎo)致定位精度損失過高,yolov3
使用了sigmoid
函數(shù)將預(yù)測值tx,ty進(jìn)行限制到[0,1]
(可以加速網(wǎng)絡(luò)的收斂),最后得出anchor
的預(yù)測坐標(biāo)
b
x
,
b
y
b_x,b_y
bx?,by?.bbox的w與h
由預(yù)測縮放因子
t
w
與
t
h
t_w與t_h
tw?與th?決定,
p
w
和
p
h
p_w和p_h
pw?和ph?為anchor
模板映射到特征圖上的寬和高,通過指數(shù)函數(shù)對
t
w
t_w
tw?與
t
h
t_h
th?進(jìn)行放大在分別與
p
w
和
p
h
p_w和p_h
pw?和ph?相乘就得到了最終預(yù)測的w和h.PS
:此處的x,y
坐標(biāo)點都是anchor
映射到特征圖的值,并不是原圖上的坐標(biāo),在plot bbox
應(yīng)該將其轉(zhuǎn)換到真實圖片上的坐標(biāo)再進(jìn)行繪制.
三、正負(fù)樣本匹配規(guī)則
在yolov3
論文中提到正負(fù)樣本的匹配規(guī)則是:給每一個groundtrue box
分配一個正樣本,這個正樣本是所有bbox
中找一個與gt_box
的重疊區(qū)域最大的一個預(yù)測框,也就是和該gt_box的iou最大的預(yù)測框.但是如果利用這個規(guī)則去尋找正樣本,正樣本的數(shù)量是很少的,這將使得網(wǎng)絡(luò)難以訓(xùn)練.如果一個樣本不是正樣本,那么它既沒有定位損失,也沒有類別損失,只有置信度損失
,在yolov3的論文中作者嘗試用focal loss來緩解正負(fù)樣本不均勻的問題,但是并沒有取得很好的效果,原因就在于負(fù)樣本值參與了置信度損失,對loss的影響占比很小.
所以我們要去看作者在源碼中是如何匹配正樣本的.
源碼中定義了build_targets
函數(shù)來匹配正樣本.
def build_targets(p, targets, model):
"""
Build targets for compute_loss(), input targets(image,class,x,y,w,h)
:param p: 預(yù)測框 由模型構(gòu)建中的yolo_out返回的三個yolo層的輸出
tensor格式 list列表 存放三個tensor 對應(yīng)的是三個yolo層的輸出
例如:[4, 3, 23, 23, 25] [4, 3, 46, 46, 25] [4, 3, 96, 96, 25] (736x736尺度下)
[batch_size, anchor_num, grid, grid, xywh + obj + classes]
p[i].shape
:param targets: 數(shù)據(jù)增強(qiáng)后一個batch的真實框 [21, 6] 21: num_object 6: batch中第幾張圖(0,1,2,3)+類別+x+y+w+h真實框
:param model: 初始化的模型
:return: tbox: append [m(正樣本個數(shù)), x偏移量(中心點坐標(biāo)相對中心所在grid_cell左上角的偏移量) + y偏移量 + w + h]
存放著當(dāng)前batch中所有anchor的正樣本 某個anchor的正樣本指的是當(dāng)前的target由這個anchor預(yù)測
另外,同一個target可能由多個anchor預(yù)測,所以通常 m>nt
indices: append [m(正樣本個數(shù)), b + a + gj + gi]
b: 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target(第a個anchor的正樣本)屬于這個batch中的哪一張圖片
a: 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target是屬于哪個anchor(index)的正樣本(由哪個anchor負(fù)責(zé)預(yù)測)
gj: 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target的中心點所在grid_cell的左上角的y坐標(biāo)
gi: 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target(第a個anchor的正樣本)的中心點所在grid_cell的左上角的x坐標(biāo)
tcls: append [m] 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target(第a個anchor的正樣本)所屬的類別
anch: append [m, 2] 和tbox一一對應(yīng) 存放著tbox中對應(yīng)位置的target是屬于哪個anchor(shape)的正樣本(由哪個anchor負(fù)責(zé)預(yù)測)
"""
nt = targets.shape[0] # 當(dāng)前batch真實框的數(shù)量 num of target
# 定義一些變量
# anch append [m, 2] 和tbox一一對應(yīng) 存放著對應(yīng)位置的target是屬于哪個anchor(shape)的正樣本(由哪個anchor負(fù)責(zé)預(yù)測)
tcls, tbox, indices, anch = [], [], [], []
gain = torch.ones(6, device=targets.device) # normalized to gridspace gain tensor([1,1,1,1,1,1])
multi_gpu = type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) # 一般False
for i, j in enumerate(model.yolo_layers): # [89, 101, 113] i,j = 0, 89 1, 101 2, 113
# 獲取該yolo predictor對應(yīng)的anchors的大小,不過這里是縮放到feature map上的 shape=[3, 2]
# 而且它就是cfg文件中的anchors除以了縮放比例stride得到的
# 比如:[3.6250, 2.8125] / 13 * 416 (縮放比例為32)= [116,90] 等于cfg文件中的anchor的大小
anchors = model.module.module_list[j].anchor_vec if multi_gpu else model.module_list[j].anchor_vec
# gain中存放的是feature map的尺寸信息
# 在原圖尺度為(736,736)情況下 p[0]=[4,3,23,23,25] p[1]=[4,3,46,46,25] p[2]=[4,3,92,92,25]
# 如原圖(736x736) gain=Tensor([1,1,23,23,23,23]) 或 Tensor([1,1,46,46,46,46]) 或 Tensor([1,1,92,92,92,92])
gain[2:] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
na = anchors.shape[0] # number of anchors 3個
# [3] -> [3, 1] -> [3, nt]
# anchor tensor, same as .repeat_interleave(nt) at.shape=[3,21] 21個0, 1, 2
at = torch.arange(na).view(na, 1).repeat(1, nt)
# Match targets to anchors
# t = targets * gain: 將box坐標(biāo)(在box標(biāo)簽生成中,對box坐標(biāo)進(jìn)行了歸一化,即除以圖像的寬高)轉(zhuǎn)換到當(dāng)前yolo層輸出的特征圖上
# 通過將歸一化的box乘以特征圖尺度,從而將box坐標(biāo)投影到特征圖上
# 廣播原理 targets=[21,6] gain=[6] => gain=[6,6] => t=[21,6]
a, t, offsets = [], targets * gain, 0
if nt: # 如果存在target的話
# 把yolo層的anchor在該feature map上對應(yīng)的wh(anchors)和所有預(yù)測真實框在該feature map上對應(yīng)的wh(t[4:6])做iou,
# 若大于model.hyp['iou_t']=0.2, 則為正樣本保留,否則則為負(fù)樣本舍棄
# anchors: [3, 2]: 當(dāng)前yolO層的三個anchor(且都是相對416x416的, 不過初始框的wh多大都可以,反正最后都是做回歸)
# t[:, 4:6]: [nt, 2]: 所有target真是框的w和h, 且都是相對當(dāng)前feature map的
# j: [3, nt]
j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2))
# t.repeat(na, 1, 1): [nt, 6] -> [3, nt, 6]
# 獲取iou大于閾值的anchor與target對應(yīng)信息
# a=tensor[30]: anchor_index(0、1、2) 0表示是屬于第一個anchor(包含4張圖片)的正樣本 同理第二。。。
# 再解釋下什么是正樣本: 表示當(dāng)前target可以由第i個anchor檢測,就表示當(dāng)前target是這個anchor的正樣本
# t=tensor[30,6]: 第0、1、2、3(4張圖片)的target, class, x, y, w, h(相對于當(dāng)前feature map尺度)
# 與a變量一一對應(yīng),a用來指示t中相對應(yīng)的位置的target是屬于哪一個anchor的正樣本
# 注意:這里的同一個target是可能會同屬于多個anchor的正樣本的,由多個anchor計算同一個target
# 不然t個數(shù)也不會大于正樣本數(shù)(30>21)
a, t = at[j], t.repeat(na, 1, 1)[j] # filter 選出所有anchor對應(yīng)屬于它們的正樣本
# Define
# b: 對應(yīng)圖片的index 即當(dāng)前target是屬于哪張圖片的
# c: 當(dāng)前target是屬于哪個類
# long等于to(torch.int64), 數(shù)值向下取整 這里都是整數(shù),long()只起到了float->int的作用
b, c = t[:, :2].long().T # image, class
gxy = t[:, 2:4] # grid xy 對應(yīng)于當(dāng)前feature map的target的xy坐標(biāo)
gwh = t[:, 4:6] # grid wh 對應(yīng)于當(dāng)前feature map的target的wh坐標(biāo)
# 匹配targets所在的grid cell左上角坐標(biāo)
# (gxy-0).long 向下取整 得到當(dāng)前target的中心點所在左上角的坐標(biāo)
gij = (gxy - offsets).long()
# grid xy indices 左上角x, y坐標(biāo)
gi, gj = gij.T
# Append
indices.append((b, a, gj, gi)) # image index, anchor, grid indices(x, y)
tbox.append(torch.cat((gxy - gij, gwh), 1)) # gt box相對當(dāng)前feature map的x,y偏移量以及w,h
anch.append(anchors[a]) # anchors
tcls.append(c) # class
if c.shape[0]: # if any targets
# 目標(biāo)的標(biāo)簽數(shù)值不能大于給定的目標(biāo)類別數(shù)
assert c.max() < model.nc, 'Model accepts %g classes labeled from 0-%g, however you labelled a class %g. ' \
'See https://github.com/ultralytics/yolov3/wiki/Train-Custom-Data' % (
model.nc, model.nc - 1, c.max())
return tcls, tbox, indices, anch
# 這里是根據(jù)寬高(左上角對齊)來進(jìn)行iou計算處理
# 與普通的IoU,GIoU,那些不一樣
def wh_iou(wh1, wh2):
"""
把yolo層的anchor在該feature map上對應(yīng)的wh(anchors)和所有預(yù)測真實框在該feature map上對應(yīng)的wh(t[4:6])做iou,
若大于model.hyp['iou_t']=0.2, 則為正樣本保留,否則則為負(fù)樣本舍棄 篩選出符合該yolo層對應(yīng)的正樣本
Args:
wh1: anchors [3, 2]
wh2: target [22,2]
Returns:
wh1 和 wh2 的iou [3, 22]
"""
# Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
wh1 = wh1[:, None] # [N,1,2] [3, 1, 2]
wh2 = wh2[None] # [1,M,2] [1, 22, 2]
inter = torch.min(wh1, wh2).prod(2) # [N,M] [3, 22]
return inter / (wh1.prod(2) + wh2.prod(2) - inter) # iou = inter / (area1 + area2 - inter)
通過對上面源碼的閱讀我們可以得出作者首先將bbox
與gr_box
的左上角對齊,再計算出存在目標(biāo)的anchor
的bbox
與gr_box
的iou
,并設(shè)定一個iou
閾值,如果anchor template
的iou
大于閾值則歸為正樣本.
四、損失函數(shù)
損失分為三個部分:置信度損失,定位損失和類別損失.
L
=
L
c
l
s
+
L
c
o
n
f
+
L
l
o
c
L = L_{cls} + L_{conf} +L_{loc}
L=Lcls?+Lconf?+Lloc?
在正負(fù)樣本匹配部分我已經(jīng)解釋過只有正樣本才有這三類損失,而負(fù)樣本只有置信度損失,所以在損失計算的過程中我們需要將anchor template
劃分為正樣本與負(fù)樣本兩種情況來計算loss
.
1.類別損失(只考慮正樣本)
L
o
s
s
c
l
s
Loss_{cls}
Losscls?選用的是BCE損失函數(shù):
L
c
l
s
=
?
∑
i
∈
p
o
s
∑
j
∈
c
l
s
(
O
i
j
l
n
(
C
^
i
j
)
)
+
(
1
?
O
i
j
)
l
n
(
1
?
C
^
i
j
)
L_{cls} =-\sum_{i\in pos}\sum_{j\in cls}(O_{ij}ln(\hat C_{ij}))+(1-O_{ij})ln(1-\hat C_{ij})
Lcls?=?i∈pos∑?j∈cls∑?(Oij?ln(C^ij?))+(1?Oij?)ln(1?C^ij?)
C
^
i
j
=
σ
(
C
i
j
)
\hat C_{ij} = \sigma (C_{ij})
C^ij?=σ(Cij?)
O
i
j
O_{ij}
Oij?代表第i
個正樣本中的第j
個類別是否存在,若存在則為1
,不存在為0
.(根據(jù)自己打的標(biāo)簽來)
C
^
i
j
\hat C_{ij}
C^ij?代表經(jīng)過sigmoid
函數(shù)分類后的預(yù)測值.
上圖是從原論文中截取的對分類損失函數(shù)的描述,作者的觀點是獲取好的分類性能并不一定要使用softmax
,相反使用了使sigmoid
函數(shù)分類和bce
損失.
讀到這兒,平時我們一般都使用softmax
進(jìn)行多分類,那為什么使用sigmoid
進(jìn)行分類?independent logistic classifiers
指的是sigmoid函數(shù),個人的理解是:它的好處是將類別概率限制到[0,1]
之間,且每個類別的概率值相互獨立,互不影響,適用于多標(biāo)簽分類.softmax
在進(jìn)行分類時使所有類別概率總和為1,很多時候會出現(xiàn)“一家獨大”
的情況,即某一個類別占比較大,概率值較高,其他類別的概率值很低,在計算類別損失時無法達(dá)到一個好的效果,并不利于網(wǎng)絡(luò)的收斂.
總而言之,softmax
函數(shù)不適用于多標(biāo)簽分類,預(yù)測值通過sigmoid
函數(shù)后每一類的值相互獨立互不影響,適用于同時預(yù)測多類目標(biāo),即多標(biāo)簽分類.
2.置信度損失(考慮所有樣本)
置信度
L
o
s
s
c
o
n
f
Loss_{conf}
Lossconf?計算公式如下所示:
L
o
s
s
c
o
n
f
=
?
∑
i
O
i
l
n
(
c
^
i
)
+
(
1
?
O
i
)
l
n
(
1
?
c
^
i
)
Loss_{conf}=-\sum_iO_iln(\hat c_i)+(1-O_i)ln(1-\hat c_i)
Lossconf?=?i∑?Oi?ln(c^i?)+(1?Oi?)ln(1?c^i?)
C
^
i
=
σ
(
C
i
)
\hat C_{i} = \sigma (C_{i})
C^i?=σ(Ci?)
O
i
O_i
Oi?代表預(yù)測目標(biāo)邊界框i中是否真實存在目標(biāo).若存在則為1
,不存在為0
.(根據(jù)自己打的標(biāo)簽來)
C
^
i
\hat C_{i}
C^i?代表表示預(yù)測目標(biāo)矩形框i內(nèi)是否存在目標(biāo)的Sigmoid
概率.Sigmoid
函數(shù)作用主要是將預(yù)測值范圍規(guī)定到[0,1]
yolov3論文中提出confidence score
的預(yù)測使用的是一種邏輯回歸的方式,所以損失函數(shù)用的是BCE
.文章來源:http://www.zghlxwxcb.cn/news/detail-440619.html
3.定位損失(只考慮正樣本)
置信度
L
o
s
s
l
o
c
Loss_{loc}
Lossloc?計算公式如下所示:
L
o
s
s
l
o
c
=
∑
i
∈
p
o
s
(
t
^
?
?
t
?
)
2
Loss_{loc}=\sum_{i\in pos}(\hat t_*-t_*)^2
Lossloc?=i∈pos∑?(t^???t??)2
根據(jù)以上論文內(nèi)容可知,計算
L
o
s
s
l
o
c
Loss_{loc}
Lossloc?時使用的時誤差平方和公式,
t
^
?
\hat t_*
t^??為真實值,
t
?
t_*
t??為預(yù)測值
所以
L
o
s
s
l
o
c
Loss_{loc}
Lossloc?可寫作如下形式
L
o
s
s
l
o
c
(
x
,
y
)
=
∑
i
∈
p
o
s
∑
m
∈
x
,
y
[
(
g
m
?
c
m
)
?
σ
(
t
m
)
]
2
Loss_{loc (x,y)} = \sum_{i\in pos}\sum_{m\in{x,y}}[ (g_m-c_m)-\sigma(t_m)]^2
Lossloc(x,y)?=i∈pos∑?m∈x,y∑?[(gm??cm?)?σ(tm?)]2
其中
t
^
?
=
g
m
?
c
m
\hat t_* =g_m-c_m
t^??=gm??cm?,
t
?
=
σ
(
t
m
)
t_* = \sigma(t_m)
t??=σ(tm?)
L
o
s
s
l
o
c
(
w
,
h
)
=
∑
i
∈
p
o
s
∑
n
∈
w
,
h
(
l
n
g
m
i
p
n
i
?
t
n
)
2
Loss_{loc (w,h)} = \sum_{i\in pos}\sum_{n\in{w,h}}(ln\frac{g_m^i}{p_n^i}-t_n)^2
Lossloc(w,h)?=i∈pos∑?n∈w,h∑?(lnpni?gmi???tn?)2
其中
t
^
?
=
l
n
g
m
i
p
n
i
\hat t_* =ln\frac{g_m^i}{p_n^i}
t^??=lnpni?gmi??,
t
?
=
t
n
t_* = t_n
t??=tn?
L
o
s
s
l
o
c
=
L
o
s
s
l
o
c
(
x
,
y
)
+
L
o
s
s
l
o
c
(
w
,
h
)
Loss_{loc}=Loss_{loc (x,y)} +Loss_{loc (w,h)}
Lossloc?=Lossloc(x,y)?+Lossloc(w,h)?
yolov3中的定位損失采用SSE顯然是不太合適的,SSE主要是對預(yù)測所得anchor中心偏移量與寬高縮放因子進(jìn)行誤差分析,是對點進(jìn)行回歸分析,并不能較好的反應(yīng)預(yù)測框與真實框之間的誤差關(guān)系,這個問題我們將在下一節(jié)yolov4的講解中進(jìn)行討論.文章來源地址http://www.zghlxwxcb.cn/news/detail-440619.html
到了這里,關(guān)于YOLOv3詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!