人工智能学习83-Yolo训练类

人工智能学习83-Yolo训练类—快手视频
人工智能学习83-Yolo训练类—快手视频
人工智能学习83-Yolo训练类—快手视频
人工智能学习83-Yolo训练类—快手视频

Yolo训练类

import datetime
import os

import keras.backend as K
import tensorflow as tf
tf.compat.v1.disable_v2_behavior()
#tf.compat.v1.disable_eager_execution()

from keras.callbacks import (EarlyStopping, LearningRateScheduler,
                             ModelCheckpoint, TensorBoard)
from keras.layers import Conv2D, Dense, DepthwiseConv2D
from keras.optimizers import SGD, Adam

from keras.regularizers import l2
from keras.utils.multi_gpu_utils import multi_gpu_model

from yolo_model import get_train_model, get_yolo_model
from yolo_training import get_lr_scheduler
from callbacks import (ExponentDecayScheduler, LossHistory,
                             ParallelModelCheckpoint, EvalCallback, TestCallback)
from dataloader import YoloDatasets
from utils import get_anchors, get_classes, show_config


'''
训练自己的目标检测模型一定需要注意以下几点:
1、训练前仔细检查自己的格式是否满足要求,该库要求数据集格式为VOC格式,需要准备好的内容有输入图片和标签
   输入图片为.jpg图片,无需固定大小,传入训练前会自动进行resize。
   灰度图会自动转成RGB图片进行训练,无需自己修改。
   输入图片如果后缀非jpg,需要自己批量转成jpg后再开始训练。

   标签为.xml格式,文件中会有需要检测的目标信息,标签文件和输入图片文件相对应。

2、损失值的大小用于判断是否收敛,比较重要的是有收敛的趋势,即验证集损失不断下降,如果验证集损失基本上不改变的话,模型基本上就收敛了。
   损失值的具体大小并没有什么意义,大和小只在于损失的计算方式,并不是接近于0才好。如果想要让损失好看点,可以直接到对应的损失函数里面除上10000。
   训练过程中的损失值会保存在logs文件夹下的loss_%Y_%m_%d_%H_%M_%S文件夹中
   
3、训练好的权值文件保存在logs文件夹中,每个训练世代(Epoch)包含若干训练步长(Step),每个训练步长(Step)进行一次梯度下降。
   如果只是训练了几个Step是不会保存的,Epoch和Step的概念要捋清楚一下。
'''
if __name__ == "__main__":
    #---------------------------------------------------------------------#
    #   train_gpu   训练用到的GPU
    #               默认为第一张卡、双卡为[0, 1]、三卡为[0, 1, 2]
    #               在使用多GPU时,每个卡上的batch为总batch除以卡的数量。
    #   [0,]使用GPU,[1,]使用cpu
    #---------------------------------------------------------------------#
    train_gpu       = [1,]
    #---------------------------------------------------------------------#
    #   classes_path    指向model_data下的txt,与自己训练的数据集相关
    #                   训练前一定要修改classes_path,使其对应自己的数据集
    #---------------------------------------------------------------------#
    classes_path    = '../model_data/coco_classes.txt' #原来是:voc_classes.txt coco_classes.txt
    #---------------------------------------------------------------------#
    #   anchors_path    代表先验框对应的txt文件,一般不修改。
    #   anchors_mask    用于帮助代码找到对应的先验框,是先验框的序号,一般不修改。
    #---------------------------------------------------------------------#
    anchors_path    = '../model_data/yolo_anchors.txt'
    anchors_mask    = [[6, 7, 8], [3, 4, 5], [0, 1, 2]] #获取先验框anchors的索引
    #----------------------------------------------------------------------------------------------------------------------------#
    #   权值文件的下载请看README,可以通过网盘下载。模型的 预训练权重 对不同数据集是通用的,因为特征是通用的。
    #   模型的 预训练权重 比较重要的部分是 主干特征提取网络的权值部分,用于进行特征提取。
    #   预训练权重对于99%的情况都必须要用,不用的话主干部分的权值太过随机,特征提取效果不明显,网络训练的结果也不会好
    #
    #   如果训练过程中存在中断训练的操作,可以将model_path设置成logs文件夹下的权值文件,将已经训练了一部分的权值再次载入。
    #   同时修改下方的 冻结阶段 或者 解冻阶段 的参数,来保证模型epoch的连续性。
    #
    #   当model_path = ''的时候不加载整个模型的权值。
    #
    #   此处使用的是整个模型的权重,因此是在train.py进行加载的。
    #   如果想要让模型从主干的预训练权值开始训练,则设置model_path为主干网络的权值,此时仅加载主干。
    #   如果想要让模型从0开始训练,则设置model_path = '',Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
    #
    #   一般来讲,网络从0开始的训练效果会很差,因为权值太过随机,特征提取效果不明显,因此非常、非常、非常不建议大家从0开始训练!
    #   如果一定要从0开始,可以了解imagenet数据集,首先训练分类模型,获得网络的主干部分权值,分类模型的 主干部分 和该模型通用,基于此进行训练。
    #----------------------------------------------------------------------------------------------------------------------------#
    model_path      = '../model_data/yolo_weights.h5'
    #------------------------------------------------------#
    #   input_shape     输入的shape大小,一定要是32的倍数  H*W
    #------------------------------------------------------#
    input_shape     = [416, 416]

    #----------------------------------------------------------------------------------------------------------------------------#
    #   训练分为两个阶段,分别是冻结阶段和解冻阶段。设置冻结阶段是为了满足机器性能不足的同学的训练需求。
    #   冻结训练需要的显存较小,显卡非常差的情况下,可设置Freeze_Epoch等于UnFreeze_Epoch,此时仅仅进行冻结训练。
    #
    #   在此提供若干参数设置建议,各位训练者根据自己的需求进行灵活调整:
    #   (一)从整个模型的预训练权重开始训练:
    #       Adam:
    #           Init_Epoch = 60,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 1e-3,weight_decay = 0。(冻结)
    #           Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 1e-3,weight_decay = 0。(不冻结)
    #       SGD:
    #           Init_Epoch = 60,Freeze_Epoch = 50,UnFreeze_Epoch = 300,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 1e-2,weight_decay = 5e-4。(冻结)
    #           Init_Epoch = 0,UnFreeze_Epoch = 300,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 1e-2,weight_decay = 5e-4。(不冻结)
    #       其中:UnFreeze_Epoch可以在100-300之间调整。
    #   (二)从主干网络的预训练权重开始训练:
    #       Adam:
    #           Init_Epoch = 60,Freeze_Epoch = 50,UnFreeze_Epoch = 100,Freeze_Train = True,optimizer_type = 'adam',Init_lr = 1e-3,weight_decay = 0。(冻结)
    #           Init_Epoch = 0,UnFreeze_Epoch = 100,Freeze_Train = False,optimizer_type = 'adam',Init_lr = 1e-3,weight_decay = 0。(不冻结)
    #       SGD:
    #           Init_Epoch = 60,Freeze_Epoch = 50,UnFreeze_Epoch = 300,Freeze_Train = True,optimizer_type = 'sgd',Init_lr = 1e-2,weight_decay = 5e-4。(冻结)
    #           Init_Epoch = 0,UnFreeze_Epoch = 300,Freeze_Train = False,optimizer_type = 'sgd',Init_lr = 1e-2,weight_decay = 5e-4。(不冻结)
    #       其中:由于从主干网络的预训练权重开始训练,主干的权值不一定适合目标检测,需要更多的训练跳出局部最优解。
    #             UnFreeze_Epoch可以在150-300之间调整,YOLOV5和YOLOX均推荐使用300。
    #             Adam相较于SGD收敛的快一些。因此UnFreeze_Epoch理论上可以小一点,但依然推荐更多的Epoch。
    #   (三)batch_size的设置:
    #       在显卡能够接受的范围内,以大为好。显存不足与数据集大小无关,提示显存不足(OOM或者CUDA out of memory)请调小batch_size。
    #       受到BatchNorm层影响,batch_size最小为2,不能为1。
    #       正常情况下Freeze_batch_size建议为Unfreeze_batch_size的1-2倍。不建议设置的差距过大,因为关系到学习率的自动调整。
    #----------------------------------------------------------------------------------------------------------------------------#
    #------------------------------------------------------------------#
    #   冻结阶段训练参数
    #   此时模型的主干被冻结了,特征提取网络不发生改变
    #   占用的显存较小,仅对网络进行微调
    #   Init_Epoch          模型当前开始的训练世代,其值可以大于Freeze_Epoch,如设置:
    #                       Init_Epoch = 60、Freeze_Epoch = 50、UnFreeze_Epoch = 100
    #                       会跳过冻结阶段,直接从60代开始,并调整对应的学习率。
    #                       (断点续练时使用)
    #   Freeze_Epoch        模型冻结训练的Freeze_Epoch
    #                       (当Freeze_Train=False时失效)
    #   Freeze_batch_size   模型冻结训练的batch_size
    #                       (当Freeze_Train=False时失效)
    #------------------------------------------------------------------#
    Init_Epoch          = 60
    Freeze_Epoch        = 50
    Freeze_batch_size   = 16
    #------------------------------------------------------------------#
    #   解冻阶段训练参数
    #   此时模型的主干不被冻结了,特征提取网络会发生改变
    #   占用的显存较大,网络所有的参数都会发生改变
    #   UnFreeze_Epoch          模型总共训练的epoch
    #                           SGD需要更长的时间收敛,因此设置较大的UnFreeze_Epoch
    #                           Adam可以使用相对较小的UnFreeze_Epoch
    #   Unfreeze_batch_size     模型在解冻后的batch_size
    #------------------------------------------------------------------#
    UnFreeze_Epoch      = 300 # 300
    Unfreeze_batch_size = 8 # 8
    #------------------------------------------------------------------#
    #   Freeze_Train    是否进行冻结训练
    #                   默认先冻结主干训练后解冻训练。
    #------------------------------------------------------------------#
    Freeze_Train        = True

    #------------------------------------------------------------------#
    #   其它训练参数:学习率、优化器、学习率下降有关
    #------------------------------------------------------------------#
    #------------------------------------------------------------------#
    #   Init_lr         模型的最大学习率
    #                   当使用Adam优化器时建议设置  Init_lr=1e-3
    #                   当使用SGD优化器时建议设置   Init_lr=1e-2
    #   Min_lr          模型的最小学习率,默认为最大学习率的0.01
    #------------------------------------------------------------------#
    Init_lr             = 1e-2  #0.01
    Min_lr              = Init_lr * 0.01
    #------------------------------------------------------------------#
    #   optimizer_type  使用到的优化器种类,可选的有adam、sgd
    #                   当使用Adam优化器时建议设置  Init_lr=1e-3
    #                   当使用SGD优化器时建议设置   Init_lr=1e-2
    #   momentum        优化器内部使用到的momentum参数
    #   weight_decay    权值衰减,可防止过拟合
    #                   adam会导致weight_decay错误,使用adam时建议设置为0。
    #------------------------------------------------------------------#
    optimizer_type      = "sgd"
    momentum            = 0.937
    weight_decay        = 5e-4  #5*0.0001=0.0005
    #------------------------------------------------------------------#
    #   lr_decay_type   使用到的学习率下降方式,可选的有'step'、'cos'
    #------------------------------------------------------------------#
    lr_decay_type       = 'cos'
    #------------------------------------------------------------------#
    #   save_period     多少个epoch保存一次权值
    #------------------------------------------------------------------#
    save_period         = 10
    #------------------------------------------------------------------#
    #   save_dir        权值与日志文件保存的文件夹
    #------------------------------------------------------------------#
    save_dir            = 'logs'
    #------------------------------------------------------------------#
    #   eval_flag       是否在训练时进行评估,评估对象为验证集
    #                   安装pycocotools库后,评估体验更佳。
    #   eval_period     代表多少个epoch评估一次,不建议频繁的评估
    #                   评估需要消耗较多的时间,频繁评估会导致训练非常慢
    #   此处获得的mAP会与get_map.py获得的会有所不同,原因有二:
    #   (一)此处获得的mAP为验证集的mAP。
    #   (二)此处设置评估参数较为保守,目的是加快评估速度。
    #------------------------------------------------------------------#
    eval_flag           = True
    eval_period         = 10
    #------------------------------------------------------------------#
    #   num_workers     用于设置是否使用多线程读取数据,1代表关闭多线程
    #                   开启后会加快数据读取速度,但是会占用更多内存
    #                   keras里开启多线程有些时候速度反而慢了许多
    #                   在IO为瓶颈的时候再开启多线程,即GPU运算速度远大于读取图片的速度。
    #------------------------------------------------------------------#
    num_workers         = 1

    #------------------------------------------------------#
    #   train_annotation_path   训练图片路径和标签
    #   val_annotation_path     验证图片路径和标签
    #------------------------------------------------------#
    train_annotation_path   = '../2007_train.txt'
    val_annotation_path     = '../2007_val.txt'

    #------------------------------------------------------#
    #   设置用到的显卡
    #------------------------------------------------------#
    os.environ["CUDA_VISIBLE_DEVICES"]  = ','.join(str(x) for x in train_gpu)
    ngpus_per_node                      = len(train_gpu)
    print('Number of devices: {}'.format(ngpus_per_node))

    #----------------------------------------------------#
    #   获取classes和anchor
    #----------------------------------------------------#
    class_names, num_classes = get_classes(classes_path)
    #train.py class_names= ['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat',
    # 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',
    # 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
    # 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
    # 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich',
    # 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'sofa', 'pottedplant', 'bed', 'diningtable',
    # 'toilet', 'tvmonitor', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink',
    # 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
    #train.py num_classes= 80
    anchors, num_anchors     = get_anchors(anchors_path)
    #train.py anchors = [[10.  13.] [16.  30.] [33. 23.] [30.  61.] [62. 45.] [59. 119.] [116. 90.] [156. 198.] [373. 326.]]
    #train.py num_anchors = 9

    #重置Keras生成的所有状态
    K.clear_session()
    #------------------------------------------------------#
    #   创建yolo模型
    #   model_body为[P5, P4, P3]张量
    #------------------------------------------------------#
    model_body  = get_yolo_model((None, None, 3), anchors_mask, num_classes)
    if model_path != '':
        #------------------------------------------------------#
        #   载入预训练权重
        #------------------------------------------------------#
        print('Load weights {}.'.format(model_path))
        model_body.load_weights(model_path, by_name=True, skip_mismatch=True)
    print('ngpus_per_node= {}.'.format(ngpus_per_node))
    if ngpus_per_node > 1:
        model = multi_gpu_model(model_body, gpus=ngpus_per_node)
        model = get_train_model(model, input_shape, num_classes, anchors, anchors_mask)
    else:
        model = get_train_model(model_body, input_shape, num_classes, anchors, anchors_mask)

    #---------------------------#
    #   读取数据集对应的txt
    #---------------------------#
    with open(train_annotation_path, encoding='utf-8') as f:
        train_lines = f.readlines()
    with open(val_annotation_path, encoding='utf-8') as f:
        val_lines   = f.readlines()
    num_train   = len(train_lines)
    num_val     = len(val_lines)
    print('num_train= {}.num_val= {}.'.format(num_train,num_val))

    """
    show_config(
        classes_path = classes_path, anchors_path = anchors_path, anchors_mask = anchors_mask, model_path = model_path, input_shape = input_shape, \
        Init_Epoch = Init_Epoch, Freeze_Epoch = Freeze_Epoch, UnFreeze_Epoch = UnFreeze_Epoch, Freeze_batch_size = Freeze_batch_size, Unfreeze_batch_size = Unfreeze_batch_size, Freeze_Train = Freeze_Train, \
        Init_lr = Init_lr, Min_lr = Min_lr, optimizer_type = optimizer_type, momentum = momentum, lr_decay_type = lr_decay_type, \
        save_period = save_period, save_dir = save_dir, num_workers = num_workers, num_train = num_train, num_val = num_val
    )
    """
    #---------------------------------------------------------#
    #   总训练世代指的是遍历全部数据的总次数
    #   总训练步长指的是梯度下降的总次数
    #   每个训练世代包含若干训练步长,每个训练步长进行一次梯度下降。
    #   此处仅建议最低训练世代,上不封顶,计算时只考虑了解冻部分
    #----------------------------------------------------------#
    wanted_step = 5e4 if optimizer_type == "sgd" else 1.5e4
    total_step  = num_train // Unfreeze_batch_size * UnFreeze_Epoch
    if total_step <= wanted_step:
        if num_train // Unfreeze_batch_size == 0:
            raise ValueError('数据集过小,无法进行训练,请扩充数据集。')
        wanted_epoch = wanted_step // (num_train // Unfreeze_batch_size) + 1
        print("\n\033[1;33;44m[Warning] 使用%s优化器时,建议将训练总步长设置到%d以上。\033[0m"%(optimizer_type, wanted_step))
        print("\033[1;33;44m[Warning] 本次运行的总训练数据量为%d,Unfreeze_batch_size为%d,共训练%d个Epoch,计算出总训练步长为%d。\033[0m"%(num_train, Unfreeze_batch_size, UnFreeze_Epoch, total_step))
        print("\033[1;33;44m[Warning] 由于总训练步长为%d,小于建议总步长%d,建议设置总世代为%d。\033[0m"%(total_step, wanted_step, wanted_epoch))
    print('model_body.layers= {}.'.format(model_body.layers))
    for layer in model_body.layers:
        if isinstance(layer, DepthwiseConv2D):
            #print('layer.1= {}.'.format(layer))
            layer.add_loss(l2(weight_decay)(layer.depthwise_kernel))
        elif isinstance(layer, Conv2D) or isinstance(layer, Dense):
            #print('layer.2= {}.'.format(layer))
            layer.add_loss(l2(weight_decay)(layer.kernel))
    #------------------------------------------------------#
    #   主干特征提取网络特征通用,冻结训练可以加快训练速度
    #   也可以在训练初期防止权值被破坏。
    #   Init_Epoch为起始世代
    #   Freeze_Epoch为冻结训练的世代
    #   UnFreeze_Epoch总训练世代
    #   提示OOM或者显存不足请调小Batch_size
    #------------------------------------------------------#
    if Freeze_Train:
        freeze_layers = 184
        for i in range(freeze_layers): model_body.layers[i].trainable = False
        print('Freeze the first {} layers of total {} layers.'.format(freeze_layers, len(model_body.layers)))

    #-------------------------------------------------------------------#
    #   如果不冻结训练的话,直接设置batch_size为Unfreeze_batch_size
    #-------------------------------------------------------------------#
    batch_size  = Freeze_batch_size if Freeze_Train else Unfreeze_batch_size
    start_epoch = Init_Epoch
    end_epoch   = Freeze_Epoch if Freeze_Train else UnFreeze_Epoch

    #-------------------------------------------------------------------#
    #   判断当前batch_size,自适应调整学习率
    #-------------------------------------------------------------------#
    nbs             = 64
    lr_limit_max    = 1e-3 if optimizer_type == 'adam' else 5e-2
    lr_limit_min    = 3e-4 if optimizer_type == 'adam' else 5e-4
    Init_lr_fit     = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
    Min_lr_fit      = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)

    optimizer = {
        'adam'  : Adam(lr = Init_lr_fit, beta_1 = momentum),
        'sgd'   : SGD(lr = Init_lr_fit, momentum = momentum, nesterov=True)
    }[optimizer_type]
    print('model.type {}.'.format(type(model)))
    #lambda y_true, y_pred: y_pred 这纯粹是一种python语法,可以看成一个函数 输入(y_true, y_pred) 返回y_pred
    #loss={'yolo_loss':lambda y_true,y_pred: y_pred} 表示loss函数引用的是yolo_loss这个层
    model.compile(optimizer=optimizer, loss={'yolo_loss': lambda y_true, y_pred: y_pred})
    #源代码中参数顺序y_true, y_pred,参考源码keras.engine.training.py第829行
    #output_loss = weighted_loss(y_true, y_pred,sample_weight, mask)
    #import tensorflow.compat.v1 as tf
    #tf.keras.losses
    #参考tensorflow.keras.losses.py第309行def call(self, y_true, y_pred)中调用次序
    #---------------------------------------#
    #   获得学习率下降的公式
    #---------------------------------------#
    lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)

    epoch_step          = num_train // batch_size
    epoch_step_val      = num_val // batch_size

    if epoch_step == 0 or epoch_step_val == 0:
        raise ValueError('数据集过小,无法进行训练,请扩充数据集。')

    train_dataloader    = YoloDatasets(train_lines, input_shape, anchors, batch_size, num_classes, anchors_mask, train = True)
    val_dataloader      = YoloDatasets(val_lines, input_shape, anchors, batch_size, num_classes, anchors_mask, train = False)

    #-------------------------------------------------------------------------------#
    #   训练参数的设置
    #   logging         用于设置tensorboard的保存地址
    #   checkpoint      用于设置权值保存的细节,period用于修改多少epoch保存一次
    #   lr_scheduler    用于设置学习率下降的方式
    #   early_stopping  用于设定早停,val_loss多次不下降自动结束训练,表示模型基本收敛
    #-------------------------------------------------------------------------------#
    time_str        = datetime.datetime.strftime(datetime.datetime.now(),'%Y_%m_%d_%H_%M_%S')
    log_dir         = os.path.join(save_dir, "loss_" + str(time_str))
    logging         = TensorBoard(log_dir)
    loss_history    = LossHistory(log_dir)
    if ngpus_per_node > 1:
        checkpoint      = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = save_period)
        checkpoint_last = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "last_epoch_weights.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = 1)
        checkpoint_best = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "best_epoch_weights.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = True, period = 1)
    else:
        checkpoint      = ModelCheckpoint(os.path.join(save_dir, "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = save_period)
        checkpoint_last = ModelCheckpoint(os.path.join(save_dir, "last_epoch_weights.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = 1)
        checkpoint_best = ModelCheckpoint(os.path.join(save_dir, "best_epoch_weights.h5"),
                                monitor = 'val_loss', save_weights_only = True, save_best_only = True, period = 1)
    early_stopping  = EarlyStopping(monitor='val_loss', min_delta = 0, patience = 10, verbose = 1)
    lr_scheduler    = LearningRateScheduler(lr_scheduler_func, verbose = 1)
    eval_callback   = EvalCallback(model_body, input_shape, anchors, anchors_mask, class_names, num_classes, val_lines, log_dir, \
                                    eval_flag=eval_flag, period=eval_period)
    test_callback = TestCallback()
    callbacks       = [logging, loss_history, checkpoint, checkpoint_last, checkpoint_best, lr_scheduler, eval_callback, test_callback]
    print('train1: start_epoch=',start_epoch,',end_epoch=',end_epoch)
    #model.inputs= [<tf.Tensor 'input_1:0' shape=(?, ?, ?, 3) dtype=float32>,
    # <tf.Tensor 'input_2:0' shape=(?, 13, 13, 3, 85) dtype=float32>,
    # <tf.Tensor 'input_3:0' shape=(?, 26, 26, 3, 85) dtype=float32>,
    # <tf.Tensor 'input_4:0' shape=(?, 52, 52, 3, 85) dtype=float32>]
    # 参考yolo.py get_train_model() 144行将 model.input与y_true连接
    if start_epoch < end_epoch:
        print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
        model.fit_generator(
            generator           = train_dataloader,
            steps_per_epoch     = epoch_step,
            validation_data     = val_dataloader,
            validation_steps    = epoch_step_val,
            epochs              = end_epoch,
            initial_epoch       = start_epoch,
            use_multiprocessing = True if num_workers > 1 else False,
            workers             = num_workers,
            callbacks           = callbacks
        )
    #---------------------------------------#
    #   如果模型有冻结学习部分
    #   则解冻,并设置参数
    #---------------------------------------#
    if Freeze_Train:
        batch_size  = Unfreeze_batch_size
        start_epoch = Freeze_Epoch if start_epoch < Freeze_Epoch else start_epoch
        end_epoch   = UnFreeze_Epoch
        print('train2: start_epoch=', start_epoch, ',end_epoch=', end_epoch)
        #-------------------------------------------------------------------#
        #   判断当前batch_size,自适应调整学习率
        #-------------------------------------------------------------------#
        nbs             = 64
        lr_limit_max    = 1e-3 if optimizer_type == 'adam' else 5e-2
        lr_limit_min    = 3e-4 if optimizer_type == 'adam' else 5e-4
        Init_lr_fit     = min(max(batch_size / nbs * Init_lr, lr_limit_min), lr_limit_max)
        Min_lr_fit      = min(max(batch_size / nbs * Min_lr, lr_limit_min * 1e-2), lr_limit_max * 1e-2)
        #---------------------------------------#
        #   获得学习率下降的公式
        #---------------------------------------#
        lr_scheduler_func = get_lr_scheduler(lr_decay_type, Init_lr_fit, Min_lr_fit, UnFreeze_Epoch)
        lr_scheduler    = LearningRateScheduler(lr_scheduler_func, verbose = 1)
        callbacks       = [logging, loss_history, checkpoint, checkpoint_last, checkpoint_best, lr_scheduler, eval_callback]

        for i in range(len(model_body.layers)):
            model_body.layers[i].trainable = True
        model.compile(optimizer = optimizer, loss={'yolo_loss': lambda y_true, y_pred: y_pred})

        epoch_step      = num_train // batch_size
        epoch_step_val  = num_val // batch_size

        if epoch_step == 0 or epoch_step_val == 0:
            raise ValueError("数据集过小,无法继续进行训练,请扩充数据集。")

        train_dataloader.batch_size    = Unfreeze_batch_size
        val_dataloader.batch_size      = Unfreeze_batch_size

        print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
        model.fit_generator(
            generator           = train_dataloader,
            steps_per_epoch     = epoch_step,
            validation_data     = val_dataloader,
            validation_steps    = epoch_step_val,
            epochs              = end_epoch,
            initial_epoch       = start_epoch,
            use_multiprocessing = True if num_workers > 1 else False,
            workers             = num_workers,
            verbose             = 1,
            callbacks           = callbacks
        )

代码解释部分

train.py第207行
os.environ[“CUDA_VISIBLE_DEVICES”] = ‘,’.join(str(x) for x in train_gpu)

根据变量train_gpu设置GPU环境变量,生成以逗号分隔字符串。

train.py第214行
class_names, num_classes = get_classes(classes_path)

从配置文件中读取物体分类和分类数量。

train.py第224行
anchors, num_anchors = get_anchors(anchors_path)

从配置文件中读取先验框列表和数量

train.py第234行
model_body = get_yolo_model((None, None, 3), anchors_mask, num_classes)

装载Yolo预测模型。

train.py第240行
model_body.load_weights(model_path, by_name=True, skip_mismatch=True)

预测模型装载权重。

train.py第242行

if ngpus_per_node > 1:
    model = multi_gpu_model(model_body, gpus=ngpus_per_node)
    model = get_train_model(model, input_shape, num_classes, anchors, anchors_mask)
else:
    model = get_train_model(model_body, input_shape, num_classes, anchors, anchors_mask)

如果存在多块GPU情况,需要对网络模型进行封装,使得训练样本分为多份,避免样本重复训练。

train.py第250行

with open(train_annotation_path, encoding='utf-8') as f:
    train_lines = f.readlines()
with open(val_annotation_path, encoding='utf-8') as f:
    val_lines   = f.readlines()
num_train   = len(train_lines)
num_val     = len(val_lines)

样本预处理后生成文本文件,此处将加载文本文件。

train.py第273行
wanted_step = 5e4 if optimizer_type == “sgd” else 1.5e4
total_step = num_train // Unfreeze_batch_size * UnFreeze_Epoch

每个Epoch分为多个step进行训练,每个step使用梯度下降法调整权重和偏置,此处计算所需step和总step数量。

train.py第283行

for layer in model_body.layers:
    if isinstance(layer, DepthwiseConv2D):
        #print('layer.1= {}.'.format(layer))
        layer.add_loss(l2(weight_decay)(layer.depthwise_kernel))
    elif isinstance(layer, Conv2D) or isinstance(layer, Dense):
        #print('layer.2= {}.'.format(layer))
        layer.add_loss(l2(weight_decay)(layer.kernel))

每个网络层添加L2正则化损失。

train.py第308行
batch_size = Freeze_batch_size if Freeze_Train else Unfreeze_batch_size
start_epoch = Init_Epoch
end_epoch = Freeze_Epoch if Freeze_Train else UnFreeze_Epoch

根据是否为冻结训练模式,设置起始时代和终止时代。

train.py第326行
model.compile(optimizer=optimizer, loss={‘yolo_loss’: lambda y_true, y_pred: y_pred})

编译模型,定义损失函数,损失函数为Lamda匿名函数,接收参数列表为y_true和y_pred,返回y_pred。其中参数列表顺序y_true,y_pred不可颠倒,参考框架源码tensorflow/python/keras/losses.py第309行代码。
在这里插入图片描述
train.py第343行
train_dataloader = YoloDatasets(train_lines, input_shape, anchors, batch_size, num_classes, anchors_mask, train = True)
val_dataloader = YoloDatasets(val_lines, input_shape, anchors, batch_size, num_classes, anchors_mask, train = False)

根据样本文件,生成样本Sequence,并生成样本标签张量y_true。

train.py第376行

if ngpus_per_node > 1:
    checkpoint      = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = save_period)
    checkpoint_last = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "last_epoch_weights.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = 1)
    checkpoint_best = ParallelModelCheckpoint(model_body, os.path.join(save_dir, "best_epoch_weights.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = True, period = 1)
else:
    checkpoint      = ModelCheckpoint(os.path.join(save_dir, "ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = save_period)
    checkpoint_last = ModelCheckpoint(os.path.join(save_dir, "last_epoch_weights.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = False, period = 1)
    checkpoint_best = ModelCheckpoint(os.path.join(save_dir, "best_epoch_weights.h5"),
                            monitor = 'val_loss', save_weights_only = True, save_best_only = True, period = 1)
early_stopping  = EarlyStopping(monitor='val_loss', min_delta = 0, patience = 10, verbose = 1)
lr_scheduler    = LearningRateScheduler(lr_scheduler_func, verbose = 1)
eval_callback   = EvalCallback(model_body, input_shape, anchors, anchors_mask, class_names, num_classes, val_lines, log_dir, \
                                eval_flag=eval_flag, period=eval_period)
test_callback = TestCallback()
callbacks       = [logging, loss_history, checkpoint, checkpoint_last, checkpoint_best, lr_scheduler, eval_callback, test_callback]
print('train1: start_epoch=',start_epoch,',end_epoch=',end_epoch)
#model.inputs= [<tf.Tensor 'input_1:0' shape=(?, ?, ?, 3) dtype=float32>,
# <tf.Tensor 'input_2:0' shape=(?, 13, 13, 3, 85) dtype=float32>,
# <tf.Tensor 'input_3:0' shape=(?, 26, 26, 3, 85) dtype=float32>,
# <tf.Tensor 'input_4:0' shape=(?, 52, 52, 3, 85) dtype=float32>]


定义训练时,每个epoch完成的回调方法,保存最后一次和最好一次模型权重和偏置。

train.py第385行

model.fit_generator(
    generator           = train_dataloader,
    steps_per_epoch     = epoch_step,
    validation_data     = val_dataloader,
    validation_steps    = epoch_step_val,
    epochs              = end_epoch,
    initial_epoch       = start_epoch,
    use_multiprocessing = True if num_workers > 1 else False,
    workers             = num_workers,
    callbacks           = callbacks
)

训练模型,使得输入数据与标签数据y_true充分拟合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值