YOLO是一句美国的俗语,You Only Live Once,人生苦短,及时行乐。
本文主要分享,如何实现YOLO v3的算法细节,Keras框架。这是第1篇,训练。当然还有第2篇,至第n篇,毕竟,这是一个完整版 :)
本文的GitHub源码:
https://github.com/SpikeKing/keras-yolo3-detection
模型的训练参数,共有5个,即:
(1) 已标注边界框的图片数据集,其格式如下:
图片的位置 框的4个坐标和1个类别ID (xmin,ymin,xmax,ymax,id) ...
dataset/image.jpg 788,351,832,426,0 805,208,855,270,0
(2) 标注框类别的汇总,即数据集中所标注物体的全部类别,例如:
aeroplane
bicycle
bird
...
(3) 预训练模型,用于迁移学习中的微调,可选YOLO v3已训练完成的COCO模型权重,即:
pretrained_path = 'model_data/yolo_weights.h5'
(4) 预测特征图的anchor框集合:
例如,COCO的anchors列表,如下:
10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
(5) 图片输入尺寸,默认为416x416,选择416的原因是:
x = DarknetConv2D_BN_Leaky(num_filters, (3, 3), strides=(2, 2))(x)
创建YOLOv3的网络模型,输入:
即:
model = create_model(input_shape, anchors, num_classes,freeze_body=2,weights_path=pretrained_path)
其中,网络的最后3层是:3个1x1的卷积层,用于将3个尺度的特征图,转换为3个尺度的预测值。
即:
out_filters = num_anchors * (num_classes + 5)
// ...
DarknetConv2D(out_filters, (1, 1))
结构如下:
conv2d_59 (Conv2D) (None, 13, 13, 18) 18450 leaky_re_lu_58[0][0]
conv2d_67 (Conv2D) (None, 26, 26, 18) 9234 leaky_re_lu_65[0][0]
conv2d_75 (Conv2D) (None, 52, 52, 18) 4626 leaky_re_lu_72[0][0]
样本洗牌,将数据集拆分为10份,训练9份,验证1份,比较简单。
实现:
val_split = 0.1 # 训练和验证的比例with open(annotation_path) as f:
lines = f.readlines()
np.random.seed(47)
np.random.shuffle(lines)
np.random.seed(None)
num_val = int(len(lines) * val_split) # 验证集数量num_train = len(lines) - num_val # 训练集数量
第1阶段,冻结部分网络,只训练底层权重:
即:
model.compile(optimizer=Adam(lr=1e-3), loss={# 使用定制的 yolo_loss Lambda层'yolo_loss': lambda y_true, y_pred: y_pred}) # 损失函数
其中,关于损失函数yolo_loss,以及y_true和y_pred:
Python的Lambda表达式:
f = lambda y_true, y_pred: y_pred
print(f(1, 2)) # 输出2
模型fit数据,使用数据生成包装器,按批次生成训练和验证数据。最终,模型model存储权重。
实现如下:
batch_size = 32 # batch
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),steps_per_epoch=max(1, num_train // batch_size),validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),validation_steps=max(1, num_val // batch_size),epochs=50,initial_epoch=0,callbacks=[logging, checkpoint])
# 存储最终的去权重,再训练过程中,也通过回调存储
model.save_weights(log_dir + 'trained_weights_stage_1.h5')
同时,在训练过程中,也会不断保存,epoch完成的模型权重,设置参数为:
即:
checkpoint = ModelCheckpoint(log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',monitor='val_loss', save_weights_only=True,save_best_only=True, period=3) # 只存储weights权重
第2阶段,使用第1阶段已训练完成的网络权重,继续训练:
实现:
for i in range(len(model.layers)):model.layers[i].trainable = Truemodel.compile(optimizer=Adam(lr=1e-4),loss={'yolo_loss': lambda y_true, y_pred: y_pred})
第2阶段的模型fit数据,与第1阶段类似,从第50个epoch开始,一直训练到第100个epoch,当触发条件时,则提前终止。额外增加了两个回调reduce_lr和early_stopping,用于控制训练提取终止的时机:
实现:
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1) # 当评价指标不在提升时,减少学习率
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1) # 验证集准确率,下降前终止
batch_size = 32
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),steps_per_epoch=max(1, num_train // batch_size),validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors,num_classes),validation_steps=max(1, num_val // batch_size),epochs=100,initial_epoch=50,callbacks=[logging, checkpoint, reduce_lr, early_stopping])
model.save_weights(log_dir + 'trained_weights_final.h5')
至此,在第2阶段训练完成之后,输出的网络权重,就是最终的模型权重。
K-Means算法是聚类算法,将一组数据划分为多个组,每组都含有一个中心。
在YOLOv3中,获取数据集的全部anchor box,通过K-Means算法,将这些边界框的宽高,聚类为9类,获取9个聚类中心,面积从小到大排列,作为9个anchor box。
模拟K-Means算法:
源码:
import matplotlib.pyplot as plt
import seaborn as sns
sns.set() # for plot styling
from sklearn.cluster import KMeans
from sklearn.datasets.samples_generator import make_blobsdef test_of_k_means():# 创建测试点,X是数据,y是标签,X:(300,2), y:(300,)X, y_true = make_blobs(n_samples=300, centers=9, cluster_std=0.60, random_state=0)kmeans = KMeans(n_clusters=9) # 将数据聚类kmeans.fit(X) # 数据Xy_kmeans = kmeans.predict(X) # 预测# 颜色范围viridis: https://matplotlib.org/examples/color/colormaps_reference.htmlplt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=20, cmap='viridis') # c是颜色,s是大小centers = kmeans.cluster_centers_ # 聚类的中心plt.scatter(centers[:, 0], centers[:, 1], c='black', s=40, alpha=0.5) # 中心点为黑色plt.show() # 展示if __name__ == '__main__':test_of_k_means()
输出:
K-Means
EarlyStopping是Callback的子类,Callback用于指定在每个阶段开始和结束时,执行的操作。在Callback中,有已经实现的简单子类,如acc、val、loss和val_loss等,还有复杂子类,如ModelCheckpoint和TensorBoard等。
Callback的回调接口,如下:
def on_epoch_begin(self, epoch, logs=None):
def on_epoch_end(self, epoch, logs=None):
def on_batch_begin(self, batch, logs=None):
def on_batch_end(self, batch, logs=None):
def on_train_begin(self, logs=None):
def on_train_end(self, logs=None):
EarlyStopping是提前停止训练的Callback子类。具体地,当训练或验证集中的loss不再减小,即减小的程度小于某个阈值,则会停止训练。这样做,可以提高调参效率,避免浪费资源。
在model的fit数据时,以列表设置callbacks回调,支持设置多个Callback,如:
callbacks=[logging, checkpoint, reduce_lr, early_stopping]
EarlyStopping的参数:
min_delta和patience需要相互配合,避免模型停止在抖动的过程中。min_delta降低,patience减少;而min_delta增加,则patience增加。
例如:
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)
@online{Wang2021Nov,
author = {Wang, C. L.},
title = {{探索 YOLO v3 源码 - 第1篇 训练}},
organization = {微信公众平台},
year = {2021},
month = {11},
date = {2021-11-22},
urldate = {2021-11-22},
note = {[Online; accessed 22. Nov. 2021]},
url = {https://mp.weixin.qq.com/s/T9LshbXoervdJDBuP564dQ},
abstract = {{本文主要分享,如何实现YOLO v3的算法细节,Keras框架。这是第1篇,训练。当然还有第2篇,至第n篇,毕竟,这是一个完整版 :)}}
}