#! /usr/bin/env python import argparse import os import numpy as np import json from voc import parse_voc_annotation from yolo import create_yolov3_model, dummy_loss from generator import BatchGenerator from utils.utils import normalize, evaluate, makedirs from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau from tensorflow.keras.optimizers import Adam from callbacks import CustomModelCheckpoint, CustomTensorBoard from utils.multi_gpu_model import multi_gpu_model import tensorflow as tf from tensorflow import keras from tensorflow.keras.models import load_model def create_training_instances( train_annot_folder, train_image_folder, train_cache, valid_annot_folder, valid_image_folder, valid_cache, labels, ): # parse annotations of the training set train_ints, train_labels = parse_voc_annotation(train_annot_folder, train_image_folder, train_cache, labels) # parse annotations of the validation set, if any, otherwise split the training set if os.path.exists(valid_annot_folder): valid_ints, valid_labels = parse_voc_annotation(valid_annot_folder, valid_image_folder, valid_cache, labels) else: print("valid_annot_folder not exists. Spliting the trainining set.") train_valid_split = int(0.8*len(train_ints)) np.random.seed(0) np.random.shuffle(train_ints) np.random.seed() valid_ints = train_ints[train_valid_split:] train_ints = train_ints[:train_valid_split] # compare the seen labels with the given labels in config.json if len(labels) > 0: overlap_labels = set(labels).intersection(set(train_labels.keys())) print('Seen labels: \t' + str(train_labels) + '\n') print('Given labels: \t' + str(labels)) # return None, None, None if some given label is not in the dataset if len(overlap_labels) < len(labels): print('Some labels have no annotations! Please revise the list of labels in the config.json.') return None, None, None else: print('No labels are provided. Train on all seen labels.') print(train_labels) labels = train_labels.keys() max_box_per_image = max([len(inst['object']) for inst in (train_ints + valid_ints)]) return train_ints, valid_ints, sorted(labels), max_box_per_image def create_callbacks(saved_weights_name, tensorboard_logs, model_to_save): makedirs(tensorboard_logs) early_stop = EarlyStopping( monitor = 'loss', min_delta = 0.01, patience = 25, mode = 'min', verbose = 1 ) checkpoint = CustomModelCheckpoint( model_to_save = model_to_save, filepath = saved_weights_name,# + '{epoch:02d}.h5', monitor = 'loss', verbose = 1, save_best_only = True, mode = 'min', save_freq = 1 ) reduce_on_plateau = ReduceLROnPlateau( monitor = 'loss', factor = 0.5, patience = 15, verbose = 1, mode = 'min', min_delta = 0.01, cooldown = 0, min_lr = 0 ) tensorboard = CustomTensorBoard( log_dir = tensorboard_logs, write_graph = True, write_images = True, ) return [early_stop, checkpoint, reduce_on_plateau, tensorboard] def create_model( nb_class, anchors, max_box_per_image, max_grid, batch_size, warmup_batches, ignore_thresh, multi_gpu, saved_weights_name, lr, grid_scales, obj_scale, noobj_scale, xywh_scale, class_scale, backend ): if multi_gpu > 1: with tf.device('/cpu:0'): template_model, infer_model = create_yolov3_model( nb_class = nb_class, anchors = anchors, max_box_per_image = max_box_per_image, max_grid = max_grid, batch_size = batch_size//multi_gpu, warmup_batches = warmup_batches, ignore_thresh = ignore_thresh, grid_scales = grid_scales, obj_scale = obj_scale, noobj_scale = noobj_scale, xywh_scale = xywh_scale, class_scale = class_scale ) else: template_model, infer_model = create_yolov3_model( nb_class = nb_class, anchors = anchors, max_box_per_image = max_box_per_image, max_grid = max_grid, batch_size = batch_size, warmup_batches = warmup_batches, ignore_thresh = ignore_thresh, grid_scales = grid_scales, obj_scale = obj_scale, noobj_scale = noobj_scale, xywh_scale = xywh_scale, class_scale = class_scale ) # load the pretrained weight if exists, otherwise load the backend weight only if os.path.exists(saved_weights_name): print("\nLoading pretrained weights.\n") template_model.load_weights(saved_weights_name) else: template_model.load_weights(backend, by_name=True) if multi_gpu > 1: train_model = multi_gpu_model(template_model, gpus=multi_gpu) else: train_model = template_model optimizer = Adam(lr=lr, clipnorm=0.001) train_model.compile(loss=dummy_loss, optimizer=optimizer) return train_model, infer_model def _main_(args): config_path = args.conf with open(config_path) as config_buffer: config = json.loads(config_buffer.read()) ############################### # Parse the annotations ############################### train_ints, valid_ints, labels, max_box_per_image = create_training_instances( config['train']['train_annot_folder'], config['train']['train_image_folder'], config['train']['cache_name'], config['valid']['valid_annot_folder'], config['valid']['valid_image_folder'], config['valid']['cache_name'], config['model']['labels'] ) print('\nTraining on: \t' + str(labels) + '\n') ############################### # Create the generators ############################### train_generator = BatchGenerator( instances = train_ints, anchors = config['model']['anchors'], labels = labels, downsample = 32, # ratio between network input's size and network output's size, 32 for YOLOv3 max_box_per_image = max_box_per_image, batch_size = config['train']['batch_size'], min_net_size = config['model']['min_input_size'], max_net_size = config['model']['max_input_size'], shuffle = True, jitter = 0.3, norm = normalize ) valid_generator = BatchGenerator( instances = valid_ints, anchors = config['model']['anchors'], labels = labels, downsample = 32, # ratio between network input's size and network output's size, 32 for YOLOv3 max_box_per_image = max_box_per_image, batch_size = config['train']['batch_size'], min_net_size = config['model']['min_input_size'], max_net_size = config['model']['max_input_size'], shuffle = True, jitter = 0.0, norm = normalize ) ############################### # Create the model ############################### if os.path.exists(config['train']['saved_weights_name']): config['train']['warmup_epochs'] = 0 warmup_batches = config['train']['warmup_epochs'] * (config['train']['train_times']*len(train_generator)) os.environ['CUDA_VISIBLE_DEVICES'] = config['train']['gpus'] multi_gpu = len(config['train']['gpus'].split(',')) print('multi_gpu:' + str(multi_gpu)) train_model, infer_model = create_model( nb_class = len(labels), anchors = config['model']['anchors'], max_box_per_image = max_box_per_image, max_grid = [config['model']['max_input_size'], config['model']['max_input_size']], batch_size = config['train']['batch_size'], warmup_batches = warmup_batches, ignore_thresh = config['train']['ignore_thresh'], multi_gpu = multi_gpu, saved_weights_name = config['train']['saved_weights_name'], lr = config['train']['learning_rate'], grid_scales = config['train']['grid_scales'], obj_scale = config['train']['obj_scale'], noobj_scale = config['train']['noobj_scale'], xywh_scale = config['train']['xywh_scale'], class_scale = config['train']['class_scale'], backend = config['model']['backend'] ) ############################### # Kick off the training ############################### callbacks = create_callbacks(config['train']['saved_weights_name'], config['train']['tensorboard_dir'], infer_model) train_model.fit( generator = train_generator, steps_per_epoch = len(train_generator) * config['train']['train_times'], epochs = config['train']['nb_epochs'] + config['train']['warmup_epochs'], verbose = 2 if config['train']['debug'] else 1, callbacks = callbacks, workers = 4, max_queue_size = 8 ) # make a GPU version of infer_model for evaluation if multi_gpu > 1: infer_model = load_model(config['train']['saved_weights_name']) ############################### # Run the evaluation ############################### # compute mAP for all the classes average_precisions = evaluate(infer_model, valid_generator) # print the score total_instances = [] precisions = [] for label, (average_precision, num_annotations) in average_precisions.items(): print('{:.0f} instances of class'.format(num_annotations), labels[label], 'with average precision: {:.4f}'.format(average_precision)) total_instances.append(num_annotations) precisions.append(average_precision) if sum(total_instances) == 0: print('No test instances found.') return print('mAP using the weighted average of precisions among classes: {:.4f}'.format(sum([a * b for a, b in zip(total_instances, precisions)]) / sum(total_instances))) print('mAP: {:.4f}'.format(sum(precisions) / sum(x > 0 for x in total_instances))) if __name__ == '__main__': argparser = argparse.ArgumentParser(description='train and evaluate YOLO_v3 model on any dataset') argparser.add_argument('-c', '--conf', help='path to configuration file') args = argparser.parse_args() _main_(args)