zoukankan      html  css  js  c++  java
  • 探索 YOLO v3 源码

    YOLO,即You Only Look Once的缩写,是一个基于卷积神经网络物体检测算法。而YOLO v3是YOLO的第3个版本,即YOLO、YOLO 9000、YOLO v3,检测效果,更准更强。

    更多细节,可以参考YOLO的官网。

    图片

    YOLO

    YOLO是一句美国的俗语,You Only Live Once,人生苦短,及时行乐。

    本文主要分享,如何实现YOLO v3的算法细节,Keras框架。这是第1篇,训练。当然还有第2篇,至第n篇,毕竟,这是一个完整版 :)

    本文的GitHub源码:

    https://github.com/SpikeKing/keras-yolo3-detection


    1.  参数

    模型的训练参数,共有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框集合:

    • 3个尺度的特征图,每个特征图3个anchor框,共9个框,从小到大排列;

    • 框1~3在大尺度52x52特征图中使用,框4~6是中尺度26x26,框7~9是小尺度13x13;

    • 大尺度特征图用于检测小物体,小尺度检测大物体;

    • 9个anchor来源于边界框的K-Means聚类。

    例如,COCO的anchors列表,如下:

    10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326

    (5) 图片输入尺寸,默认为416x416,选择416的原因是:

    • 图片尺寸满足32的倍数,在DarkNet网络中,执行5次步长为2卷积,降采样,其卷积操作如下:

    x = DarknetConv2D_BN_Leaky(num_filters, (3, 3), strides=(2, 2))(x)

    • 在最底层时,特征图尺寸需要满足为奇数,如13,以保证中心点落在唯一框中。如果为偶数时,则中心点落在中心的4个框中,导致歧义。


    2.  创建模型

    创建YOLOv3的网络模型,输入:

    • input_shape:图片尺寸;

    • anchors:9个anchor box;

    • num_classes:类别数;

    • freeze_body:冻结模式,1是冻结DarkNet53的层,2是冻结全部,只保留最后3层;

    • weights_path:预训练模型的权重。

    即:

    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]


    3.  样本数量

    样本洗牌,将数据集拆分为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  # 训练集数量


    4.  第1阶段训练

    第1阶段,冻结部分网络,只训练底层权重:

    • 优化器使用常见的Adam;

    • 损失函数,直接使用模型的输出y_pred,忽略真值y_true;

    即:

    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:

    • 把y_true当成输入,作为模型的多输入,把loss封装为层,作为输出;

    • 在模型中,最终输出的y_pred就是loss;

    • 在编译时,将loss设置为y_pred即可,无视y_true;

    • 在训练时,随意添加一个符合结构的y_true即可。

    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完成的模型权重,设置参数为:

    • 只存储权重(save_weights_only);

    • 只存储最优结果(save_best_only);

    • 每隔3个epoch存储一次(period)。

    即:

    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权重


    5.  第2阶段训练

    第2阶段,使用第1阶段已训练完成的网络权重,继续训练:

    • 将全部的权重都设置为可训练,而在第1阶段中,则是冻结部分权重;

    • 优化器,仍是Adam,只是学习率有所下降,从1e-3减少至1e-4;

    • 损失函数,仍是只使用y_pred,忽略y_true。

    实现:

    for i in range(len(model.layers)):    model.layers[i].trainable = True model.compile(optimizer=Adam(lr=1e-4),              loss={'yolo_loss': lambda y_true, y_pred: y_pred})

    第2阶段的模型fit数据,与第1阶段类似,从第50个epoch开始,一直训练到第100个epoch,当触发条件时,则提前终止。额外增加了两个回调reduce_lrearly_stopping,用于控制训练提取终止的时机:

    • reduce_lr:当评价指标不在提升时,减少学习率,每次减少10%,当验证损失值,持续3次未减少时,则终止训练。

    • early_stopping:当验证集损失值,连续增加小于0时,持续10个epoch,则终止训练。

    实现:

    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阶段训练完成之后,输出的网络权重,就是最终的模型权重。


    补充1.  K-Means

    K-Means算法是聚类算法,将一组数据划分为多个组,每组都含有一个中心。

    在YOLOv3中,获取数据集的全部anchor box,通过K-Means算法,将这些边界框的宽高,聚类为9类,获取9个聚类中心,面积从小到大排列,作为9个anchor box。

    模拟K-Means算法:

    • 创建测试点,X是数据,y是标签,如X:(300,2), y:(300,);

    • 将数据聚类为9类;

    • 输入数据X,训练;

    • 预测X的类别,为y_kmeans;

    • 使用scatter绘制散点图,颜色范围viridis;

    • 获取聚类中心cluster_centers_,以黑色点表示;

    源码:

    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_blobs def 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)  # 数据X    y_kmeans = kmeans.predict(X)  # 预测    # 颜色范围viridis: https://matplotlib.org/examples/color/colormaps_reference.html    plt.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


    补充2.  EarlyStopping

    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的参数:

    • monitor:监控数据的类型,支持acc、val_acc、loss、val_loss等;

    • min_delta:停止阈值,与mode参数配合,支持增加或下降;

    • mode:min是最少,max是最多,auto是自动,与min_delta配合;

    • patience:达到阈值之后,能够容忍的epoch数,避免停止在抖动中;

    • verbose:日志的繁杂程度,值越大,输出的信息越多。

    min_delta和patience需要相互配合,避免模型停止在抖动的过程中。min_delta降低,patience减少;而min_delta增加,则patience增加。

    例如:

    early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)


    OK, that's all! Enjoy it!

    By C. L. Wang @ 美图云 视觉技术部

  • 相关阅读:
    iOS8之后,UITableViewRowAction实现滑动多个按钮
    关于UINavigationController的一些技巧
    NSRegularExpression 使用
    UIWindow
    SVN:The working copy is locked due to a previous error (二)
    iOS监听电话来电、挂断、拨号等
    UIDeviceOrientation 和 UIInterfaceOrientation
    java_day03
    java_day 02
    java_day_02
  • 原文地址:https://www.cnblogs.com/shuimuqingyang/p/14132245.html
Copyright © 2011-2022 走看看