Keras搭建YoloV4目标检测平台
學(xué)習(xí)前言
什么是YOLOV4
代碼下載
YOLOV4改進(jìn)的部分(不完全)
YOLOV4結(jié)構(gòu)解析
1、主干特征提取網(wǎng)絡(luò)Backbone
2、特征金字塔
3、YoloHead利用獲得到的特征進(jìn)行預(yù)測(cè)
4、預(yù)測(cè)結(jié)果的解碼
5、在原圖上進(jìn)行繪制
YOLOV4的訓(xùn)練
1、YOLOV4的改進(jìn)訓(xùn)練技巧
a)、Mosaic數(shù)據(jù)增強(qiáng)
b)、Label Smoothing平滑
c)、CIOU
d)、學(xué)習(xí)率余弦退火衰減
2、loss組成
a)、計(jì)算loss所需參數(shù)
b)、y_pre是什么
c)、y_true是什么。
d)、loss的計(jì)算過(guò)程
訓(xùn)練自己的YOLOV4模型
學(xué)習(xí)前言
哈哈哈我最喜歡的YOLO更新了!
什么是YOLOV4
YOLOV4是YOLOV3的改進(jìn)版,在YOLOV3的基礎(chǔ)上結(jié)合了非常多的小Tricks。
盡管沒(méi)有目標(biāo)檢測(cè)上革命性的改變,但是YOLOV4依然很好的結(jié)合了速度與精度。
根據(jù)上圖也可以看出來(lái),YOLOV4在YOLOV3的基礎(chǔ)上,在FPS不下降的情況下,mAP達(dá)到了44,提高非常明顯。
YOLOV4整體上的檢測(cè)思路和YOLOV3相比相差并不大,都是使用三個(gè)特征層進(jìn)行分類(lèi)與回歸預(yù)測(cè)。
請(qǐng)注意!
強(qiáng)烈建議在學(xué)習(xí)YOLOV4之前學(xué)習(xí)YOLOV3,因?yàn)閅OLOV4確實(shí)可以看作是YOLOV3結(jié)合一系列改進(jìn)的版本!
強(qiáng)烈建議在學(xué)習(xí)YOLOV4之前學(xué)習(xí)YOLOV3,因?yàn)閅OLOV4確實(shí)可以看作是YOLOV3結(jié)合一系列改進(jìn)的版本!
強(qiáng)烈建議在學(xué)習(xí)YOLOV4之前學(xué)習(xí)YOLOV3,因?yàn)閅OLOV4確實(shí)可以看作是YOLOV3結(jié)合一系列改進(jìn)的版本!
(重要的事情說(shuō)三遍!)
YOLOV3可參考該博客:
https://blog.csdn.net/weixin_44791964/article/details/103276106
代碼下載
https://github.com/bubbliiiing/yolov4-keras
喜歡的可以給個(gè)star噢!
YOLOV4改進(jìn)的部分(不完全)
1、主干特征提取網(wǎng)絡(luò):DarkNet53 => CSPDarkNet53
2、特征金字塔:SPP,PAN
3、分類(lèi)回歸層:YOLOv3(未改變)
4、訓(xùn)練用到的小技巧:Mosaic數(shù)據(jù)增強(qiáng)、Label Smoothing平滑、CIOU、學(xué)習(xí)率余弦退火衰減
5、激活函數(shù):使用Mish激活函數(shù)
以上并非全部的改進(jìn)部分,還存在一些其它的改進(jìn),由于YOLOV4使用的改進(jìn)實(shí)在太多了,很難完全實(shí)現(xiàn)與列出來(lái),這里只列出來(lái)了一些我比較感興趣,而且非常有效的改進(jìn)。
整篇BLOG會(huì)結(jié)合YOLOV3與YOLOV4的差別進(jìn)行解析
YOLOV4結(jié)構(gòu)解析
1、主干特征提取網(wǎng)絡(luò)Backbone
當(dāng)輸入是416x416時(shí),特征結(jié)構(gòu)如下:
當(dāng)輸入是608x608時(shí),特征結(jié)構(gòu)如下:
主干特征提取網(wǎng)絡(luò)Backbone的改進(jìn)點(diǎn)有兩個(gè):
a).主干特征提取網(wǎng)絡(luò):DarkNet53 => CSPDarkNet53
b).激活函數(shù):使用Mish激活函數(shù)
如果大家對(duì)YOLOV3比較熟悉的話(huà),應(yīng)該知道Darknet53的結(jié)構(gòu),其由一系列殘差網(wǎng)絡(luò)結(jié)構(gòu)構(gòu)成。在Darknet53中,其存在如下resblock_body模塊,其由一次下采樣和多次殘差結(jié)構(gòu)的堆疊構(gòu)成,Darknet53便是由resblock_body模塊組合而成。
def resblock_body(x, num_filters, num_blocks):
? ? x = ZeroPadding2D(((1,0),(1,0)))(x)
? ? x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x)
? ? for i in range(num_blocks):
? ? ? ? y = DarknetConv2D_BN_Leaky(num_filters//2, (1,1))(x)
? ? ? ? y = DarknetConv2D_BN_Leaky(num_filters, (3,3))(y)
? ? ? ? x = Add()([x,y])
? ? return x
1
2
3
4
5
6
7
8
而在YOLOV4中,其對(duì)該部分進(jìn)行了一定的修改。
1、其一是將DarknetConv2D的激活函數(shù)由LeakyReLU修改成了Mish,卷積塊由DarknetConv2D_BN_Leaky變成了DarknetConv2D_BN_Mish。
Mish函數(shù)的公式與圖像如下:
Mish=x×tanh(ln(1+ex))Mish=x \times tanh(ln(1+e^x))
Mish=x×tanh(ln(1+e?
x
?))
2、其二是將resblock_body的結(jié)構(gòu)進(jìn)行修改,使用了CSPnet結(jié)構(gòu)。此時(shí)YOLOV4當(dāng)中的Darknet53被修改成了CSPDarknet53。
CSPnet結(jié)構(gòu)并不算復(fù)雜,就是將原來(lái)的殘差塊的堆疊進(jìn)行了一個(gè)拆分,拆成左右兩部分:
主干部分繼續(xù)進(jìn)行原來(lái)的殘差塊的堆疊;
另一部分則像一個(gè)殘差邊一樣,經(jīng)過(guò)少量處理直接連接到最后。
因此可以認(rèn)為CSP中存在一個(gè)大的殘差邊。
#---------------------------------------------------#
# ? CSPdarknet的結(jié)構(gòu)塊
# ? 存在一個(gè)大殘差邊
# ? 這個(gè)大殘差邊繞過(guò)了很多的殘差結(jié)構(gòu)
#---------------------------------------------------#
def resblock_body(x, num_filters, num_blocks, all_narrow=True):
? ? # 進(jìn)行長(zhǎng)和寬的壓縮
? ? preconv1 = ZeroPadding2D(((1,0),(1,0)))(x)
? ? preconv1 = DarknetConv2D_BN_Mish(num_filters, (3,3), strides=(2,2))(preconv1)
? ? # 生成一個(gè)大的殘差邊?
? ? shortconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
? ? # 主干部分的卷積
? ? mainconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
? ? # 1x1卷積對(duì)通道數(shù)進(jìn)行整合->3x3卷積提取特征,使用殘差結(jié)構(gòu)
? ? for i in range(num_blocks):
? ? ? ? y = compose(
? ? ? ? ? ? ? ? DarknetConv2D_BN_Mish(num_filters//2, (1,1)),
? ? ? ? ? ? ? ? DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (3,3)))(mainconv)
? ? ? ? mainconv = Add()([mainconv,y])
? ? # 1x1卷積后和殘差邊堆疊
? ? postconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(mainconv)
? ? route = Concatenate()([postconv, shortconv])
? ? # 最后對(duì)通道數(shù)進(jìn)行整合
? ? return DarknetConv2D_BN_Mish(num_filters, (1,1))(route)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
全部實(shí)現(xiàn)代碼為:
from functools import wraps
from keras import backend as K
from keras.layers import Conv2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, MaxPooling2D, Layer
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.regularizers import l2
from utils.utils import compose
class Mish(Layer):
? ? def __init__(self, **kwargs):
? ? ? ? super(Mish, self).__init__(**kwargs)
? ? ? ? self.supports_masking = True
? ? def call(self, inputs):
? ? ? ? return inputs * K.tanh(K.softplus(inputs))
? ? def get_config(self):
? ? ? ? config = super(Mish, self).get_config()
? ? ? ? return config
? ? def compute_output_shape(self, input_shape):
? ? ? ? return input_shape
#--------------------------------------------------#
# ? 單次卷積
#--------------------------------------------------#
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):
? ? darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}
? ? darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same'
? ? darknet_conv_kwargs.update(kwargs)
? ? return Conv2D(*args, **darknet_conv_kwargs)
#---------------------------------------------------#
# ? 卷積塊
# ? DarknetConv2D + BatchNormalization + Mish
#---------------------------------------------------#
def DarknetConv2D_BN_Mish(*args, **kwargs):
? ? no_bias_kwargs = {'use_bias': False}
? ? no_bias_kwargs.update(kwargs)
? ? return compose(
? ? ? ? DarknetConv2D(*args, **no_bias_kwargs),
? ? ? ? BatchNormalization(),
? ? ? ? Mish())
#---------------------------------------------------#
# ? CSPdarknet的結(jié)構(gòu)塊
# ? 存在一個(gè)大殘差邊
# ? 這個(gè)大殘差邊繞過(guò)了很多的殘差結(jié)構(gòu)
#---------------------------------------------------#
def resblock_body(x, num_filters, num_blocks, all_narrow=True):
? ? # 進(jìn)行長(zhǎng)和寬的壓縮
? ? preconv1 = ZeroPadding2D(((1,0),(1,0)))(x)
? ? preconv1 = DarknetConv2D_BN_Mish(num_filters, (3,3), strides=(2,2))(preconv1)
? ? # 生成一個(gè)大的殘差邊?
? ? shortconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
? ? # 主干部分的卷積
? ? mainconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(preconv1)
? ? # 1x1卷積對(duì)通道數(shù)進(jìn)行整合->3x3卷積提取特征,使用殘差結(jié)構(gòu)
? ? for i in range(num_blocks):
? ? ? ? y = compose(
? ? ? ? ? ? ? ? DarknetConv2D_BN_Mish(num_filters//2, (1,1)),
? ? ? ? ? ? ? ? DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (3,3)))(mainconv)
? ? ? ? mainconv = Add()([mainconv,y])
? ? # 1x1卷積后和殘差邊堆疊
? ? postconv = DarknetConv2D_BN_Mish(num_filters//2 if all_narrow else num_filters, (1,1))(mainconv)
? ? route = Concatenate()([postconv, shortconv])
? ? # 最后對(duì)通道數(shù)進(jìn)行整合
? ? return DarknetConv2D_BN_Mish(num_filters, (1,1))(route)
#---------------------------------------------------#
# ? CSPdarknet53 的主體部分
#---------------------------------------------------#
def darknet_body(x):
? ? x = DarknetConv2D_BN_Mish(32, (3,3))(x)
? ? x = resblock_body(x, 64, 1, False)
? ? x = resblock_body(x, 128, 2)
? ? x = resblock_body(x, 256, 8)
? ? feat1 = x
? ? x = resblock_body(x, 512, 8)
? ? feat2 = x
? ? x = resblock_body(x, 1024, 4)
? ? feat3 = x
? ? return feat1,feat2,feat3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
2、特征金字塔
當(dāng)輸入是416x416時(shí),特征結(jié)構(gòu)如下:
當(dāng)輸入是608x608時(shí),特征結(jié)構(gòu)如下:
在特征金字塔部分,YOLOV4結(jié)合了兩種改進(jìn):
a).使用了SPP結(jié)構(gòu)。
b).使用了PANet結(jié)構(gòu)。
如上圖所示,除去CSPDarknet53和Yolo Head的結(jié)構(gòu)外,都是特征金字塔的結(jié)構(gòu)。
1、SPP結(jié)構(gòu)參雜在對(duì)CSPdarknet53的最后一個(gè)特征層的卷積里,在對(duì)CSPdarknet53的最后一個(gè)特征層進(jìn)行三次DarknetConv2D_BN_Leaky卷積后,分別利用四個(gè)不同尺度的最大池化進(jìn)行處理,最大池化的池化核大小分別為13x13、9x9、5x5、1x1(1x1即無(wú)處理)
# 使用了SPP結(jié)構(gòu),即不同尺度的最大池化后堆疊。
maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])
1
2
3
4
5
其可以它能夠極大地增加感受野,分離出最顯著的上下文特征。
2、PANet是2018的一種實(shí)例分割算法,其具體結(jié)構(gòu)由反復(fù)提升特征的意思。
上圖為原始的PANet的結(jié)構(gòu),可以看出來(lái)其具有一個(gè)非常重要的特點(diǎn)就是特征的反復(fù)提取。
在(a)里面是傳統(tǒng)的特征金字塔結(jié)構(gòu),在完成特征金字塔從下到上的特征提取后,還需要實(shí)現(xiàn)(b)中從上到下的特征提取。
而在YOLOV4當(dāng)中,其主要是在三個(gè)有效特征層上使用了PANet結(jié)構(gòu)。
實(shí)現(xiàn)代碼如下:
#---------------------------------------------------#
# ? 特征層->最后的輸出
#---------------------------------------------------#
def yolo_body(inputs, num_anchors, num_classes):
? ? # 生成darknet53的主干模型
? ? feat1,feat2,feat3 = darknet_body(inputs)
? ? P5 = DarknetConv2D_BN_Leaky(512, (1,1))(feat3)
? ? P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
? ? P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
? ? # 使用了SPP結(jié)構(gòu),即不同尺度的最大池化后堆疊。
? ? maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5)
? ? maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5)
? ? maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5)
? ? P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5])
? ? P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
? ? P5 = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
? ? P5 = DarknetConv2D_BN_Leaky(512, (1,1))(P5)
? ? P5_upsample = compose(DarknetConv2D_BN_Leaky(256, (1,1)), UpSampling2D(2))(P5)
? ??
? ? P4 = DarknetConv2D_BN_Leaky(256, (1,1))(feat2)
? ? P4 = Concatenate()([P4, P5_upsample])
? ? P4 = make_five_convs(P4,256)
? ? P4_upsample = compose(DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(P4)
? ??
? ? P3 = DarknetConv2D_BN_Leaky(128, (1,1))(feat1)
? ? P3 = Concatenate()([P3, P4_upsample])
? ? P3 = make_five_convs(P3,128)
? ? # 76x76的out
? ? P3_output = DarknetConv2D_BN_Leaky(256, (3,3))(P3)
? ? P3_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P3_output)
? ? P3_downsample = ZeroPadding2D(((1,0),(1,0)))(P3)
? ? P3_downsample = DarknetConv2D_BN_Leaky(256, (3,3), strides=(2,2))(P3_downsample)
? ? P4 = Concatenate()([P3_downsample, P4])
? ? P4 = make_five_convs(P4,256)
? ??
? ? # 38x38的out
? ? P4_output = DarknetConv2D_BN_Leaky(512, (3,3))(P4)
? ? P4_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P4_output)
? ??
? ? P4_downsample = ZeroPadding2D(((1,0),(1,0)))(P4)
? ? P4_downsample = DarknetConv2D_BN_Leaky(512, (3,3), strides=(2,2))(P4_downsample)
? ? P5 = Concatenate()([P4_downsample, P5])
? ? P5 = make_five_convs(P5,512)
? ??
? ? # 19x19的out
? ? P5_output = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
? ? P5_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P5_output)
? ? return Model(inputs, [P5_output, P4_output, P3_output])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
3、YoloHead利用獲得到的特征進(jìn)行預(yù)測(cè)
當(dāng)輸入是416x416時(shí),特征結(jié)構(gòu)如下:
當(dāng)輸入是608x608時(shí),特征結(jié)構(gòu)如下:
1、在特征利用部分,YoloV4提取多特征層進(jìn)行目標(biāo)檢測(cè),一共提取三個(gè)特征層,分別位于中間層,中下層,底層,三個(gè)特征層的shape分別為(76,76,256)、(38,38,512)、(19,19,1024)。
2、輸出層的shape分別為(19,19,75),(38,38,75),(76,76,75),最后一個(gè)維度為75是因?yàn)樵搱D是基于voc數(shù)據(jù)集的,它的類(lèi)為20種,YoloV4只有針對(duì)每一個(gè)特征層存在3個(gè)先驗(yàn)框,所以最后維度為3x25;
如果使用的是coco訓(xùn)練集,類(lèi)則為80種,最后的維度應(yīng)該為255 = 3x85,三個(gè)特征層的shape為(19,19,255),(38,38,255),(76,76,255)
實(shí)現(xiàn)代碼如下:
#---------------------------------------------------#
# ? 特征層->最后的輸出
#---------------------------------------------------#
def yolo_body(inputs, num_anchors, num_classes):
# 省略了一部分,只看最后的head部分
? ? P3_output = DarknetConv2D_BN_Leaky(256, (3,3))(P3)
? ? P3_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P3_output)
? ? P4_output = DarknetConv2D_BN_Leaky(512, (3,3))(P4)
? ? P4_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P4_output)
? ? P5_output = DarknetConv2D_BN_Leaky(1024, (3,3))(P5)
? ? P5_output = DarknetConv2D(num_anchors*(num_classes+5), (1,1))(P5_output)
1
2
3
4
5
6
7
8
9
10
11
12
13
4、預(yù)測(cè)結(jié)果的解碼
由第二步我們可以獲得三個(gè)特征層的預(yù)測(cè)結(jié)果,shape分別為(N,19,19,255),(N,38,38,255),(N,76,76,255)的數(shù)據(jù),對(duì)應(yīng)每個(gè)圖分為19x19、38x38、76x76的網(wǎng)格上3個(gè)預(yù)測(cè)框的位置。
但是這個(gè)預(yù)測(cè)結(jié)果并不對(duì)應(yīng)著最終的預(yù)測(cè)框在圖片上的位置,還需要解碼才可以完成。
此處要講一下yolo3的預(yù)測(cè)原理,yolo3的3個(gè)特征層分別將整幅圖分為19x19、38x38、76x76的網(wǎng)格,每個(gè)網(wǎng)絡(luò)點(diǎn)負(fù)責(zé)一個(gè)區(qū)域的檢測(cè)。
我們知道特征層的預(yù)測(cè)結(jié)果對(duì)應(yīng)著三個(gè)預(yù)測(cè)框的位置,我們先將其reshape一下,其結(jié)果為(N,19,19,3,85),(N,38,38,3,85),(N,76,76,3,85)。
最后一個(gè)維度中的85包含了4+1+80,分別代表x_offset、y_offset、h和w、置信度、分類(lèi)結(jié)果。
yolo3的解碼過(guò)程就是將每個(gè)網(wǎng)格點(diǎn)加上它對(duì)應(yīng)的x_offset和y_offset,加完后的結(jié)果就是預(yù)測(cè)框的中心,然后再利用 先驗(yàn)框和h、w結(jié)合 計(jì)算出預(yù)測(cè)框的長(zhǎng)和寬。這樣就能得到整個(gè)預(yù)測(cè)框的位置了。
當(dāng)然得到最終的預(yù)測(cè)結(jié)構(gòu)后還要進(jìn)行得分排序與非極大抑制篩選
這一部分基本上是所有目標(biāo)檢測(cè)通用的部分。不過(guò)該項(xiàng)目的處理方式與其它項(xiàng)目不同。其對(duì)于每一個(gè)類(lèi)進(jìn)行判別。
1、取出每一類(lèi)得分大于self.obj_threshold的框和得分。
2、利用框的位置和得分進(jìn)行非極大抑制。
實(shí)現(xiàn)代碼如下,當(dāng)調(diào)用yolo_eval時(shí),就會(huì)對(duì)每個(gè)特征層進(jìn)行解碼:
#---------------------------------------------------#
# ? 將預(yù)測(cè)值的每個(gè)特征層調(diào)成真實(shí)值
#---------------------------------------------------#
def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
? ? num_anchors = len(anchors)
? ? # [1, 1, 1, num_anchors, 2]
? ? anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
? ? # 獲得x,y的網(wǎng)格
? ? # (19, 19, 1, 2)
? ? grid_shape = K.shape(feats)[1:3] # height, width
? ? grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
? ? ? ? [1, grid_shape[1], 1, 1])
? ? grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
? ? ? ? [grid_shape[0], 1, 1, 1])
? ? grid = K.concatenate([grid_x, grid_y])
? ? grid = K.cast(grid, K.dtype(feats))
? ? # (batch_size,19,19,3,85)
? ? feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
? ? # 將預(yù)測(cè)值調(diào)成真實(shí)值
? ? # box_xy對(duì)應(yīng)框的中心點(diǎn)
? ? # box_wh對(duì)應(yīng)框的寬和高
? ? box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
? ? box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
? ? box_confidence = K.sigmoid(feats[..., 4:5])
? ? box_class_probs = K.sigmoid(feats[..., 5:])
? ? # 在計(jì)算loss的時(shí)候返回如下參數(shù)
? ? if calc_loss == True:
? ? ? ? return grid, feats, box_xy, box_wh
? ? return box_xy, box_wh, box_confidence, box_class_probs
#---------------------------------------------------#
# ? 對(duì)box進(jìn)行調(diào)整,使其符合真實(shí)圖片的樣子
#---------------------------------------------------#
def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
? ? box_yx = box_xy[..., ::-1]
? ? box_hw = box_wh[..., ::-1]
? ??
? ? input_shape = K.cast(input_shape, K.dtype(box_yx))
? ? image_shape = K.cast(image_shape, K.dtype(box_yx))
? ? new_shape = K.round(image_shape * K.min(input_shape/image_shape))
? ? offset = (input_shape-new_shape)/2./input_shape
? ? scale = input_shape/new_shape
? ? box_yx = (box_yx - offset) * scale
? ? box_hw *= scale
? ? box_mins = box_yx - (box_hw / 2.)
? ? box_maxes = box_yx + (box_hw / 2.)
? ? boxes = ?K.concatenate([
? ? ? ? box_mins[..., 0:1], ?# y_min
? ? ? ? box_mins[..., 1:2], ?# x_min
? ? ? ? box_maxes[..., 0:1], ?# y_max
? ? ? ? box_maxes[..., 1:2] ?# x_max
? ? ])
? ? boxes *= K.concatenate([image_shape, image_shape])
? ? return boxes
#---------------------------------------------------#
# ? 獲取每個(gè)box和它的得分
#---------------------------------------------------#
def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape):
? ? # 將預(yù)測(cè)值調(diào)成真實(shí)值
? ? # box_xy對(duì)應(yīng)框的中心點(diǎn)
? ? # box_wh對(duì)應(yīng)框的寬和高
? ? # -1,19,19,3,2; -1,19,19,3,2; -1,19,19,3,1; -1,19,19,3,80
? ? box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats, anchors, num_classes, input_shape)
? ? # 將box_xy、和box_wh調(diào)節(jié)成y_min,y_max,xmin,xmax
? ? boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape)
? ? # 獲得得分和box
? ? boxes = K.reshape(boxes, [-1, 4])
? ? box_scores = box_confidence * box_class_probs
? ? box_scores = K.reshape(box_scores, [-1, num_classes])
? ? return boxes, box_scores
#---------------------------------------------------#
# ? 圖片預(yù)測(cè)
#---------------------------------------------------#
def yolo_eval(yolo_outputs,
? ? ? ? ? ? ? anchors,
? ? ? ? ? ? ? num_classes,
? ? ? ? ? ? ? image_shape,
? ? ? ? ? ? ? max_boxes=20,
? ? ? ? ? ? ? score_threshold=.6,
? ? ? ? ? ? ? iou_threshold=.5):
? ? # 獲得特征層的數(shù)量
? ? num_layers = len(yolo_outputs)
? ? # 特征層1對(duì)應(yīng)的anchor是678
? ? # 特征層2對(duì)應(yīng)的anchor是345
? ? # 特征層3對(duì)應(yīng)的anchor是012
? ? anchor_mask = [[6,7,8], [3,4,5], [0,1,2]]
? ??
? ? input_shape = K.shape(yolo_outputs[0])[1:3] * 32
? ? boxes = []
? ? box_scores = []
? ? # 對(duì)每個(gè)特征層進(jìn)行處理
? ? for l in range(num_layers):
? ? ? ? _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], anchors[anchor_mask[l]], num_classes, input_shape, image_shape)
? ? ? ? boxes.append(_boxes)
? ? ? ? box_scores.append(_box_scores)
? ? # 將每個(gè)特征層的結(jié)果進(jìn)行堆疊
? ? boxes = K.concatenate(boxes, axis=0)
? ? box_scores = K.concatenate(box_scores, axis=0)
? ? mask = box_scores >= score_threshold
? ? max_boxes_tensor = K.constant(max_boxes, dtype='int32')
? ? boxes_ = []
? ? scores_ = []
? ? classes_ = []
? ? for c in range(num_classes):
? ? ? ? # 取出所有box_scores >= score_threshold的框,和成績(jī)
? ? ? ? class_boxes = tf.boolean_mask(boxes, mask[:, c])
? ? ? ? class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])
? ? ? ? # 非極大抑制,去掉box重合程度高的那一些
? ? ? ? nms_index = tf.image.non_max_suppression(
? ? ? ? ? ? class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold)
? ? ? ? # 獲取非極大抑制后的結(jié)果
? ? ? ? # 下列三個(gè)分別是
? ? ? ? # 框的位置,得分與種類(lèi)
? ? ? ? class_boxes = K.gather(class_boxes, nms_index)
? ? ? ? class_box_scores = K.gather(class_box_scores, nms_index)
? ? ? ? classes = K.ones_like(class_box_scores, 'int32') * c
? ? ? ? boxes_.append(class_boxes)
? ? ? ? scores_.append(class_box_scores)
? ? ? ? classes_.append(classes)
? ? boxes_ = K.concatenate(boxes_, axis=0)
? ? scores_ = K.concatenate(scores_, axis=0)
? ? classes_ = K.concatenate(classes_, axis=0)
? ? return boxes_, scores_, classes_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
5、在原圖上進(jìn)行繪制
通過(guò)第四步,我們可以獲得預(yù)測(cè)框在原圖上的位置,而且這些預(yù)測(cè)框都是經(jīng)過(guò)篩選的。這些篩選后的框可以直接繪制在圖片上,就可以獲得結(jié)果了。
YOLOV4的訓(xùn)練
1、YOLOV4的改進(jìn)訓(xùn)練技巧
a)、Mosaic數(shù)據(jù)增強(qiáng)
Yolov4的mosaic數(shù)據(jù)增強(qiáng)參考了CutMix數(shù)據(jù)增強(qiáng)方式,理論上具有一定的相似性!
CutMix數(shù)據(jù)增強(qiáng)方式利用兩張圖片進(jìn)行拼接。
但是mosaic利用了四張圖片,根據(jù)論文所說(shuō)其擁有一個(gè)巨大的優(yōu)點(diǎn)是豐富檢測(cè)物體的背景!且在BN計(jì)算的時(shí)候一下子會(huì)計(jì)算四張圖片的數(shù)據(jù)!
就像下圖這樣:
實(shí)現(xiàn)思路如下:
1、每次讀取四張圖片。
2、分別對(duì)四張圖片進(jìn)行翻轉(zhuǎn)、縮放、色域變化等,并且按照四個(gè)方向位置擺好。
3、進(jìn)行圖片的組合和框的組合
def rand(a=0, b=1):
? ? return np.random.rand()*(b-a) + a
def merge_bboxes(bboxes, cutx, cuty):
? ? merge_bbox = []
? ? for i in range(len(bboxes)):
? ? ? ? for box in bboxes[i]:
? ? ? ? ? ? tmp_box = []
? ? ? ? ? ? x1,y1,x2,y2 = box[0], box[1], box[2], box[3]
? ? ? ? ? ? if i == 0:
? ? ? ? ? ? ? ? if y1 > cuty or x1 > cutx:
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if y2 >= cuty and y1 <= cuty:
? ? ? ? ? ? ? ? ? ? y2 = cuty
? ? ? ? ? ? ? ? ? ? if y2-y1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if x2 >= cutx and x1 <= cutx:
? ? ? ? ? ? ? ? ? ? x2 = cutx
? ? ? ? ? ? ? ? ? ? if x2-x1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ??
? ? ? ? ? ? if i == 1:
? ? ? ? ? ? ? ? if y2 < cuty or x1 > cutx:
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if y2 >= cuty and y1 <= cuty:
? ? ? ? ? ? ? ? ? ? y1 = cuty
? ? ? ? ? ? ? ? ? ? if y2-y1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? if x2 >= cutx and x1 <= cutx:
? ? ? ? ? ? ? ? ? ? x2 = cutx
? ? ? ? ? ? ? ? ? ? if x2-x1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? if i == 2:
? ? ? ? ? ? ? ? if y2 < cuty or x2 < cutx:
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if y2 >= cuty and y1 <= cuty:
? ? ? ? ? ? ? ? ? ? y1 = cuty
? ? ? ? ? ? ? ? ? ? if y2-y1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if x2 >= cutx and x1 <= cutx:
? ? ? ? ? ? ? ? ? ? x1 = cutx
? ? ? ? ? ? ? ? ? ? if x2-x1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? if i == 3:
? ? ? ? ? ? ? ? if y1 > cuty or x2 < cutx:
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if y2 >= cuty and y1 <= cuty:
? ? ? ? ? ? ? ? ? ? y2 = cuty
? ? ? ? ? ? ? ? ? ? if y2-y1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? if x2 >= cutx and x1 <= cutx:
? ? ? ? ? ? ? ? ? ? x1 = cutx
? ? ? ? ? ? ? ? ? ? if x2-x1 < 5:
? ? ? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? tmp_box.append(x1)
? ? ? ? ? ? tmp_box.append(y1)
? ? ? ? ? ? tmp_box.append(x2)
? ? ? ? ? ? tmp_box.append(y2)
? ? ? ? ? ? tmp_box.append(box[-1])
? ? ? ? ? ? merge_bbox.append(tmp_box)
? ? return merge_bbox
def get_random_data(annotation_line, input_shape, random=True, hue=.1, sat=1.5, val=1.5, proc_img=True):
? ? '''random preprocessing for real-time data augmentation'''
? ? h, w = input_shape
? ? min_offset_x = 0.4
? ? min_offset_y = 0.4
? ? scale_low = 1-min(min_offset_x,min_offset_y)
? ? scale_high = scale_low+0.2
? ? image_datas = []?
? ? box_datas = []
? ? index = 0
? ? place_x = [0,0,int(w*min_offset_x),int(w*min_offset_x)]
? ? place_y = [0,int(h*min_offset_y),int(w*min_offset_y),0]
? ? for line in annotation_line:
? ? ? ? # 每一行進(jìn)行分割
? ? ? ? line_content = line.split()
? ? ? ? # 打開(kāi)圖片
? ? ? ? image = Image.open(line_content[0])
? ? ? ? image = image.convert("RGB")?
? ? ? ? # 圖片的大小
? ? ? ? iw, ih = image.size
? ? ? ? # 保存框的位置
? ? ? ? box = np.array([np.array(list(map(int,box.split(',')))) for box in line_content[1:]])
? ? ? ??
? ? ? ? # image.save(str(index)+".jpg")
? ? ? ? # 是否翻轉(zhuǎn)圖片
? ? ? ? flip = rand()<.5
? ? ? ? if flip and len(box)>0:
? ? ? ? ? ? image = image.transpose(Image.FLIP_LEFT_RIGHT)
? ? ? ? ? ? box[:, [0,2]] = iw - box[:, [2,0]]
? ? ? ? # 對(duì)輸入進(jìn)來(lái)的圖片進(jìn)行縮放
? ? ? ? new_ar = w/h
? ? ? ? scale = rand(scale_low, scale_high)
? ? ? ? if new_ar < 1:
? ? ? ? ? ? nh = int(scale*h)
? ? ? ? ? ? nw = int(nh*new_ar)
? ? ? ? else:
? ? ? ? ? ? nw = int(scale*w)
? ? ? ? ? ? nh = int(nw/new_ar)
? ? ? ? image = image.resize((nw,nh), Image.BICUBIC)
? ? ? ? # 進(jìn)行色域變換
? ? ? ? hue = rand(-hue, hue)
? ? ? ? sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
? ? ? ? val = rand(1, val) if rand()<.5 else 1/rand(1, val)
? ? ? ? x = rgb_to_hsv(np.array(image)/255.)
? ? ? ? x[..., 0] += hue
? ? ? ? x[..., 0][x[..., 0]>1] -= 1
? ? ? ? x[..., 0][x[..., 0]<0] += 1
? ? ? ? x[..., 1] *= sat
? ? ? ? x[..., 2] *= val
? ? ? ? x[x>1] = 1
? ? ? ? x[x<0] = 0
? ? ? ? image = hsv_to_rgb(x)
? ? ? ? image = Image.fromarray((image*255).astype(np.uint8))
? ? ? ? # 將圖片進(jìn)行放置,分別對(duì)應(yīng)四張分割圖片的位置
? ? ? ? dx = place_x[index]
? ? ? ? dy = place_y[index]
? ? ? ? new_image = Image.new('RGB', (w,h), (128,128,128))
? ? ? ? new_image.paste(image, (dx, dy))
? ? ? ? image_data = np.array(new_image)/255
? ? ? ? # Image.fromarray((image_data*255).astype(np.uint8)).save(str(index)+"distort.jpg")
? ? ? ??
? ? ? ? index = index + 1
? ? ? ? box_data = []
? ? ? ? # 對(duì)box進(jìn)行重新處理
? ? ? ? if len(box)>0:
? ? ? ? ? ? np.random.shuffle(box)
? ? ? ? ? ? box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
? ? ? ? ? ? box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
? ? ? ? ? ? box[:, 0:2][box[:, 0:2]<0] = 0
? ? ? ? ? ? box[:, 2][box[:, 2]>w] = w
? ? ? ? ? ? box[:, 3][box[:, 3]>h] = h
? ? ? ? ? ? box_w = box[:, 2] - box[:, 0]
? ? ? ? ? ? box_h = box[:, 3] - box[:, 1]
? ? ? ? ? ? box = box[np.logical_and(box_w>1, box_h>1)]
? ? ? ? ? ? box_data = np.zeros((len(box),5))
? ? ? ? ? ? box_data[:len(box)] = box
? ? ? ??
? ? ? ? image_datas.append(image_data)
? ? ? ? box_datas.append(box_data)
? ? ? ? img = Image.fromarray((image_data*255).astype(np.uint8))
? ? ? ? for j in range(len(box_data)):
? ? ? ? ? ? thickness = 3
? ? ? ? ? ? left, top, right, bottom ?= box_data[j][0:4]
? ? ? ? ? ? draw = ImageDraw.Draw(img)
? ? ? ? ? ? for i in range(thickness):
? ? ? ? ? ? ? ? draw.rectangle([left + i, top + i, right - i, bottom - i],outline=(255,255,255))
? ? ? ? img.show()
? ??
? ? # 將圖片分割,放在一起
? ? cutx = np.random.randint(int(w*min_offset_x), int(w*(1 - min_offset_x)))
? ? cuty = np.random.randint(int(h*min_offset_y), int(h*(1 - min_offset_y)))
? ? new_image = np.zeros([h,w,3])
? ? new_image[:cuty, :cutx, :] = image_datas[0][:cuty, :cutx, :]
? ? new_image[cuty:, :cutx, :] = image_datas[1][cuty:, :cutx, :]
? ? new_image[cuty:, cutx:, :] = image_datas[2][cuty:, cutx:, :]
? ? new_image[:cuty, cutx:, :] = image_datas[3][:cuty, cutx:, :]
? ? # 對(duì)框進(jìn)行進(jìn)一步的處理
? ? new_boxes = merge_bboxes(box_datas, cutx, cuty)
? ? return new_image, new_boxes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
b)、Label Smoothing平滑
標(biāo)簽平滑的思想很簡(jiǎn)單,具體公式如下:
new_onehot_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes
1
當(dāng)label_smoothing的值為0.01得時(shí)候,公式變成如下所示:
new_onehot_labels = y * (1 - 0.01) + 0.01 / num_classes
1
其實(shí)Label Smoothing平滑就是將標(biāo)簽進(jìn)行一個(gè)平滑,原始的標(biāo)簽是0、1,在平滑后變成0.005(如果是二分類(lèi))、0.995,也就是說(shuō)對(duì)分類(lèi)準(zhǔn)確做了一點(diǎn)懲罰,讓模型不可以分類(lèi)的太準(zhǔn)確,太準(zhǔn)確容易過(guò)擬合。
實(shí)現(xiàn)代碼如下:
#---------------------------------------------------#
# ? 平滑標(biāo)簽
#---------------------------------------------------#
def _smooth_labels(y_true, label_smoothing):
? ? num_classes = K.shape(y_true)[-1],
? ? label_smoothing = K.constant(label_smoothing, dtype=K.floatx())
? ? return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
1
2
3
4
5
6
7
c)、CIOU
IoU是比值的概念,對(duì)目標(biāo)物體的scale是不敏感的。然而常用的BBox的回歸損失優(yōu)化和IoU優(yōu)化不是完全等價(jià)的,尋常的IoU無(wú)法直接優(yōu)化沒(méi)有重疊的部分。
于是有人提出直接使用IOU作為回歸優(yōu)化loss,CIOU是其中非常優(yōu)秀的一種想法。
CIOU將目標(biāo)與anchor之間的距離,重疊率、尺度以及懲罰項(xiàng)都考慮進(jìn)去,使得目標(biāo)框回歸變得更加穩(wěn)定,不會(huì)像IoU和GIoU一樣出現(xiàn)訓(xùn)練過(guò)程中發(fā)散等問(wèn)題。而懲罰因子把預(yù)測(cè)框長(zhǎng)寬比擬合目標(biāo)框的長(zhǎng)寬比考慮進(jìn)去。
CIOU公式如下
CIOU=IOU?ρ2(b,bgt)c2?αvCIOU = IOU - \frac{\rho^2(b,b^{gt})}{c^2} - \alpha v
CIOU=IOU??
c?
2
?
ρ?
2
?(b,b?
gt
?)
??? ?
??αv
其中,ρ2(b,bgt)\rho^2(b,b^{gt})ρ?
2
?(b,b?
gt
?)分別代表了預(yù)測(cè)框和真實(shí)框的中心點(diǎn)的歐式距離。 c代表的是能夠同時(shí)包含預(yù)測(cè)框和真實(shí)框的最小閉包區(qū)域的對(duì)角線(xiàn)距離。
而α\alphaα和vvv的公式如下
α=v1?IOU+v\alpha = \frac{v}{1-IOU+v}
α=?
1?IOU+v
v
??? ?
?
v=4π2(arctanwgthgt?arctanwh)2v = \frac{4}{\pi ^2}(arctan\frac{w^{gt}}{h^{gt}}-arctan\frac{w}{h})^2
v=?
π?
2
?
4
??? ?
?(arctan?
h?
gt
?
w?
gt
?
??? ?
??arctan?
h
w
??? ?
?)?
2
?
把1-CIOU就可以得到相應(yīng)的LOSS了。
LOSSCIOU=1?IOU+ρ2(b,bgt)c2+αvLOSS_{CIOU} = 1 - IOU + \frac{\rho^2(b,b^{gt})}{c^2} + \alpha v
LOSS?
CIOU
??? ?
?=1?IOU+?
c?
2
?
ρ?
2
?(b,b?
gt
?)
??? ?
?+αv
def box_ciou(b1, b2):
? ? """
? ? 輸入為:
? ? ----------
? ? b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
? ? b2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh
? ? 返回為:
? ? -------
? ? ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)
? ? """
? ? # 求出預(yù)測(cè)框左上角右下角
? ? b1_xy = b1[..., :2]
? ? b1_wh = b1[..., 2:4]
? ? b1_wh_half = b1_wh/2.
? ? b1_mins = b1_xy - b1_wh_half
? ? b1_maxes = b1_xy + b1_wh_half
? ? # 求出真實(shí)框左上角右下角
? ? b2_xy = b2[..., :2]
? ? b2_wh = b2[..., 2:4]
? ? b2_wh_half = b2_wh/2.
? ? b2_mins = b2_xy - b2_wh_half
? ? b2_maxes = b2_xy + b2_wh_half
? ? # 求真實(shí)框和預(yù)測(cè)框所有的iou
? ? intersect_mins = K.maximum(b1_mins, b2_mins)
? ? intersect_maxes = K.minimum(b1_maxes, b2_maxes)
? ? intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
? ? intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
? ? b1_area = b1_wh[..., 0] * b1_wh[..., 1]
? ? b2_area = b2_wh[..., 0] * b2_wh[..., 1]
? ? union_area = b1_area + b2_area - intersect_area
? ? iou = intersect_area / (union_area + K.epsilon())
? ? # 計(jì)算中心的差距
? ? center_distance = K.sum(K.square(b1_xy - b2_xy), axis=-1)
? ? # 找到包裹兩個(gè)框的最小框的左上角和右下角
? ? enclose_mins = K.minimum(b1_mins, b2_mins)
? ? enclose_maxes = K.maximum(b1_maxes, b2_maxes)
? ? enclose_wh = K.maximum(enclose_maxes - enclose_mins, 0.0)
? ? # 計(jì)算對(duì)角線(xiàn)距離
? ? enclose_diagonal = K.sum(K.square(enclose_wh), axis=-1)
? ? # calculate ciou, add epsilon in denominator to avoid dividing by 0
? ? ciou = iou - 1.0 * (center_distance) / (enclose_diagonal + K.epsilon())
? ? # calculate param v and alpha to extend to CIoU
? ? v = 4*K.square(tf.math.atan2(b1_wh[..., 0], b1_wh[..., 1]) - tf.math.atan2(b2_wh[..., 0], b2_wh[..., 1])) / (math.pi * math.pi)
? ? alpha = v / (1.0 - iou + v)
? ? ciou = ciou - alpha * v
? ? ciou = K.expand_dims(ciou, -1)
? ? return ciou
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
d)、學(xué)習(xí)率余弦退火衰減
余弦退火衰減法,學(xué)習(xí)率會(huì)先上升再下降,這是退火優(yōu)化法的思想。(關(guān)于什么是退火算法可以百度。)
上升的時(shí)候使用線(xiàn)性上升,下降的時(shí)候模擬cos函數(shù)下降。
效果如圖所示:
余弦退火衰減有幾個(gè)比較必要的參數(shù):
1、learning_rate_base:學(xué)習(xí)率最高值。
2、warmup_learning_rate:最開(kāi)始的學(xué)習(xí)率。
3、warmup_steps:多少步長(zhǎng)后到達(dá)頂峰值。
實(shí)現(xiàn)方式如下,利用Callback實(shí)現(xiàn),與普通的ReduceLROnPlateau調(diào)用方式類(lèi)似:
import numpy as np
import matplotlib.pyplot as plt
import keras
from keras import backend as K
from keras.layers import Flatten,Conv2D,Dropout,Input,Dense,MaxPooling2D
from keras.models import Model
def cosine_decay_with_warmup(global_step,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?learning_rate_base,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?total_steps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?warmup_learning_rate=0.0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?warmup_steps=0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hold_base_rate_steps=0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?min_learn_rate=0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?):
? ? """
? ? 參數(shù):
? ? ? ? ? ? global_step: 上面定義的Tcur,記錄當(dāng)前執(zhí)行的步數(shù)。
? ? ? ? ? ? learning_rate_base:預(yù)先設(shè)置的學(xué)習(xí)率,當(dāng)warm_up階段學(xué)習(xí)率增加到learning_rate_base,就開(kāi)始學(xué)習(xí)率下降。
? ? ? ? ? ? total_steps: 是總的訓(xùn)練的步數(shù),等于epoch*sample_count/batch_size,(sample_count是樣本總數(shù),epoch是總的循環(huán)次數(shù))
? ? ? ? ? ? warmup_learning_rate: 這是warm up階段線(xiàn)性增長(zhǎng)的初始值
? ? ? ? ? ? warmup_steps: warm_up總的需要持續(xù)的步數(shù)
? ? ? ? ? ? hold_base_rate_steps: 這是可選的參數(shù),即當(dāng)warm up階段結(jié)束后保持學(xué)習(xí)率不變,知道hold_base_rate_steps結(jié)束后才開(kāi)始學(xué)習(xí)率下降
? ? """
? ? if total_steps < warmup_steps:
? ? ? ? raise ValueError('total_steps must be larger or equal to '
? ? ? ? ? ? ? ? ? ? ? ? ? ? 'warmup_steps.')
? ? #這里實(shí)現(xiàn)了余弦退火的原理,設(shè)置學(xué)習(xí)率的最小值為0,所以簡(jiǎn)化了表達(dá)式
? ? learning_rate = 0.5 * learning_rate_base * (1 + np.cos(np.pi *
? ? ? ? (global_step - warmup_steps - hold_base_rate_steps) / float(total_steps - warmup_steps - hold_base_rate_steps)))
? ? #如果hold_base_rate_steps大于0,表明在warm up結(jié)束后學(xué)習(xí)率在一定步數(shù)內(nèi)保持不變
? ? if hold_base_rate_steps > 0:
? ? ? ? learning_rate = np.where(global_step > warmup_steps + hold_base_rate_steps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? learning_rate, learning_rate_base)
? ? if warmup_steps > 0:
? ? ? ? if learning_rate_base < warmup_learning_rate:
? ? ? ? ? ? raise ValueError('learning_rate_base must be larger or equal to '
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 'warmup_learning_rate.')
? ? ? ? #線(xiàn)性增長(zhǎng)的實(shí)現(xiàn)
? ? ? ? slope = (learning_rate_base - warmup_learning_rate) / warmup_steps
? ? ? ? warmup_rate = slope * global_step + warmup_learning_rate
? ? ? ? #只有當(dāng)global_step 仍然處于warm up階段才會(huì)使用線(xiàn)性增長(zhǎng)的學(xué)習(xí)率warmup_rate,否則使用余弦退火的學(xué)習(xí)率learning_rate
? ? ? ? learning_rate = np.where(global_step < warmup_steps, warmup_rate,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? learning_rate)
? ? learning_rate = max(learning_rate,min_learn_rate)
? ? return learning_rate
class WarmUpCosineDecayScheduler(keras.callbacks.Callback):
? ? """
? ? 繼承Callback,實(shí)現(xiàn)對(duì)學(xué)習(xí)率的調(diào)度
? ? """
? ? def __init__(self,
? ? ? ? ? ? ? ? ?learning_rate_base,
? ? ? ? ? ? ? ? ?total_steps,
? ? ? ? ? ? ? ? ?global_step_init=0,
? ? ? ? ? ? ? ? ?warmup_learning_rate=0.0,
? ? ? ? ? ? ? ? ?warmup_steps=0,
? ? ? ? ? ? ? ? ?hold_base_rate_steps=0,
? ? ? ? ? ? ? ? ?min_learn_rate=0,
? ? ? ? ? ? ? ? ?verbose=0):
? ? ? ? super(WarmUpCosineDecayScheduler, self).__init__()
? ? ? ? # 基礎(chǔ)的學(xué)習(xí)率
? ? ? ? self.learning_rate_base = learning_rate_base
? ? ? ? # 總共的步數(shù),訓(xùn)練完所有世代的步數(shù)epochs * sample_count / batch_size
? ? ? ? self.total_steps = total_steps
? ? ? ? # 全局初始化step
? ? ? ? self.global_step = global_step_init
? ? ? ? # 熱調(diào)整參數(shù)
? ? ? ? self.warmup_learning_rate = warmup_learning_rate
? ? ? ? # 熱調(diào)整步長(zhǎng),warmup_epoch * sample_count / batch_size
? ? ? ? self.warmup_steps = warmup_steps
? ? ? ? self.hold_base_rate_steps = hold_base_rate_steps
? ? ? ? # 參數(shù)顯示 ?
? ? ? ? self.verbose = verbose
? ? ? ? # learning_rates用于記錄每次更新后的學(xué)習(xí)率,方便圖形化觀察
? ? ? ? self.min_learn_rate = min_learn_rate
? ? ? ? self.learning_rates = []
?? ?#更新global_step,并記錄當(dāng)前學(xué)習(xí)率
? ? def on_batch_end(self, batch, logs=None):
? ? ? ? self.global_step = self.global_step + 1
? ? ? ? lr = K.get_value(self.model.optimizer.lr)
? ? ? ? self.learning_rates.append(lr)
?? ?#更新學(xué)習(xí)率
? ? def on_batch_begin(self, batch, logs=None):
? ? ? ? lr = cosine_decay_with_warmup(global_step=self.global_step,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? learning_rate_base=self.learning_rate_base,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? total_steps=self.total_steps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_learning_rate=self.warmup_learning_rate,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? warmup_steps=self.warmup_steps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? hold_base_rate_steps=self.hold_base_rate_steps,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? min_learn_rate = self.min_learn_rate)
? ? ? ? K.set_value(self.model.optimizer.lr, lr)
? ? ? ? if self.verbose > 0:
? ? ? ? ? ? print('\nBatch %05d: setting learning '
? ? ? ? ? ? ? ? ? 'rate to %s.' % (self.global_step + 1, lr))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
2、loss組成
a)、計(jì)算loss所需參數(shù)
在計(jì)算loss的時(shí)候,實(shí)際上是y_pre和y_true之間的對(duì)比:
y_pre就是一幅圖像經(jīng)過(guò)網(wǎng)絡(luò)之后的輸出,內(nèi)部含有三個(gè)特征層的內(nèi)容;其需要解碼才能夠在圖上作畫(huà)
y_true就是一個(gè)真實(shí)圖像中,它的每個(gè)真實(shí)框?qū)?yīng)的(19,19)、(38,38)、(76,76)網(wǎng)格上的偏移位置、長(zhǎng)寬與種類(lèi)。其仍需要編碼才能與y_pred的結(jié)構(gòu)一致
實(shí)際上y_pre和y_true內(nèi)容的shape都是
(batch_size,19,19,3,85)
(batch_size,38,38,3,85)
(batch_size,76,76,3,85)
b)、y_pre是什么
網(wǎng)絡(luò)最后輸出的內(nèi)容就是三個(gè)特征層每個(gè)網(wǎng)格點(diǎn)對(duì)應(yīng)的預(yù)測(cè)框及其種類(lèi),即三個(gè)特征層分別對(duì)應(yīng)著圖片被分為不同size的網(wǎng)格后,每個(gè)網(wǎng)格點(diǎn)上三個(gè)先驗(yàn)框?qū)?yīng)的位置、置信度及其種類(lèi)。
對(duì)于輸出的y1、y2、y3而言,[…, : 2]指的是相對(duì)于每個(gè)網(wǎng)格點(diǎn)的偏移量,[…, 2: 4]指的是寬和高,[…, 4: 5]指的是該框的置信度,[…, 5: ]指的是每個(gè)種類(lèi)的預(yù)測(cè)概率。
現(xiàn)在的y_pre還是沒(méi)有解碼的,解碼了之后才是真實(shí)圖像上的情況。
c)、y_true是什么。
y_true就是一個(gè)真實(shí)圖像中,它的每個(gè)真實(shí)框?qū)?yīng)的(19,19)、(38,38)、(76,76)網(wǎng)格上的偏移位置、長(zhǎng)寬與種類(lèi)。其仍需要編碼才能與y_pred的結(jié)構(gòu)一致
在yolo4中,其使用了一個(gè)專(zhuān)門(mén)的函數(shù)用于處理讀取進(jìn)來(lái)的圖片的框的真實(shí)情況。
def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
1
其輸入為:
true_boxes:shape為(m, T, 5)代表m張圖T個(gè)框的x_min、y_min、x_max、y_max、class_id。
input_shape:輸入的形狀,此處為608、608
anchors:代表9個(gè)先驗(yàn)框的大小
num_classes:種類(lèi)的數(shù)量。
其實(shí)對(duì)真實(shí)框的處理是將真實(shí)框轉(zhuǎn)化成圖片中相對(duì)網(wǎng)格的xyhw,步驟如下:
1、取框的真實(shí)值,獲取其框的中心及其寬高,除去input_shape變成比例的模式。
2、建立全為0的y_true,y_true是一個(gè)列表,包含三個(gè)特征層,shape分別為(batch_size,19,19,3,85)、(batch_size,38,38,3,85)、(batch_size,76,76,3,85)。
3、對(duì)每一張圖片處理,將每一張圖片中的真實(shí)框的wh和先驗(yàn)框的wh對(duì)比,計(jì)算IOU值,選取其中IOU最高的一個(gè),得到其所屬特征層及其網(wǎng)格點(diǎn)的位置,在對(duì)應(yīng)的y_true中將內(nèi)容進(jìn)行保存。
for t, n in enumerate(best_anchor):
? ? for l in range(num_layers):
? ? ? ? if n in anchor_mask[l]:
? ? ? ? ? ? # 計(jì)算該目標(biāo)在第l個(gè)特征層所處網(wǎng)格的位置
? ? ? ? ? ? i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
? ? ? ? ? ? j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
? ? ? ? ? ? # 找到best_anchor索引的索引
? ? ? ? ? ? k = anchor_mask[l].index(n)
? ? ? ? ? ? c = true_boxes[b,t, 4].astype('int32')
? ? ? ? ? ??
? ? ? ? ? ? # 保存到y(tǒng)_true中
? ? ? ? ? ? y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
? ? ? ? ? ? y_true[l][b, j, i, k, 4] = 1
? ? ? ? ? ? y_true[l][b, j, i, k, 5+c] = 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
對(duì)于最后輸出的y_true而言,只有每個(gè)圖里每個(gè)框最對(duì)應(yīng)的位置有數(shù)據(jù),其它的地方都為0。
preprocess_true_boxes全部的代碼如下:
#---------------------------------------------------#
# ? 讀入xml文件,并輸出y_true
#---------------------------------------------------#
def preprocess_true_boxes(true_boxes, input_shape, anchors, num_classes):
? ? assert (true_boxes[..., 4]<num_classes).all(), 'class id must be less than num_classes'
? ? # 一共有三個(gè)特征層數(shù)
? ? num_layers = len(anchors)//3
? ? # 先驗(yàn)框
? ? # 678為116,90, ?156,198, ?373,326
? ? # 345為30,61, ?62,45, ?59,119
? ? # 012為10,13, ?16,30, ?33,23, ?
? ? anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
? ? true_boxes = np.array(true_boxes, dtype='float32')
? ? input_shape = np.array(input_shape, dtype='int32') # 416,416
? ? # 讀出xy軸,讀出長(zhǎng)寬
? ? # 中心點(diǎn)(m,n,2)
? ? boxes_xy = (true_boxes[..., 0:2] + true_boxes[..., 2:4]) // 2
? ? boxes_wh = true_boxes[..., 2:4] - true_boxes[..., 0:2]
? ? # 計(jì)算比例
? ? true_boxes[..., 0:2] = boxes_xy/input_shape[:]
? ? true_boxes[..., 2:4] = boxes_wh/input_shape[:]
? ? # m張圖
? ? m = true_boxes.shape[0]
? ? # 得到網(wǎng)格的shape為19,19;38,38;76,76
? ? grid_shapes = [input_shape//{0:32, 1:16, 2:8}[l] for l in range(num_layers)]
? ? # y_true的格式為(m,19,19,3,85)(m,38,38,3,85)(m,76,76,3,85)
? ? y_true = [np.zeros((m,grid_shapes[l][0],grid_shapes[l][1],len(anchor_mask[l]),5+num_classes),
? ? ? ? dtype='float32') for l in range(num_layers)]
? ? # [1,9,2]
? ? anchors = np.expand_dims(anchors, 0)
? ? anchor_maxes = anchors / 2.
? ? anchor_mins = -anchor_maxes
? ? # 長(zhǎng)寬要大于0才有效
? ? valid_mask = boxes_wh[..., 0]>0
? ? for b in range(m):
? ? ? ? # 對(duì)每一張圖進(jìn)行處理
? ? ? ? wh = boxes_wh[b, valid_mask[b]]
? ? ? ? if len(wh)==0: continue
? ? ? ? # [n,1,2]
? ? ? ? wh = np.expand_dims(wh, -2)
? ? ? ? box_maxes = wh / 2.
? ? ? ? box_mins = -box_maxes
? ? ? ? # 計(jì)算真實(shí)框和哪個(gè)先驗(yàn)框最契合
? ? ? ? intersect_mins = np.maximum(box_mins, anchor_mins)
? ? ? ? intersect_maxes = np.minimum(box_maxes, anchor_maxes)
? ? ? ? intersect_wh = np.maximum(intersect_maxes - intersect_mins, 0.)
? ? ? ? intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
? ? ? ? box_area = wh[..., 0] * wh[..., 1]
? ? ? ? anchor_area = anchors[..., 0] * anchors[..., 1]
? ? ? ? iou = intersect_area / (box_area + anchor_area - intersect_area)
? ? ? ? # 維度是(n) 感謝 消盡不死鳥(niǎo) 的提醒
? ? ? ? best_anchor = np.argmax(iou, axis=-1)
? ? ? ? for t, n in enumerate(best_anchor):
? ? ? ? ? ? for l in range(num_layers):
? ? ? ? ? ? ? ? if n in anchor_mask[l]:
? ? ? ? ? ? ? ? ? ? # floor用于向下取整
? ? ? ? ? ? ? ? ? ? i = np.floor(true_boxes[b,t,0]*grid_shapes[l][1]).astype('int32')
? ? ? ? ? ? ? ? ? ? j = np.floor(true_boxes[b,t,1]*grid_shapes[l][0]).astype('int32')
? ? ? ? ? ? ? ? ? ? # 找到真實(shí)框在特征層l中第b副圖像對(duì)應(yīng)的位置
? ? ? ? ? ? ? ? ? ? k = anchor_mask[l].index(n)
? ? ? ? ? ? ? ? ? ? c = true_boxes[b,t, 4].astype('int32')
? ? ? ? ? ? ? ? ? ? y_true[l][b, j, i, k, 0:4] = true_boxes[b,t, 0:4]
? ? ? ? ? ? ? ? ? ? y_true[l][b, j, i, k, 4] = 1
? ? ? ? ? ? ? ? ? ? y_true[l][b, j, i, k, 5+c] = 1
? ? return y_true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
d)、loss的計(jì)算過(guò)程
在得到了y_pre和y_true后怎么對(duì)比呢?不是簡(jiǎn)單的減一下!
loss值需要對(duì)三個(gè)特征層進(jìn)行處理,這里以最小的特征層為例。
1、利用y_true取出該特征層中真實(shí)存在目標(biāo)的點(diǎn)的位置(m,19,19,3,1)及其對(duì)應(yīng)的種類(lèi)(m,19,19,3,80)。
2、將yolo_outputs的預(yù)測(cè)值輸出進(jìn)行處理,得到reshape后的預(yù)測(cè)值y_pre,shape為(m,19,19,3,85)。還有解碼后的xy,wh。
3、對(duì)于每一幅圖,計(jì)算其中所有真實(shí)框與預(yù)測(cè)框的IOU,如果某些預(yù)測(cè)框和真實(shí)框的重合程度大于0.5,則忽略。
4、計(jì)算ciou作為回歸的loss,這里只計(jì)算正樣本的回歸loss。
5、計(jì)算置信度的loss,其有兩部分構(gòu)成,第一部分是實(shí)際上存在目標(biāo)的,預(yù)測(cè)結(jié)果中置信度的值與1對(duì)比;第二部分是實(shí)際上不存在目標(biāo)的,在第四步中得到其最大IOU的值與0對(duì)比。
6、計(jì)算預(yù)測(cè)種類(lèi)的loss,其計(jì)算的是實(shí)際上存在目標(biāo)的,預(yù)測(cè)類(lèi)與真實(shí)類(lèi)的差距。
其實(shí)際上計(jì)算的總的loss是三個(gè)loss的和,這三個(gè)loss分別是:
實(shí)際存在的框,CIOU LOSS。
實(shí)際存在的框,預(yù)測(cè)結(jié)果中置信度的值與1對(duì)比;實(shí)際不存在的框,在上述步驟中,在第四步中得到其最大IOU的值與0對(duì)比。
實(shí)際存在的框,種類(lèi)預(yù)測(cè)結(jié)果與實(shí)際結(jié)果的對(duì)比。
其實(shí)際代碼如下,使用yolo_loss就可以獲得loss值:
#---------------------------------------------------#
# ? 平滑標(biāo)簽
#---------------------------------------------------#
def _smooth_labels(y_true, label_smoothing):
? ? num_classes = K.shape(y_true)[-1],
? ? label_smoothing = K.constant(label_smoothing, dtype=K.floatx())
? ? return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
#---------------------------------------------------#
# ? 將預(yù)測(cè)值的每個(gè)特征層調(diào)成真實(shí)值
#---------------------------------------------------#
def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
? ? num_anchors = len(anchors)
? ? # [1, 1, 1, num_anchors, 2]
? ? anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
? ? # 獲得x,y的網(wǎng)格
? ? # (19,19, 1, 2)
? ? grid_shape = K.shape(feats)[1:3] # height, width
? ? grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),
? ? ? ? [1, grid_shape[1], 1, 1])
? ? grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
? ? ? ? [grid_shape[0], 1, 1, 1])
? ? grid = K.concatenate([grid_x, grid_y])
? ? grid = K.cast(grid, K.dtype(feats))
? ? # (batch_size,19,19,3,85)
? ? feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
? ? # 將預(yù)測(cè)值調(diào)成真實(shí)值
? ? # box_xy對(duì)應(yīng)框的中心點(diǎn)
? ? # box_wh對(duì)應(yīng)框的寬和高
? ? box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
? ? box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
? ? box_confidence = K.sigmoid(feats[..., 4:5])
? ? box_class_probs = K.sigmoid(feats[..., 5:])
? ? # 在計(jì)算loss的時(shí)候返回如下參數(shù)
? ? if calc_loss == True:
? ? ? ? return grid, feats, box_xy, box_wh
? ? return box_xy, box_wh, box_confidence, box_class_probs
#---------------------------------------------------#
# ? 用于計(jì)算每個(gè)預(yù)測(cè)框與真實(shí)框的iou
#---------------------------------------------------#
def box_iou(b1, b2):
? ? # 19,19,3,1,4
? ? # 計(jì)算左上角的坐標(biāo)和右下角的坐標(biāo)
? ? b1 = K.expand_dims(b1, -2)
? ? b1_xy = b1[..., :2]
? ? b1_wh = b1[..., 2:4]
? ? b1_wh_half = b1_wh/2.
? ? b1_mins = b1_xy - b1_wh_half
? ? b1_maxes = b1_xy + b1_wh_half
? ? # 1,n,4
? ? # 計(jì)算左上角和右下角的坐標(biāo)
? ? b2 = K.expand_dims(b2, 0)
? ? b2_xy = b2[..., :2]
? ? b2_wh = b2[..., 2:4]
? ? b2_wh_half = b2_wh/2.
? ? b2_mins = b2_xy - b2_wh_half
? ? b2_maxes = b2_xy + b2_wh_half
? ? # 計(jì)算重合面積
? ? intersect_mins = K.maximum(b1_mins, b2_mins)
? ? intersect_maxes = K.minimum(b1_maxes, b2_maxes)
? ? intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
? ? intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
? ? b1_area = b1_wh[..., 0] * b1_wh[..., 1]
? ? b2_area = b2_wh[..., 0] * b2_wh[..., 1]
? ? iou = intersect_area / (b1_area + b2_area - intersect_area)
? ? return iou
#---------------------------------------------------#
# ? loss值計(jì)算
#---------------------------------------------------#
def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, label_smoothing=0.1, print_loss=False):
? ? # 一共有三層
? ? num_layers = len(anchors)//3?
? ? # 將預(yù)測(cè)結(jié)果和實(shí)際ground truth分開(kāi),args是[*model_body.output, *y_true]
? ? # y_true是一個(gè)列表,包含三個(gè)特征層,shape分別為(m,19,19,3,85),(m,38,38,3,85),(m,76,76,3,85)。
? ? # yolo_outputs是一個(gè)列表,包含三個(gè)特征層,shape分別為(m,19,19,3,85),(m,38,38,3,85),(m,76,76,3,85)。
? ? y_true = args[num_layers:]
? ? yolo_outputs = args[:num_layers]
? ? # 先驗(yàn)框
? ? # 678為116,90, ?156,198, ?373,326
? ? # 345為30,61, ?62,45, ?59,119
? ? # 012為10,13, ?16,30, ?33,23, ?
? ? anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
? ? # 得到input_shpae為608,608?
? ? input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0]))
? ? loss = 0
? ? # 取出每一張圖片
? ? # m的值就是batch_size
? ? m = K.shape(yolo_outputs[0])[0]
? ? mf = K.cast(m, K.dtype(yolo_outputs[0]))
? ? # y_true是一個(gè)列表,包含三個(gè)特征層,shape分別為(m,19,19,3,85),(m,38,38,3,85),(m,76,76,3,85)。
? ? # yolo_outputs是一個(gè)列表,包含三個(gè)特征層,shape分別為(m,19,19,3,85),(m,38,38,3,85),(m,76,76,3,85)。
? ? for l in range(num_layers):
? ? ? ? # 以第一個(gè)特征層(m,19,19,3,85)為例子
? ? ? ? # 取出該特征層中存在目標(biāo)的點(diǎn)的位置。(m,19,19,3,1)
? ? ? ? object_mask = y_true[l][..., 4:5]
? ? ? ? # 取出其對(duì)應(yīng)的種類(lèi)(m,19,19,3,80)
? ? ? ? true_class_probs = y_true[l][..., 5:]
? ? ? ? if label_smoothing:
? ? ? ? ? ? true_class_probs = _smooth_labels(true_class_probs, label_smoothing)
? ? ? ? # 將yolo_outputs的特征層輸出進(jìn)行處理
? ? ? ? # grid為網(wǎng)格結(jié)構(gòu)(19,19,1,2),raw_pred為尚未處理的預(yù)測(cè)結(jié)果(m,19,19,3,85)
? ? ? ? # 還有解碼后的xy,wh,(m,19,19,3,2)
? ? ? ? grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l],
? ? ? ? ? ? ?anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True)
? ? ? ??
? ? ? ? # 這個(gè)是解碼后的預(yù)測(cè)的box的位置
? ? ? ? # (m,19,19,3,4)
? ? ? ? pred_box = K.concatenate([pred_xy, pred_wh])
? ? ? ? # 找到負(fù)樣本群組,第一步是創(chuàng)建一個(gè)數(shù)組,[]
? ? ? ? ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)
? ? ? ? object_mask_bool = K.cast(object_mask, 'bool')
? ? ? ??
? ? ? ? # 對(duì)每一張圖片計(jì)算ignore_mask
? ? ? ? def loop_body(b, ignore_mask):
? ? ? ? ? ? # 取出第b副圖內(nèi),真實(shí)存在的所有的box的參數(shù)
? ? ? ? ? ? # n,4
? ? ? ? ? ? true_box = tf.boolean_mask(y_true[l][b,...,0:4], object_mask_bool[b,...,0])
? ? ? ? ? ? # 計(jì)算預(yù)測(cè)結(jié)果與真實(shí)情況的iou
? ? ? ? ? ? # pred_box為19,19,3,4
? ? ? ? ? ? # 計(jì)算的結(jié)果是每個(gè)pred_box和其它所有真實(shí)框的iou
? ? ? ? ? ? # 19,19,3,n
? ? ? ? ? ? iou = box_iou(pred_box[b], true_box)
? ? ? ? ? ? # 19,19,3,1
? ? ? ? ? ? best_iou = K.max(iou, axis=-1)
? ? ? ? ? ? # 如果某些預(yù)測(cè)框和真實(shí)框的重合程度大于0.5,則忽略。
? ? ? ? ? ? ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))
? ? ? ? ? ? return b+1, ignore_mask
? ? ? ? # 遍歷所有的圖片
? ? ? ? _, ignore_mask = K.control_flow_ops.while_loop(lambda b,*args: b<m, loop_body, [0, ignore_mask])
? ? ? ? # 將每幅圖的內(nèi)容壓縮,進(jìn)行處理
? ? ? ? ignore_mask = ignore_mask.stack()
? ? ? ? #(m,19,19,3,1,1)
? ? ? ? ignore_mask = K.expand_dims(ignore_mask, -1)
? ? ? ? box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]
? ? ? ? # Calculate ciou loss as location loss
? ? ? ? raw_true_box = y_true[l][...,0:4]
? ? ? ? ciou = box_ciou(pred_box, raw_true_box)
? ? ? ? ciou_loss = object_mask * box_loss_scale * (1 - ciou)
? ? ? ? ciou_loss = K.sum(ciou_loss) / mf
? ? ? ? location_loss = ciou_loss
? ? ? ??
? ? ? ? # 如果該位置本來(lái)有框,那么計(jì)算1與置信度的交叉熵
? ? ? ? # 如果該位置本來(lái)沒(méi)有框,而且滿(mǎn)足best_iou<ignore_thresh,則被認(rèn)定為負(fù)樣本
? ? ? ? # best_iou<ignore_thresh用于限制負(fù)樣本數(shù)量
? ? ? ? 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)
? ? ? ? confidence_loss = K.sum(confidence_loss) / mf
? ? ? ? class_loss = K.sum(class_loss) / mf
? ? ? ? loss += location_loss + confidence_loss + class_loss
? ? ? ? if print_loss:
? ? ? ? ? ? loss = tf.Print(loss, [loss, location_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ')
? ? return loss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
訓(xùn)練自己的YOLOV4模型
yolo4整體的文件夾構(gòu)架如下:
本文使用VOC格式進(jìn)行訓(xùn)練。
訓(xùn)練前將標(biāo)簽文件放在VOCdevkit文件夾下的VOC2007文件夾下的Annotation中。
訓(xùn)練前將圖片文件放在VOCdevkit文件夾下的VOC2007文件夾下的JPEGImages中。
在訓(xùn)練前利用voc2yolo3.py文件生成對(duì)應(yīng)的txt。
再運(yùn)行根目錄下的voc_annotation.py,運(yùn)行前需要將classes改成你自己的classes。
classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
1
就會(huì)生成對(duì)應(yīng)的2007_train.txt,每一行對(duì)應(yīng)其圖片位置及其真實(shí)框的位置。
在訓(xùn)練前需要修改model_data里面的voc_classes.txt文件,需要將classes改成你自己的classes。
運(yùn)行train.py即可開(kāi)始訓(xùn)練。
?
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Keras搭建YoloV4目标检测平台的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 重磅更新!YoloV4最新论文与源码!权
- 下一篇: No module named ker