From a85e6d0fc0036fe72b1a378ce35a5e7b70f29703 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 14:53:32 -0500 Subject: [PATCH 01/68] add parser arg for hyp yaml file --- train.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index 1e2d55a3c84..0cf3d14f8cc 100644 --- a/train.py +++ b/train.py @@ -43,7 +43,9 @@ 'translate': 0.0, # image translation (+/- fraction) 'scale': 0.5, # image scale (+/- gain) 'shear': 0.0} # image shear (+/- deg) -print(hyp) + +# Don't need to be printing every time +#print(hyp) # Overwrite hyp with hyp*.txt (optional) f = glob.glob('hyp*.txt') @@ -382,10 +384,12 @@ def train(hyp): parser.add_argument('--adam', action='store_true', help='use adam optimizer') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') + parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file') opt = parser.parse_args() opt.weights = last if opt.resume else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file + opt.hyp = check_file(opt.hyp) #check file print(opt) opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 sizes (train, test) device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size) From d9f446cd81c88b2f62fdc0092156adb97d6dd8ac Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 15:06:13 -0500 Subject: [PATCH 02/68] add save yaml of opt and hyp to tensorboard log_dir in train() --- train.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index 0cf3d14f8cc..533fcbc9cad 100644 --- a/train.py +++ b/train.py @@ -48,7 +48,6 @@ #print(hyp) # Overwrite hyp with hyp*.txt (optional) -f = glob.glob('hyp*.txt') if f: print('Using %s' % f[0]) for k, v in zip(hyp.keys(), np.loadtxt(f[0])): @@ -64,6 +63,9 @@ def train(hyp): batch_size = opt.batch_size # 64 weights = opt.weights # initial training weights + #write all results to the tb log_dir, so all data from one run is together + log_dir = tb_writer.log_dir + # Configure init_seeds(1) with open(opt.data) as f: @@ -192,6 +194,13 @@ def train(hyp): model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights model.names = data_dict['names'] + #save hyperparamter and training options in run folder + with open(os.path.join(log_dir, 'hyp.yaml', 'w')) as f: + yaml.dump(hyp, f) + + with open(os.path.join(log_dir, 'opt.yaml', 'w')) as f: + yaml.dump(opt, f) + # Class frequency labels = np.concatenate(dataset.labels, 0) c = torch.tensor(labels[:, 0]) # classes From 4418809cf5b561783a8e6680a79049b3df6eebc7 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 15:09:51 -0500 Subject: [PATCH 03/68] change weights dir (wdir) to be unique to each run, under log_dir --- train.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/train.py b/train.py index 533fcbc9cad..4fa900571b0 100644 --- a/train.py +++ b/train.py @@ -18,11 +18,6 @@ print('Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex') mixed_precision = False # not installed -wdir = 'weights' + os.sep # weights dir -os.makedirs(wdir, exist_ok=True) -last = wdir + 'last.pt' -best = wdir + 'best.pt' -results_file = 'results.txt' # Hyperparameters hyp = {'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) @@ -59,13 +54,21 @@ def train(hyp): + #write all results to the tb log_dir, so all data from one run is together + log_dir = tb_writer.log_dir + + #weights dir unique to each experiment + wdir = os.path.join(log_dir, 'weights') + os.sep # weights dir + + os.makedirs(wdir, exist_ok=True) + last = wdir + 'last.pt' + best = wdir + 'best.pt' + results_file = 'results.txt' + epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 weights = opt.weights # initial training weights - #write all results to the tb log_dir, so all data from one run is together - log_dir = tb_writer.log_dir - # Configure init_seeds(1) with open(opt.data) as f: From 490f1e7b9c46f1e3fd04fe52cd3025eab0844788 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 15:13:03 -0500 Subject: [PATCH 04/68] add save_dir arg to plot_lr_scheduler, default to current dir. Uncomment plot_lr_scheduler in train() and pass log_dir as save location --- train.py | 2 +- utils/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index 4fa900571b0..df5e1edb803 100644 --- a/train.py +++ b/train.py @@ -148,7 +148,7 @@ def train(hyp): scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) scheduler.last_epoch = start_epoch - 1 # do not move # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 - # plot_lr_scheduler(optimizer, scheduler, epochs) + plot_lr_scheduler(optimizer, scheduler, epochs, save_dir = log_dir) # Initialize distributed training if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): diff --git a/utils/utils.py b/utils/utils.py index 95d1198fde4..8ac73e3cc6c 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -1005,7 +1005,7 @@ def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max return mosaic -def plot_lr_scheduler(optimizer, scheduler, epochs=300): +def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir='./'): # Plot LR simulating training for full epochs optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals y = [] From 25e51bcec723eb0ff094824a0f89ac726a5ee701 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 15:50:27 -0500 Subject: [PATCH 05/68] add util function to get most recent last.pt file added logic in train.py __main__ to handle resuming from a run --- train.py | 13 ++++++++++--- utils/utils.py | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index df5e1edb803..25cf3d46929 100644 --- a/train.py +++ b/train.py @@ -198,10 +198,10 @@ def train(hyp): model.names = data_dict['names'] #save hyperparamter and training options in run folder - with open(os.path.join(log_dir, 'hyp.yaml', 'w')) as f: + with open(os.path.join(log_dir, 'hyp.yaml'), 'w') as f: yaml.dump(hyp, f) - with open(os.path.join(log_dir, 'opt.yaml', 'w')) as f: + with open(os.path.join(log_dir, 'opt.yaml'), 'w') as f: yaml.dump(opt, f) # Class frequency @@ -294,7 +294,7 @@ def train(hyp): # Plot if ni < 3: - f = 'train_batch%g.jpg' % i # filename + f = os.path.join(log_dir, 'train_batch%g.jpg' % i) # filename res = plot_images(images=imgs, targets=targets, paths=paths, fname=f) if tb_writer: tb_writer.add_image(f, res, dataformats='HWC', global_step=epoch) @@ -385,6 +385,7 @@ def train(hyp): parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--resume', action='store_true', help='resume training from last.pt') + parser.add_argument('--resume_from_run', type=str, default='', 'resume training from last.pt in this dir') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') @@ -398,6 +399,12 @@ def train(hyp): parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file') opt = parser.parse_args() + + if opt.resume and not opt.resume_from_run: + last = get_latest_run() + print(f'WARNING: No run provided to resume from. Resuming from most recent run found at {last}') + else: + last = opt.resume_from_run opt.weights = last if opt.resume else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file diff --git a/utils/utils.py b/utils/utils.py index 8ac73e3cc6c..56fb66bcad4 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -36,6 +36,12 @@ def init_seeds(seed=0): np.random.seed(seed) torch_utils.init_seeds(seed=seed) +def get_latest_run(search_dir = './runs/'): + # get path to most recent 'last.pt' in run dirs + # assumes most recently saved 'last.pt' is the desired weights to --resume from + last_list = glob.glob('runs/*/last.pt') + latest = max(last_list, key = os.path.getctime) + return latest def check_git_status(): # Suggest 'git pull' if repo is out of date From a448c3bcd7b70a53058dc53646efbb36b284d4f5 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 16:30:12 -0500 Subject: [PATCH 06/68] add logic for resuming and getting hyp for resume run --- train.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/train.py b/train.py index 25cf3d46929..dd0f029a3fa 100644 --- a/train.py +++ b/train.py @@ -63,7 +63,7 @@ def train(hyp): os.makedirs(wdir, exist_ok=True) last = wdir + 'last.pt' best = wdir + 'best.pt' - results_file = 'results.txt' + results_file = wdir + 'results.txt' epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 @@ -360,7 +360,7 @@ def train(hyp): if len(n): n = '_' + n if not n.isnumeric() else n fresults, flast, fbest = 'results%s.txt' % n, wdir + 'last%s.pt' % n, wdir + 'best%s.pt' % n - for f1, f2 in zip([wdir + 'last.pt', wdir + 'best.pt', 'results.txt'], [flast, fbest, fresults]): + for f1, f2 in zip([wdir + 'last.pt', wdir + 'best.pt', wdir + 'results.txt'], [flast, fbest, fresults]): if os.path.exists(f1): os.rename(f1, f2) # rename ispt = f2.endswith('.pt') # is *.pt @@ -382,10 +382,10 @@ def train(hyp): parser.add_argument('--batch-size', type=int, default=16) parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='*.cfg path') parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path') - parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes') + parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--resume', action='store_true', help='resume training from last.pt') - parser.add_argument('--resume_from_run', type=str, default='', 'resume training from last.pt in this dir') + parser.add_argument('--resume-from-run', type=str, default='', help='resume training from last.pt in this dir') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') @@ -397,18 +397,30 @@ def train(hyp): parser.add_argument('--adam', action='store_true', help='use adam optimizer') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') - parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file') + parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file. Not needed with --resume.') opt = parser.parse_args() - if opt.resume and not opt.resume_from_run: + # logic to resume from latest run if either --resume or --resume-from-run is selected + # Note if neither --resume or --resume-from-run, last is set to empty string + if opt.resume_from_run: + opt.resume = True + last = opt.resume_from_run + elif opt.resume and not opt.resume_from_run: last = get_latest_run() print(f'WARNING: No run provided to resume from. Resuming from most recent run found at {last}') else: - last = opt.resume_from_run + last = '' + + # if resuming, check for hyp file + if last: + last_hyp = last.replace('last.pt', 'hyp.yaml') + if os.path.exists(last_hyp): + opt.hyp = last_hyp + opt.weights = last if opt.resume else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file - opt.hyp = check_file(opt.hyp) #check file + opt.hyp = check_file(opt.hyp) if opt.hyp else '' #check file print(opt) opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 sizes (train, test) device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size) From 333f678b374e7677070ce037ddbe8a655563e8f6 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 16:36:20 -0500 Subject: [PATCH 07/68] add update default hyp dict with provided yaml --- train.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/train.py b/train.py index dd0f029a3fa..cfc00594b75 100644 --- a/train.py +++ b/train.py @@ -42,17 +42,6 @@ # Don't need to be printing every time #print(hyp) -# Overwrite hyp with hyp*.txt (optional) -if f: - print('Using %s' % f[0]) - for k, v in zip(hyp.keys(), np.loadtxt(f[0])): - hyp[k] = v - -# Print focal loss if gamma > 0 -if hyp['fl_gamma']: - print('Using FocalLoss(gamma=%g)' % hyp['fl_gamma']) - - def train(hyp): #write all results to the tb log_dir, so all data from one run is together log_dir = tb_writer.log_dir @@ -410,7 +399,7 @@ def train(hyp): print(f'WARNING: No run provided to resume from. Resuming from most recent run found at {last}') else: last = '' - + # if resuming, check for hyp file if last: last_hyp = last.replace('last.pt', 'hyp.yaml') @@ -430,7 +419,16 @@ def train(hyp): # Train if not opt.evolve: tb_writer = SummaryWriter(comment=opt.name) + + #updates hyp defaults from hyp.yaml + if opt.hyp: hyp.update(opt.hyp) + + # Print focal loss if gamma > 0 + if hyp['fl_gamma']: + print('Using FocalLoss(gamma=%g)' % hyp['fl_gamma']) + print(f'Beginning training with {hyp}\n\n') print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') + train(hyp) # Evolve hyperparameters (optional) From 5f2eeba233be3a7ed0764070ab339161c1055c76 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 16 Jun 2020 17:09:39 -0500 Subject: [PATCH 08/68] remove old print statements --- train.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/train.py b/train.py index cfc00594b75..9ea357ec093 100644 --- a/train.py +++ b/train.py @@ -39,8 +39,6 @@ 'scale': 0.5, # image scale (+/- gain) 'shear': 0.0} # image shear (+/- deg) -# Don't need to be printing every time -#print(hyp) def train(hyp): #write all results to the tb log_dir, so all data from one run is together From d34291733bb67679a35a33ff97449ac35a2f2e6b Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 10:26:55 -0500 Subject: [PATCH 09/68] Fix get_latest_run() to search inside 'weights' subfolders --- utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.py b/utils/utils.py index 56fb66bcad4..4570cd0506a 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -39,7 +39,7 @@ def init_seeds(seed=0): def get_latest_run(search_dir = './runs/'): # get path to most recent 'last.pt' in run dirs # assumes most recently saved 'last.pt' is the desired weights to --resume from - last_list = glob.glob('runs/*/last.pt') + last_list = glob.glob('runs/*/weights/last.pt') latest = max(last_list, key = os.path.getctime) return latest From 3263a204ea027effce0a9b1bf7163d4bc28fc478 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 10:34:37 -0500 Subject: [PATCH 10/68] Fix get_latest_run to search 'search_dir' recursivly --- utils/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index 4570cd0506a..5332beeb6c1 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -36,10 +36,10 @@ def init_seeds(seed=0): np.random.seed(seed) torch_utils.init_seeds(seed=seed) -def get_latest_run(search_dir = './runs/'): +def get_latest_run(search_dir = './runs'): # get path to most recent 'last.pt' in run dirs # assumes most recently saved 'last.pt' is the desired weights to --resume from - last_list = glob.glob('runs/*/weights/last.pt') + last_list = glob.glob(f'{search_dir}/**/last.pt', recursive=True) latest = max(last_list, key = os.path.getctime) return latest From ade023cff2047bef6761bf2b719d2ee7dbbbbe50 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 10:59:20 -0500 Subject: [PATCH 11/68] Fix hyp file read in and dict update. Add example of hyp yaml --- new_hyp.yaml | 18 ++++++++++++++++++ train.py | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 new_hyp.yaml diff --git a/new_hyp.yaml b/new_hyp.yaml new file mode 100644 index 00000000000..46838fe8e14 --- /dev/null +++ b/new_hyp.yaml @@ -0,0 +1,18 @@ +anchor_t: 10.0 +cls: 0.58 +cls_pw: 1.0 +degrees: 20.0 +fl_gamma: 1.0 +giou: 0.15 +hsv_h: 0.014 +hsv_s: 0.68 +hsv_v: 0.36 +iou_t: 0.2 +lr0: 0.001 +momentum: 0.900 +obj: 1.0 +obj_pw: 1.0 +scale: 0.5 +shear: 0.0 +translate: 0.0 +weight_decay: 0.000625 \ No newline at end of file diff --git a/train.py b/train.py index 9ea357ec093..b34a32d2806 100644 --- a/train.py +++ b/train.py @@ -408,6 +408,7 @@ def train(hyp): opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file opt.hyp = check_file(opt.hyp) if opt.hyp else '' #check file + print(opt) opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 sizes (train, test) device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size) @@ -419,7 +420,10 @@ def train(hyp): tb_writer = SummaryWriter(comment=opt.name) #updates hyp defaults from hyp.yaml - if opt.hyp: hyp.update(opt.hyp) + if opt.hyp: + with open(opt.hyp) as f: + updated_hyp = yaml.load(f, Loader=yaml.FullLoader) + hyp.update(updated_hyp) # Print focal loss if gamma > 0 if hyp['fl_gamma']: From 3b2b330872feeff208ac79c469fb9936d9cf2723 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 15:55:45 -0500 Subject: [PATCH 12/68] Move results.txt from weights/ to log_dir --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index b34a32d2806..e4fc6254a5b 100644 --- a/train.py +++ b/train.py @@ -50,7 +50,7 @@ def train(hyp): os.makedirs(wdir, exist_ok=True) last = wdir + 'last.pt' best = wdir + 'best.pt' - results_file = wdir + 'results.txt' + results_file = log_dir + 'results.txt' epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 From 945307beba39fbe627c38eb9a9082b1a4c2c22e4 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 16:03:18 -0500 Subject: [PATCH 13/68] Add save_dir to plot_lr_scheduler and plot_labels Set save_dir = log_dir in train.py --- data/coco128.yaml | 4 ++-- train.py | 2 +- utils/utils.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/coco128.yaml b/data/coco128.yaml index 2b6184890cd..6f72f4ab9d0 100644 --- a/data/coco128.yaml +++ b/data/coco128.yaml @@ -8,8 +8,8 @@ # train and val datasets (image directory or *.txt file with image paths) -train: ../coco128/images/train2017/ -val: ../coco128/images/train2017/ +train: C:/Users/astoken/projects/yolov5/data/coco/images/train2017 +val: C:/Users/astoken/projects/yolov5/data/coco/images/train2017 # number of classes nc: 80 diff --git a/train.py b/train.py index e4fc6254a5b..500a6581889 100644 --- a/train.py +++ b/train.py @@ -196,7 +196,7 @@ def train(hyp): c = torch.tensor(labels[:, 0]) # classes # cf = torch.bincount(c.long(), minlength=nc) + 1. # model._initialize_biases(cf.to(device)) - plot_labels(labels) + plot_labels(labels, save_dir=log_dir) tb_writer.add_histogram('classes', c, 0) # Check anchors diff --git a/utils/utils.py b/utils/utils.py index 5332beeb6c1..fb8d4877d83 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -1025,7 +1025,7 @@ def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir='./'): plt.xlim(0, epochs) plt.ylim(0) plt.tight_layout() - plt.savefig('LR.png', dpi=200) + plt.savefig(os.path.join(save_dir, 'LR.png'), dpi=200) def plot_test_txt(): # from utils.utils import *; plot_test() @@ -1088,7 +1088,7 @@ def plot_study_txt(f='study.txt', x=None): # from utils.utils import *; plot_st plt.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_labels(labels): +def plot_labels(labels, save_dir= '.'): # plot dataset labels c, b = labels[:, 0], labels[:, 1:].transpose() # classees, boxes @@ -1109,7 +1109,7 @@ def hist2d(x, y, n=100): ax[2].scatter(b[2], b[3], c=hist2d(b[2], b[3], 90), cmap='jet') ax[2].set_xlabel('width') ax[2].set_ylabel('height') - plt.savefig('labels.png', dpi=200) + plt.savefig(os.path.join(save_dir,'labels.png'), dpi=200) def plot_evolution_results(hyp): # from utils.utils import *; plot_evolution_results(hyp) From 9b7386f603eb7485e3d0ef233f3bd28eadbc444b Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 16:08:46 -0500 Subject: [PATCH 14/68] Add save_dir arg to test.test, use arg as location for saving batch jpgs --- test.py | 9 +++++---- train.py | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test.py b/test.py index 8d252ff7e33..f9844fa7e54 100644 --- a/test.py +++ b/test.py @@ -20,7 +20,8 @@ def test(data, model=None, dataloader=None, fast=False, - verbose=False): + verbose=False, + save_dir='.'): # Initialize/load model and set device if model is None: training = False @@ -28,7 +29,7 @@ def test(data, half = device.type != 'cpu' # half precision only supported on CUDA # Remove previous - for f in glob.glob('test_batch*.jpg'): + for f in glob.glob(f'{save_dir}/test_batch*.jpg'): os.remove(f) # Load model @@ -177,9 +178,9 @@ def test(data, # Plot images if batch_i < 1: - f = 'test_batch%g_gt.jpg' % batch_i # filename + f = os.path.join(save_dir, 'test_batch%g_gt.jpg' % batch_i) # filename plot_images(img, targets, paths, f, names) # ground truth - f = 'test_batch%g_pred.jpg' % batch_i + f = os.path.join(save_dir,'test_batch%g_pred.jpg' % batch_i) plot_images(img, output_to_target(output, width, height), paths, f, names) # predictions # Compute statistics diff --git a/train.py b/train.py index 500a6581889..210da6f0f18 100644 --- a/train.py +++ b/train.py @@ -303,7 +303,8 @@ def train(hyp): model=ema.ema, single_cls=opt.single_cls, dataloader=testloader, - fast=epoch < epochs / 2) + fast=epoch < epochs / 2 + save_dir=log_dir) # Write with open(results_file, 'a') as f: From c8152c81a6cd4dcb96668e8fd25b00bad3bee06f Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 17 Jun 2020 16:32:13 -0500 Subject: [PATCH 15/68] Syntax fixes --- train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index 210da6f0f18..31427812f9a 100644 --- a/train.py +++ b/train.py @@ -50,7 +50,7 @@ def train(hyp): os.makedirs(wdir, exist_ok=True) last = wdir + 'last.pt' best = wdir + 'best.pt' - results_file = log_dir + 'results.txt' + results_file = log_dir + os.sep + 'results.txt' epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 @@ -303,7 +303,7 @@ def train(hyp): model=ema.ema, single_cls=opt.single_cls, dataloader=testloader, - fast=epoch < epochs / 2 + fast=epoch < epochs / 2, save_dir=log_dir) # Write From e572bb0803a62d3e6820330e32da0d5e82d4496b Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Sun, 21 Jun 2020 09:36:28 -0500 Subject: [PATCH 16/68] Add plot_results save location to log_dir --- train.py | 2 +- utils/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index 31427812f9a..75cbea6c776 100644 --- a/train.py +++ b/train.py @@ -356,7 +356,7 @@ def train(hyp): os.system('gsutil cp %s gs://%s/weights' % (f2, opt.bucket)) if opt.bucket and ispt else None # upload if not opt.evolve: - plot_results() # save as results.png + plot_results(save_dir = log_dir) # save as results.png print('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) dist.destroy_process_group() if torch.cuda.device_count() > 1 else None torch.cuda.empty_cache() diff --git a/utils/utils.py b/utils/utils.py index fb8d4877d83..df69c1a39b5 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -1154,7 +1154,7 @@ def plot_results_overlay(start=0, stop=0): # from utils.utils import *; plot_re fig.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_results(start=0, stop=0, bucket='', id=(), labels=()): # from utils.utils import *; plot_results() +def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= '.'): # from utils.utils import *; plot_results() # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov5#reproduce-our-training fig, ax = plt.subplots(2, 5, figsize=(12, 6)) ax = ax.ravel() @@ -1164,7 +1164,7 @@ def plot_results(start=0, stop=0, bucket='', id=(), labels=()): # from utils.ut os.system('rm -rf storage.googleapis.com') files = ['https://storage.googleapis.com/%s/results%g.txt' % (bucket, x) for x in id] else: - files = glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt') + files = glob.glob(os.path.join(save_dir,'results*.txt')) + glob.glob('../../Downloads/results*.txt') for fi, f in enumerate(files): try: results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T From ccf0af18b1ec8bdc2c230653d5bf89a64c76d3a9 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 09:13:41 -0500 Subject: [PATCH 17/68] Revert coco128.yaml to initial commit --- data/coco128.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/coco128.yaml b/data/coco128.yaml index 6f72f4ab9d0..2b6184890cd 100644 --- a/data/coco128.yaml +++ b/data/coco128.yaml @@ -8,8 +8,8 @@ # train and val datasets (image directory or *.txt file with image paths) -train: C:/Users/astoken/projects/yolov5/data/coco/images/train2017 -val: C:/Users/astoken/projects/yolov5/data/coco/images/train2017 +train: ../coco128/images/train2017/ +val: ../coco128/images/train2017/ # number of classes nc: 80 From d64ad0fbf341646010e8bba68bfed32a9063d7b2 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 09:17:27 -0500 Subject: [PATCH 18/68] Remove --resume functionality and related checks/logic. --- equip_hyp.yaml | 18 ++++++++++++++++++ train.py | 22 +--------------------- 2 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 equip_hyp.yaml diff --git a/equip_hyp.yaml b/equip_hyp.yaml new file mode 100644 index 00000000000..0623b9f0015 --- /dev/null +++ b/equip_hyp.yaml @@ -0,0 +1,18 @@ +anchor_t: 4.0 +cls: 0.58 +cls_pw: 1.0 +degrees: 0.0 +fl_gamma: 0.0 +giou: 0.05 +hsv_h: 0.014 +hsv_s: 0.68 +hsv_v: 0.36 +iou_t: 0.2 +lr0: 0.001 +momentum: 0.90 +obj: 1.0 +obj_pw: 1.0 +scale: 0.5 +shear: 5 +translate: 0.05 +weight_decay: 0.0005 diff --git a/train.py b/train.py index 75cbea6c776..efbc03a41cd 100644 --- a/train.py +++ b/train.py @@ -372,8 +372,6 @@ def train(hyp): parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path') parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') parser.add_argument('--rect', action='store_true', help='rectangular training') - parser.add_argument('--resume', action='store_true', help='resume training from last.pt') - parser.add_argument('--resume-from-run', type=str, default='', help='resume training from last.pt in this dir') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') @@ -385,27 +383,9 @@ def train(hyp): parser.add_argument('--adam', action='store_true', help='use adam optimizer') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') - parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file. Not needed with --resume.') + parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file.') opt = parser.parse_args() - # logic to resume from latest run if either --resume or --resume-from-run is selected - # Note if neither --resume or --resume-from-run, last is set to empty string - if opt.resume_from_run: - opt.resume = True - last = opt.resume_from_run - elif opt.resume and not opt.resume_from_run: - last = get_latest_run() - print(f'WARNING: No run provided to resume from. Resuming from most recent run found at {last}') - else: - last = '' - - # if resuming, check for hyp file - if last: - last_hyp = last.replace('last.pt', 'hyp.yaml') - if os.path.exists(last_hyp): - opt.hyp = last_hyp - - opt.weights = last if opt.resume else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file opt.hyp = check_file(opt.hyp) if opt.hyp else '' #check file From 7edbf6570e4c7691e5e8e535a31587d42a479c02 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 09:45:57 -0500 Subject: [PATCH 19/68] Fix help message for cfg files --- train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index efbc03a41cd..dea9c50d72c 100644 --- a/train.py +++ b/train.py @@ -368,8 +368,8 @@ def train(hyp): parser = argparse.ArgumentParser() parser.add_argument('--epochs', type=int, default=300) parser.add_argument('--batch-size', type=int, default=16) - parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='*.cfg path') - parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path') + parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model cfg path[*.yaml]') + parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data cfg path [*.yaml]') parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') @@ -383,7 +383,7 @@ def train(hyp): parser.add_argument('--adam', action='store_true', help='use adam optimizer') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') - parser.add_argument('--hyp', type=str, default='', help ='path to hyp yaml file.') + parser.add_argument('--hyp', type=str, default='', help ='hyp cfg path [*.yaml].') opt = parser.parse_args() opt.cfg = check_file(opt.cfg) # check file From 7abf202cadd6707d2fd4cf5905b784d33d9741f3 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 10:03:21 -0500 Subject: [PATCH 20/68] Mode all optimizer settings to 'hyp.yaml', integrate proper momentum with Adam optimizer --- train.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/train.py b/train.py index dea9c50d72c..73456a06516 100644 --- a/train.py +++ b/train.py @@ -20,8 +20,9 @@ # Hyperparameters -hyp = {'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) - 'momentum': 0.937, # SGD momentum +hyp = {'optimizer': 'adam' #if none, default is SGD + 'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) + 'momentum': 0.937, # SGD momentum/Adam beta1 'weight_decay': 5e-4, # optimizer weight decay 'giou': 0.05, # giou loss gain 'cls': 0.58, # cls loss gain @@ -90,8 +91,11 @@ def train(hyp): else: pg0.append(v) # all else - optimizer = optim.Adam(pg0, lr=hyp['lr0']) if opt.adam else \ - optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) + if hyp.optimizer =='adam': + optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) #use default beta2, adjust beta1 for Adam momentum per momentum adjustments in https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR + else: + optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) + optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay optimizer.add_param_group({'params': pg2}) # add pg2 (biases) print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0))) @@ -380,7 +384,6 @@ def train(hyp): parser.add_argument('--weights', type=str, default='', help='initial weights path') parser.add_argument('--name', default='', help='renames results.txt to results_name.txt if supplied') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--adam', action='store_true', help='use adam optimizer') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') parser.add_argument('--hyp', type=str, default='', help ='hyp cfg path [*.yaml].') From bc4ef4861b7649d2743b431f2bd68d32acf3af60 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 10:07:43 -0500 Subject: [PATCH 21/68] Default optimizer SGD --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 73456a06516..a3f43d6dba6 100644 --- a/train.py +++ b/train.py @@ -20,7 +20,7 @@ # Hyperparameters -hyp = {'optimizer': 'adam' #if none, default is SGD +hyp = {'optimizer': 'SGD', # ['adam, 'SGD', None] if none, default is SGD 'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) 'momentum': 0.937, # SGD momentum/Adam beta1 'weight_decay': 5e-4, # optimizer weight decay From 611aacf1bfc63a6bb556a5f27ac5e560cd8b1191 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 10:49:08 -0500 Subject: [PATCH 22/68] Turn opt into dictionary before sending it to yaml --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index a3f43d6dba6..27a6d9fe810 100644 --- a/train.py +++ b/train.py @@ -193,7 +193,7 @@ def train(hyp): yaml.dump(hyp, f) with open(os.path.join(log_dir, 'opt.yaml'), 'w') as f: - yaml.dump(opt, f) + yaml.dump(vars(opt), f) # Class frequency labels = np.concatenate(dataset.labels, 0) From d1ca6f231d51677faa860c42f971a857a0a98b16 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 11:22:12 -0500 Subject: [PATCH 23/68] Delete equip_hyp.yaml --- equip_hyp.yaml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 equip_hyp.yaml diff --git a/equip_hyp.yaml b/equip_hyp.yaml deleted file mode 100644 index 0623b9f0015..00000000000 --- a/equip_hyp.yaml +++ /dev/null @@ -1,18 +0,0 @@ -anchor_t: 4.0 -cls: 0.58 -cls_pw: 1.0 -degrees: 0.0 -fl_gamma: 0.0 -giou: 0.05 -hsv_h: 0.014 -hsv_s: 0.68 -hsv_v: 0.36 -iou_t: 0.2 -lr0: 0.001 -momentum: 0.90 -obj: 1.0 -obj_pw: 1.0 -scale: 0.5 -shear: 5 -translate: 0.05 -weight_decay: 0.0005 From 2d396bea00df15dc2b27727f9e3ef340df71f1de Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 16:57:12 -0500 Subject: [PATCH 24/68] Fix bug in --help from percent sign in help string --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 27a6d9fe810..a0b358e5ed0 100644 --- a/train.py +++ b/train.py @@ -384,7 +384,7 @@ def train(hyp): parser.add_argument('--weights', type=str, default='', help='initial weights path') parser.add_argument('--name', default='', help='renames results.txt to results_name.txt if supplied') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') + parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') parser.add_argument('--hyp', type=str, default='', help ='hyp cfg path [*.yaml].') opt = parser.parse_args() From de191655e49fd5ed7b9af5b9ffaf99b4d63f9c92 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Wed, 24 Jun 2020 17:21:54 -0500 Subject: [PATCH 25/68] Fix yaml saving (don't sort keys), reorder --opt keys, bug fix hyp dict accessor --- train.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/train.py b/train.py index a0b358e5ed0..654a6bf271e 100644 --- a/train.py +++ b/train.py @@ -91,7 +91,7 @@ def train(hyp): else: pg0.append(v) # all else - if hyp.optimizer =='adam': + if hyp['optimizer'] =='adam': optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) #use default beta2, adjust beta1 for Adam momentum per momentum adjustments in https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR else: optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) @@ -190,10 +190,10 @@ def train(hyp): #save hyperparamter and training options in run folder with open(os.path.join(log_dir, 'hyp.yaml'), 'w') as f: - yaml.dump(hyp, f) + yaml.dump(hyp, f, sort_keys=False) with open(os.path.join(log_dir, 'opt.yaml'), 'w') as f: - yaml.dump(vars(opt), f) + yaml.dump(vars(opt), f, sort_keys=False) # Class frequency labels = np.concatenate(dataset.labels, 0) @@ -370,10 +370,11 @@ def train(hyp): if __name__ == '__main__': check_git_status() parser = argparse.ArgumentParser() - parser.add_argument('--epochs', type=int, default=300) - parser.add_argument('--batch-size', type=int, default=16) parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model cfg path[*.yaml]') parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data cfg path [*.yaml]') + parser.add_argument('--hyp', type=str, default='',help='hyp cfg path [*.yaml].') + parser.add_argument('--epochs', type=int, default=300) + parser.add_argument('--batch-size', type=int, default=16) parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') @@ -386,7 +387,7 @@ def train(hyp): parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') - parser.add_argument('--hyp', type=str, default='', help ='hyp cfg path [*.yaml].') + opt = parser.parse_args() opt.cfg = check_file(opt.cfg) # check file From b57f83d005d710f36b39fadb315a34a2caa29df3 Mon Sep 17 00:00:00 2001 From: wanghaoyang0106 Date: Fri, 3 Jul 2020 13:09:21 +0800 Subject: [PATCH 26/68] [bug fix] potential problem if img fed to model is in rectangular shape --- utils/datasets.py | 4 ++-- utils/utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index 1ebd709482f..6caae67423e 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -679,8 +679,8 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding elif scaleFill: # stretch dw, dh = 0.0, 0.0 - new_unpad = new_shape - ratio = new_shape[0] / shape[1], new_shape[1] / shape[0] # width, height ratios + new_unpad = (new_shape[1], new_shape[0]) + ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios dw /= 2 # divide padding into 2 sides dh /= 2 diff --git a/utils/utils.py b/utils/utils.py index 305486a5f6a..332251dcfd3 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -173,7 +173,7 @@ def xywh2xyxy(x): def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None): # Rescale coords (xyxy) from img1_shape to img0_shape if ratio_pad is None: # calculate from img0_shape - gain = max(img1_shape) / max(img0_shape) # gain = old / new + gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding else: gain = ratio_pad[0][0] From 2541f77946530c6afebc5f2400701a220e2954a9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 11:57:48 -0700 Subject: [PATCH 27/68] update detect.py --- detect.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/detect.py b/detect.py index 2650c202d49..268b5dfcd32 100644 --- a/detect.py +++ b/detect.py @@ -123,10 +123,11 @@ def detect(save_img=False): if isinstance(vid_writer, cv2.VideoWriter): vid_writer.release() # release previous video writer + fourcc = 'mp4v' # output video codec fps = vid_cap.get(cv2.CAP_PROP_FPS) w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*opt.fourcc), fps, (w, h)) + vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h)) vid_writer.write(im0) if save_txt or save_img: @@ -145,20 +146,20 @@ def detect(save_img=False): parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') - parser.add_argument('--fourcc', type=str, default='mp4v', help='output video codec (verify ffmpeg support)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--view-img', action='store_true', help='display results') parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') parser.add_argument('--classes', nargs='+', type=int, help='filter by class') parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') + parser.add_argument('--update', action='store_true', help='update all models') opt = parser.parse_args() print(opt) with torch.no_grad(): - detect() - - # # Update all models - # for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: - # detect() - # create_pretrained(opt.weights, opt.weights) + if opt.update: # update all models (to fix SourceChangeWarning) + for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: + detect() + create_pretrained(opt.weights, opt.weights) + else: + detect() From 8c43a6906d32e96002b8d6aa6b5cfb8571fda3ce Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 12:33:51 -0700 Subject: [PATCH 28/68] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1e29d183519..d97be159718 100755 --- a/README.md +++ b/README.md @@ -41,9 +41,13 @@ $ pip install -U -r requirements.txt ## Tutorials * [Notebook](https://github.com/ultralytics/yolov5/blob/master/tutorial.ipynb) Open In Colab +* [Kaggle](https://www.kaggle.com/ultralytics/yolov5-tutorial) * [Train Custom Data](https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data) -* [Google Cloud Quickstart Guide](https://github.com/ultralytics/yolov5/wiki/GCP-Quickstart) -* [Docker Quickstart Guide](https://github.com/ultralytics/yolov5/wiki/Docker-Quickstart) ![Docker Pulls](https://img.shields.io/docker/pulls/ultralytics/yolov5?logo=docker) +* [PyTorch Hub](https://github.com/ultralytics/yolov5/issues/36) +* [ONNX and TorchScript Export](https://github.com/ultralytics/yolov5/issues/251) +* [Test-Time Augmentation (TTA)](https://github.com/ultralytics/yolov5/issues/303) +* [Google Cloud Quickstart](https://github.com/ultralytics/yolov5/wiki/GCP-Quickstart) +* [Docker Quickstart](https://github.com/ultralytics/yolov5/wiki/Docker-Quickstart) ![Docker Pulls](https://img.shields.io/docker/pulls/ultralytics/yolov5?logo=docker) ## Inference From 997ba7b346beede15009b187b6d1d47367f94434 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 12:50:04 -0700 Subject: [PATCH 29/68] import yaml in yolo.py --- models/yolo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/models/yolo.py b/models/yolo.py index 7961fabbd62..b2d09cc51ea 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -48,6 +48,7 @@ def __init__(self, model_cfg='yolov5s.yaml', ch=3, nc=None): # model, input cha if type(model_cfg) is dict: self.md = model_cfg # model dict else: # is *.yaml + import yaml with open(model_cfg) as f: self.md = yaml.load(f, Loader=yaml.FullLoader) # model dict From 38f5c1ad1d0f4d391544e302498eb81ff234e175 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 13:41:21 -0700 Subject: [PATCH 30/68] pruning and sparsity initial commit --- models/yolo.py | 2 +- utils/torch_utils.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/models/yolo.py b/models/yolo.py index b2d09cc51ea..9617f5b3aaa 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -48,7 +48,7 @@ def __init__(self, model_cfg='yolov5s.yaml', ch=3, nc=None): # model, input cha if type(model_cfg) is dict: self.md = model_cfg # model dict else: # is *.yaml - import yaml + import yaml # for torch hub with open(model_cfg) as f: self.md = yaml.load(f, Loader=yaml.FullLoader) # model dict diff --git a/utils/torch_utils.py b/utils/torch_utils.py index fd00b8bde08..35ef0116d26 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -76,6 +76,26 @@ def find_modules(model, mclass=nn.Conv2d): return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)] +def sparsity(model): + # Return global model sparsity + a, b = 0., 0. + for p in model.parameters(): + a += p.numel() + b += (p == 0).sum() + return b / a + + +def prune(model, amount=0.3): + # Prune model to requested global sparsity + import torch.nn.utils.prune as prune + print('Pruning model... ', end='') + for name, m in model.named_modules(): + if isinstance(m, torch.nn.Conv2d): + prune.l1_unstructured(m, name='weight', amount=amount) # prune + prune.remove(m, 'weight') # make permanent + print(' %.3g global sparsity' % sparsity(model)) + + def fuse_conv_and_bn(conv, bn): # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ with torch.no_grad(): From 5ba1de0cdcc414c69ceb7a4c45eb1e3895eca32a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 15:02:56 -0700 Subject: [PATCH 31/68] update experimental.py with Ensemble() module --- models/experimental.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/models/experimental.py b/models/experimental.py index 539e7f970ac..146a61b67a4 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -107,3 +107,15 @@ def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): def forward(self, x): return x + self.act(self.bn(torch.cat([m(x) for m in self.m], 1))) + + +class Ensemble(nn.ModuleList): + # Ensemble of models + def __init__(self): + super(Ensemble, self).__init__() + + def forward(self, x, augment=False): + y = [] + for module in self: + y.append(module(x, augment)[0]) + return torch.cat(y, 1), None # ensembled inference output, train output From 04bdbe4104728dac15937ad06dbb9071ae3bebf9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:16:50 -0700 Subject: [PATCH 32/68] fuse update --- detect.py | 9 +++------ models/yolo.py | 4 ++-- test.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/detect.py b/detect.py index 268b5dfcd32..44cd64e5316 100644 --- a/detect.py +++ b/detect.py @@ -21,13 +21,10 @@ def detect(save_img=False): # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float() # load to FP32 - # torch.save(torch.load(weights, map_location=device), weights) # update model if SourceChangeWarning - # model.fuse() - model.to(device).eval() - imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size + model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model + imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size if half: - model.half() # to FP16 + model.float() # to FP16 # Second-stage classifier classify = False diff --git a/models/yolo.py b/models/yolo.py index 9617f5b3aaa..3fd87a336c6 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -142,14 +142,14 @@ def _print_biases(self): # print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers - print('Fusing layers...') + print('Fusing layers... ', end='') for m in self.model.modules(): if type(m) is Conv: m.conv = torch_utils.fuse_conv_and_bn(m.conv, m.bn) # update conv m.bn = None # remove batchnorm m.forward = m.fuseforward # update forward torch_utils.model_info(self) - + return self def parse_model(md, ch): # model_dict, input_channels(3) print('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) diff --git a/test.py b/test.py index 259d44444bc..644f6b977b3 100644 --- a/test.py +++ b/test.py @@ -22,6 +22,7 @@ def test(data, # Initialize/load model and set device if model is None: training = False + merge = opt.merge # use Merge NMS device = torch_utils.select_device(opt.device, batch_size=batch_size) # Remove previous @@ -59,7 +60,6 @@ def test(data, # Dataloader if dataloader is None: # not training - merge = opt.merge # use Merge NMS img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images From a40f615c6f0c9b3acf3a39b3e58841c006010af2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:24:53 -0700 Subject: [PATCH 33/68] .half() bug fix --- detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detect.py b/detect.py index 44cd64e5316..d02f0a92281 100644 --- a/detect.py +++ b/detect.py @@ -24,7 +24,7 @@ def detect(save_img=False): model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size if half: - model.float() # to FP16 + model.half() # to FP16 # Second-stage classifier classify = False From a62333e8084301721fa66797e8968b11b2204bbc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:27:03 -0700 Subject: [PATCH 34/68] test.py .fuse() update --- test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test.py b/test.py index 644f6b977b3..2d6ab5454b5 100644 --- a/test.py +++ b/test.py @@ -31,11 +31,8 @@ def test(data, # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float() # load to FP32 - torch_utils.model_info(model) - model.fuse() - model.to(device) - imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size + model = torch.load(weights, map_location=device)['model'].float().fuse() # load to FP32 + imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: From 6b95d6d2c06879f0f64f004e1f166246f7e90093 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:37:36 -0700 Subject: [PATCH 35/68] .to(device) bug fix --- test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.py b/test.py index 2d6ab5454b5..1cfae959128 100644 --- a/test.py +++ b/test.py @@ -31,7 +31,7 @@ def test(data, # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float().fuse() # load to FP32 + model = torch.load(weights, map_location=device)['model'].float().fuse().to(device) # load to FP32 imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 From 121d90b3f2ffe085176ca3a21bbbc87260667655 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 6 Jul 2020 11:46:10 -0700 Subject: [PATCH 36/68] update fuse_conv_and_bn() --- utils/torch_utils.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 35ef0116d26..6baa9d5061e 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -90,7 +90,7 @@ def prune(model, amount=0.3): import torch.nn.utils.prune as prune print('Pruning model... ', end='') for name, m in model.named_modules(): - if isinstance(m, torch.nn.Conv2d): + if isinstance(m, nn.Conv2d): prune.l1_unstructured(m, name='weight', amount=amount) # prune prune.remove(m, 'weight') # make permanent print(' %.3g global sparsity' % sparsity(model)) @@ -100,12 +100,12 @@ def fuse_conv_and_bn(conv, bn): # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ with torch.no_grad(): # init - fusedconv = torch.nn.Conv2d(conv.in_channels, - conv.out_channels, - kernel_size=conv.kernel_size, - stride=conv.stride, - padding=conv.padding, - bias=True) + fusedconv = nn.Conv2d(conv.in_channels, + conv.out_channels, + kernel_size=conv.kernel_size, + stride=conv.stride, + padding=conv.padding, + bias=True).to(conv.weight.device) # prepare filters w_conv = conv.weight.clone().view(conv.out_channels, -1) @@ -113,10 +113,7 @@ def fuse_conv_and_bn(conv, bn): fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size())) # prepare spatial bias - if conv.bias is not None: - b_conv = conv.bias - else: - b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) + b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn) @@ -159,8 +156,8 @@ def load_classifier(name='resnet101', n=2): # Reshape output to n classes filters = model.fc.weight.shape[1] - model.fc.bias = torch.nn.Parameter(torch.zeros(n), requires_grad=True) - model.fc.weight = torch.nn.Parameter(torch.zeros(n, filters), requires_grad=True) + model.fc.bias = nn.Parameter(torch.zeros(n), requires_grad=True) + model.fc.weight = nn.Parameter(torch.zeros(n, filters), requires_grad=True) model.fc.out_features = n return model From 5ac517b22a4f8aea2fc4d92b77876bcfcd83dfb9 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 7 Jul 2020 10:09:53 -0500 Subject: [PATCH 37/68] Remove duplicate `verbose` arg in test.py --- test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test.py b/test.py index 05358213bdf..f3d0ec65613 100644 --- a/test.py +++ b/test.py @@ -19,7 +19,6 @@ def test(data, model=None, dataloader=None, fast=False, - verbose=False, save_dir='.', merge=False): From 8b6dbb7cfc9a7a4ec7b0661257455e8978715aaa Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 7 Jul 2020 10:30:48 -0500 Subject: [PATCH 38/68] Add optimizer choice to hyp file --- new_hyp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/new_hyp.yaml b/new_hyp.yaml index 46838fe8e14..498a4e12e54 100644 --- a/new_hyp.yaml +++ b/new_hyp.yaml @@ -1,3 +1,4 @@ +optimizer: 'adam' anchor_t: 10.0 cls: 0.58 cls_pw: 1.0 From 52bac22f09dd11f9240de3d1f3f2729184f5264e Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 7 Jul 2020 10:42:28 -0500 Subject: [PATCH 39/68] Add in --resume functionality with option to specify path or to get most recent run --- train.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index 4a9251f3169..34b4950c72d 100644 --- a/train.py +++ b/train.py @@ -22,7 +22,7 @@ # Hyperparameters -hyp = {'optimizer': 'SGD', # ['adam, 'SGD', None] if none, default is SGD +hyp = {'optimizer': 'SGD', # ['adam', 'SGD', None] if none, default is SGD 'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) 'momentum': 0.937, # SGD momentum/Adam beta1 'weight_decay': 5e-4, # optimizer weight decay @@ -375,7 +375,7 @@ def train(hyp): parser.add_argument('--batch-size', type=int, default=16) parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') parser.add_argument('--rect', action='store_true', help='rectangular training') - parser.add_argument('--resume', action='store_true', help='resume training from last.pt') + parser.add_argument('--resume', nargs='?', const = 'get_last', default=False, help='resume training from given path/to/last.pt, or most recent run if blank.') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check') @@ -390,7 +390,13 @@ def train(hyp): opt = parser.parse_args() + # use given path/to/last.pt or find most recent run if no path given + last = get_latest_run() if opt.resume == 'get_last' else opt.resume + if last and not opt.weights: + print(f'Resuming training from {last}') opt.weights = last if opt.resume and not opt.weights else opt.weights + + opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file opt.hyp = check_file(opt.hyp) if opt.hyp else '' #check file From 95f0a56df721ced26af2d08710b2340b226bbf26 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 7 Jul 2020 11:19:49 -0500 Subject: [PATCH 40/68] Bug fix to get_latest_run when recent run is named --- utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.py b/utils/utils.py index d9e0b836ec7..35be9ea1797 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -39,7 +39,7 @@ def init_seeds(seed=0): def get_latest_run(search_dir = './runs'): # get path to most recent 'last.pt' in run dirs # assumes most recently saved 'last.pt' is the desired weights to --resume from - last_list = glob.glob(f'{search_dir}/**/last.pt', recursive=True) + last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) latest = max(last_list, key = os.path.getctime) return latest From 43647514daab4037548ae378c15f39471d3c3b81 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Tue, 7 Jul 2020 11:24:11 -0500 Subject: [PATCH 41/68] Colab example/test of new features --- advanced_logging_test.ipynb | 544 ++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 advanced_logging_test.ipynb diff --git a/advanced_logging_test.ipynb b/advanced_logging_test.ipynb new file mode 100644 index 00000000000..0036b3b1a19 --- /dev/null +++ b/advanced_logging_test.ipynb @@ -0,0 +1,544 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "advanced_logging_test.ipynb", + "provenance": [], + "authorship_tag": "ABX9TyPFy7j0vPOSgtY60fQfXjdq", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-cMOOhIGFBJa", + "colab_type": "text" + }, + "source": [ + "# **Test Advanced Logging Branch Features**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "YwXvkCXB9Yif", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 136 + }, + "outputId": "9a7ab9a3-c73d-4dae-ae35-237c9d84728f" + }, + "source": [ + "!git clone -b advanced_logging https://github.com/alexstoken/yolov5.git" + ], + "execution_count": 1, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Cloning into 'yolov5'...\n", + "remote: Enumerating objects: 53, done.\u001b[K\n", + "remote: Counting objects: 100% (53/53), done.\u001b[K\n", + "remote: Compressing objects: 100% (39/39), done.\u001b[K\n", + "remote: Total 1223 (delta 28), reused 35 (delta 14), pack-reused 1170\u001b[K\n", + "Receiving objects: 100% (1223/1223), 3.50 MiB | 3.20 MiB/s, done.\n", + "Resolving deltas: 100% (811/811), done.\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "pNJbzKWK9r3l", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "outputId": "67d82040-b5af-48b5-fb93-4ed962e48680" + }, + "source": [ + "!pip install -r yolov5/requirements.txt # install dependencies\n", + "%cd yolov5\n", + "\n", + "import torch\n", + "from IPython.display import Image, clear_output # to display images\n", + "from utils.google_utils import gdrive_download # to download models/datasets\n", + "\n", + "clear_output()\n", + "print('Setup complete. Using torch %s %s' % (torch.__version__, torch.cuda.get_device_properties(0) if torch.cuda.is_available() else 'CPU'))" + ], + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Setup complete. Using torch 1.5.1+cu101 _CudaDeviceProperties(name='Tesla K80', major=3, minor=7, total_memory=11441MB, multi_processor_count=13)\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "amtgnroz901E", + "colab_type": "text" + }, + "source": [ + "## Train" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "0NSb22om9ybq", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + }, + "outputId": "ee202bce-ad59-4bba-fcd2-77a3dd98fd4a" + }, + "source": [ + "# Download tutorial dataset coco128.yaml\n", + "gdrive_download('1n_oKgR81BJtqk75b00eAjdv03qVCQn2f','coco128.zip') # tutorial dataset\n", + "!mv ./coco128 ../ # move folder alongside /yolov5" + ], + "execution_count": 3, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Downloading https://drive.google.com/uc?export=download&id=1n_oKgR81BJtqk75b00eAjdv03qVCQn2f as coco128.zip... unzipping... Done (5.1s)\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "71hxUTfvFJky", + "colab_type": "text" + }, + "source": [ + "**Verify Help Arg Works**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "6SLw4hNi-C1L", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 544 + }, + "outputId": "e0ca506c-ddfb-4b86-d5e8-ca98729672b9" + }, + "source": [ + "!python train.py --help" + ], + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex\n", + "usage: train.py [-h] [--cfg CFG] [--data DATA] [--hyp HYP] [--epochs EPOCHS]\n", + " [--batch-size BATCH_SIZE] [--img-size IMG_SIZE [IMG_SIZE ...]]\n", + " [--rect] [--resume [RESUME]] [--nosave] [--notest]\n", + " [--noautoanchor] [--evolve] [--bucket BUCKET] [--cache-images]\n", + " [--weights WEIGHTS] [--name NAME] [--device DEVICE]\n", + " [--multi-scale] [--single-cls]\n", + "\n", + "optional arguments:\n", + " -h, --help show this help message and exit\n", + " --cfg CFG model cfg path[*.yaml]\n", + " --data DATA data cfg path [*.yaml]\n", + " --hyp HYP hyp cfg path [*.yaml].\n", + " --epochs EPOCHS\n", + " --batch-size BATCH_SIZE\n", + " --img-size IMG_SIZE [IMG_SIZE ...]\n", + " train,test sizes. Assumes square imgs.\n", + " --rect rectangular training\n", + " --resume [RESUME] resume training from given path/to/last.pt, or most\n", + " recent run if blank.\n", + " --nosave only save final checkpoint\n", + " --notest only test final epoch\n", + " --noautoanchor disable autoanchor check\n", + " --evolve evolve hyperparameters\n", + " --bucket BUCKET gsutil bucket\n", + " --cache-images cache images for faster training\n", + " --weights WEIGHTS initial weights path\n", + " --name NAME renames results.txt to results_name.txt if supplied\n", + " --device DEVICE cuda device, i.e. 0 or 0,1,2,3 or cpu\n", + " --multi-scale vary img-size +/- 50%\n", + " --single-cls train as single-class dataset\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ou1ochfx-VTr", + "colab_type": "text" + }, + "source": [ + "**Run with hyperparameters from yaml file**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "wLHoZYbk-EqT", + "colab_type": "code", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "outputId": "63ed4676-22b8-4852-e663-5c3cfefe19df" + }, + "source": [ + "!python train.py --img 320 --batch 32 --epochs 3 --data ./data/coco128.yaml --cfg ./models/yolov5s.yaml --weights yolov5s.pt --name tutorial --cache --hyp new_hyp.yaml" + ], + "execution_count": 9, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex\n", + "Namespace(batch_size=32, bucket='', cache_images=True, cfg='./models/yolov5s.yaml', data='./data/coco128.yaml', device='', epochs=3, evolve=False, hyp='new_hyp.yaml', img_size=[320], multi_scale=False, name='tutorial', noautoanchor=False, nosave=False, notest=False, rect=False, resume=False, single_cls=False, weights='yolov5s.pt')\n", + "Using CUDA device0 _CudaDeviceProperties(name='Tesla K80', total_memory=11441MB)\n", + "\n", + "2020-07-07 16:03:25.684542: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1\n", + "Using FocalLoss(gamma=1)\n", + "Beginning training with {'optimizer': 'adam', 'lr0': 0.001, 'momentum': 0.9, 'weight_decay': 0.000625, 'giou': 0.15, 'cls': 0.58, 'cls_pw': 1.0, 'obj': 1.0, 'obj_pw': 1.0, 'iou_t': 0.2, 'anchor_t': 10.0, 'fl_gamma': 1.0, 'hsv_h': 0.014, 'hsv_s': 0.68, 'hsv_v': 0.36, 'degrees': 20.0, 'translate': 0.0, 'scale': 0.5, 'shear': 0.0}\n", + "\n", + "\n", + "Start Tensorboard with \"tensorboard --logdir=runs\", view at http://localhost:6006/\n", + "\n", + " from n params module arguments \n", + " 0 -1 1 3520 models.common.Focus [3, 32, 3] \n", + " 1 -1 1 18560 models.common.Conv [32, 64, 3, 2] \n", + " 2 -1 1 19904 models.common.BottleneckCSP [64, 64, 1] \n", + " 3 -1 1 73984 models.common.Conv [64, 128, 3, 2] \n", + " 4 -1 1 161152 models.common.BottleneckCSP [128, 128, 3] \n", + " 5 -1 1 295424 models.common.Conv [128, 256, 3, 2] \n", + " 6 -1 1 641792 models.common.BottleneckCSP [256, 256, 3] \n", + " 7 -1 1 1180672 models.common.Conv [256, 512, 3, 2] \n", + " 8 -1 1 656896 models.common.SPP [512, 512, [5, 9, 13]] \n", + " 9 -1 1 1248768 models.common.BottleneckCSP [512, 512, 1, False] \n", + " 10 -1 1 131584 models.common.Conv [512, 256, 1, 1] \n", + " 11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", + " 12 [-1, 6] 1 0 models.common.Concat [1] \n", + " 13 -1 1 378624 models.common.BottleneckCSP [512, 256, 1, False] \n", + " 14 -1 1 33024 models.common.Conv [256, 128, 1, 1] \n", + " 15 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", + " 16 [-1, 4] 1 0 models.common.Concat [1] \n", + " 17 -1 1 95104 models.common.BottleneckCSP [256, 128, 1, False] \n", + " 18 -1 1 32895 torch.nn.modules.conv.Conv2d [128, 255, 1, 1] \n", + " 19 -2 1 147712 models.common.Conv [128, 128, 3, 2] \n", + " 20 [-1, 14] 1 0 models.common.Concat [1] \n", + " 21 -1 1 313088 models.common.BottleneckCSP [256, 256, 1, False] \n", + " 22 -1 1 65535 torch.nn.modules.conv.Conv2d [256, 255, 1, 1] \n", + " 23 -2 1 590336 models.common.Conv [256, 256, 3, 2] \n", + " 24 [-1, 10] 1 0 models.common.Concat [1] \n", + " 25 -1 1 1248768 models.common.BottleneckCSP [512, 512, 1, False] \n", + " 26 -1 1 130815 torch.nn.modules.conv.Conv2d [512, 255, 1, 1] \n", + " 27 [-1, 22, 18] 1 0 models.yolo.Detect [80, [[116, 90, 156, 198, 373, 326], [30, 61, 62, 45, 59, 119], [10, 13, 16, 30, 33, 23]]]\n", + "Model Summary: 191 layers, 7.46816e+06 parameters, 7.46816e+06 gradients\n", + "\n", + "Optimizer groups: 62 .bias, 70 conv.weight, 59 other\n", + "/usr/local/lib/python3.6/dist-packages/torch/optim/lr_scheduler.py:123: UserWarning: Detected call of `lr_scheduler.step()` before `optimizer.step()`. In PyTorch 1.1.0 and later, you should call them in the opposite order: `optimizer.step()` before `lr_scheduler.step()`. Failure to do this will result in PyTorch skipping the first value of the learning rate schedule. See more details at https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate\n", + " \"https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate\", UserWarning)\n", + "Caching labels ../coco128/labels/train2017 (126 found, 0 missing, 2 empty, 0 duplicate, for 128 images): 100% 128/128 [00:00<00:00, 6343.82it/s]\n", + "Caching images (0.0GB): 100% 128/128 [00:00<00:00, 137.36it/s]\n", + "Caching labels ../coco128/labels/train2017 (126 found, 0 missing, 2 empty, 0 duplicate, for 128 images): 100% 128/128 [00:00<00:00, 4530.37it/s]\n", + "Caching images (0.0GB): 100% 128/128 [00:01<00:00, 124.18it/s]\n", + "\n", + "Analyzing anchors... Best Possible Recall (BPR) = 0.9968\n", + "Image sizes 320 train, 320 test\n", + "Using 2 dataloader workers\n", + "Starting training for 3 epochs...\n", + "\n", + " Epoch gpu_mem GIoU obj cls total targets img_size\n", + " 0/2 2.71G 0.2492 0.08469 0.01266 0.3466 469 320: 100% 4/4 [00:06<00:00, 1.56s/it]\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:21<00:00, 5.39s/it]\n", + " all 128 929 0.0866 0.26 0.14 0.0587\n", + "\n", + " Epoch gpu_mem GIoU obj cls total targets img_size\n", + " 1/2 2.67G 0.245 0.07209 0.01212 0.3292 424 320: 100% 4/4 [00:02<00:00, 1.69it/s]\n", + " Class Images Targets P R mAP@.5 mAP@.5:.95: 0% 0/4 [00:00 Date: Tue, 7 Jul 2020 11:31:25 -0500 Subject: [PATCH 42/68] Delete advanced_logging_test.ipynb Remove from advanced_logging branch before merge. --- advanced_logging_test.ipynb | 544 ------------------------------------ 1 file changed, 544 deletions(-) delete mode 100644 advanced_logging_test.ipynb diff --git a/advanced_logging_test.ipynb b/advanced_logging_test.ipynb deleted file mode 100644 index 0036b3b1a19..00000000000 --- a/advanced_logging_test.ipynb +++ /dev/null @@ -1,544 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "name": "advanced_logging_test.ipynb", - "provenance": [], - "authorship_tag": "ABX9TyPFy7j0vPOSgtY60fQfXjdq", - "include_colab_link": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "accelerator": "GPU" - }, - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "view-in-github", - "colab_type": "text" - }, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "-cMOOhIGFBJa", - "colab_type": "text" - }, - "source": [ - "# **Test Advanced Logging Branch Features**" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "YwXvkCXB9Yif", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 136 - }, - "outputId": "9a7ab9a3-c73d-4dae-ae35-237c9d84728f" - }, - "source": [ - "!git clone -b advanced_logging https://github.com/alexstoken/yolov5.git" - ], - "execution_count": 1, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Cloning into 'yolov5'...\n", - "remote: Enumerating objects: 53, done.\u001b[K\n", - "remote: Counting objects: 100% (53/53), done.\u001b[K\n", - "remote: Compressing objects: 100% (39/39), done.\u001b[K\n", - "remote: Total 1223 (delta 28), reused 35 (delta 14), pack-reused 1170\u001b[K\n", - "Receiving objects: 100% (1223/1223), 3.50 MiB | 3.20 MiB/s, done.\n", - "Resolving deltas: 100% (811/811), done.\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "pNJbzKWK9r3l", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "67d82040-b5af-48b5-fb93-4ed962e48680" - }, - "source": [ - "!pip install -r yolov5/requirements.txt # install dependencies\n", - "%cd yolov5\n", - "\n", - "import torch\n", - "from IPython.display import Image, clear_output # to display images\n", - "from utils.google_utils import gdrive_download # to download models/datasets\n", - "\n", - "clear_output()\n", - "print('Setup complete. Using torch %s %s' % (torch.__version__, torch.cuda.get_device_properties(0) if torch.cuda.is_available() else 'CPU'))" - ], - "execution_count": 2, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Setup complete. Using torch 1.5.1+cu101 _CudaDeviceProperties(name='Tesla K80', major=3, minor=7, total_memory=11441MB, multi_processor_count=13)\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "amtgnroz901E", - "colab_type": "text" - }, - "source": [ - "## Train" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "0NSb22om9ybq", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "ee202bce-ad59-4bba-fcd2-77a3dd98fd4a" - }, - "source": [ - "# Download tutorial dataset coco128.yaml\n", - "gdrive_download('1n_oKgR81BJtqk75b00eAjdv03qVCQn2f','coco128.zip') # tutorial dataset\n", - "!mv ./coco128 ../ # move folder alongside /yolov5" - ], - "execution_count": 3, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Downloading https://drive.google.com/uc?export=download&id=1n_oKgR81BJtqk75b00eAjdv03qVCQn2f as coco128.zip... unzipping... Done (5.1s)\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "71hxUTfvFJky", - "colab_type": "text" - }, - "source": [ - "**Verify Help Arg Works**" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "6SLw4hNi-C1L", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 544 - }, - "outputId": "e0ca506c-ddfb-4b86-d5e8-ca98729672b9" - }, - "source": [ - "!python train.py --help" - ], - "execution_count": 4, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex\n", - "usage: train.py [-h] [--cfg CFG] [--data DATA] [--hyp HYP] [--epochs EPOCHS]\n", - " [--batch-size BATCH_SIZE] [--img-size IMG_SIZE [IMG_SIZE ...]]\n", - " [--rect] [--resume [RESUME]] [--nosave] [--notest]\n", - " [--noautoanchor] [--evolve] [--bucket BUCKET] [--cache-images]\n", - " [--weights WEIGHTS] [--name NAME] [--device DEVICE]\n", - " [--multi-scale] [--single-cls]\n", - "\n", - "optional arguments:\n", - " -h, --help show this help message and exit\n", - " --cfg CFG model cfg path[*.yaml]\n", - " --data DATA data cfg path [*.yaml]\n", - " --hyp HYP hyp cfg path [*.yaml].\n", - " --epochs EPOCHS\n", - " --batch-size BATCH_SIZE\n", - " --img-size IMG_SIZE [IMG_SIZE ...]\n", - " train,test sizes. Assumes square imgs.\n", - " --rect rectangular training\n", - " --resume [RESUME] resume training from given path/to/last.pt, or most\n", - " recent run if blank.\n", - " --nosave only save final checkpoint\n", - " --notest only test final epoch\n", - " --noautoanchor disable autoanchor check\n", - " --evolve evolve hyperparameters\n", - " --bucket BUCKET gsutil bucket\n", - " --cache-images cache images for faster training\n", - " --weights WEIGHTS initial weights path\n", - " --name NAME renames results.txt to results_name.txt if supplied\n", - " --device DEVICE cuda device, i.e. 0 or 0,1,2,3 or cpu\n", - " --multi-scale vary img-size +/- 50%\n", - " --single-cls train as single-class dataset\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ou1ochfx-VTr", - "colab_type": "text" - }, - "source": [ - "**Run with hyperparameters from yaml file**" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "wLHoZYbk-EqT", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "outputId": "63ed4676-22b8-4852-e663-5c3cfefe19df" - }, - "source": [ - "!python train.py --img 320 --batch 32 --epochs 3 --data ./data/coco128.yaml --cfg ./models/yolov5s.yaml --weights yolov5s.pt --name tutorial --cache --hyp new_hyp.yaml" - ], - "execution_count": 9, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex\n", - "Namespace(batch_size=32, bucket='', cache_images=True, cfg='./models/yolov5s.yaml', data='./data/coco128.yaml', device='', epochs=3, evolve=False, hyp='new_hyp.yaml', img_size=[320], multi_scale=False, name='tutorial', noautoanchor=False, nosave=False, notest=False, rect=False, resume=False, single_cls=False, weights='yolov5s.pt')\n", - "Using CUDA device0 _CudaDeviceProperties(name='Tesla K80', total_memory=11441MB)\n", - "\n", - "2020-07-07 16:03:25.684542: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1\n", - "Using FocalLoss(gamma=1)\n", - "Beginning training with {'optimizer': 'adam', 'lr0': 0.001, 'momentum': 0.9, 'weight_decay': 0.000625, 'giou': 0.15, 'cls': 0.58, 'cls_pw': 1.0, 'obj': 1.0, 'obj_pw': 1.0, 'iou_t': 0.2, 'anchor_t': 10.0, 'fl_gamma': 1.0, 'hsv_h': 0.014, 'hsv_s': 0.68, 'hsv_v': 0.36, 'degrees': 20.0, 'translate': 0.0, 'scale': 0.5, 'shear': 0.0}\n", - "\n", - "\n", - "Start Tensorboard with \"tensorboard --logdir=runs\", view at http://localhost:6006/\n", - "\n", - " from n params module arguments \n", - " 0 -1 1 3520 models.common.Focus [3, 32, 3] \n", - " 1 -1 1 18560 models.common.Conv [32, 64, 3, 2] \n", - " 2 -1 1 19904 models.common.BottleneckCSP [64, 64, 1] \n", - " 3 -1 1 73984 models.common.Conv [64, 128, 3, 2] \n", - " 4 -1 1 161152 models.common.BottleneckCSP [128, 128, 3] \n", - " 5 -1 1 295424 models.common.Conv [128, 256, 3, 2] \n", - " 6 -1 1 641792 models.common.BottleneckCSP [256, 256, 3] \n", - " 7 -1 1 1180672 models.common.Conv [256, 512, 3, 2] \n", - " 8 -1 1 656896 models.common.SPP [512, 512, [5, 9, 13]] \n", - " 9 -1 1 1248768 models.common.BottleneckCSP [512, 512, 1, False] \n", - " 10 -1 1 131584 models.common.Conv [512, 256, 1, 1] \n", - " 11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", - " 12 [-1, 6] 1 0 models.common.Concat [1] \n", - " 13 -1 1 378624 models.common.BottleneckCSP [512, 256, 1, False] \n", - " 14 -1 1 33024 models.common.Conv [256, 128, 1, 1] \n", - " 15 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", - " 16 [-1, 4] 1 0 models.common.Concat [1] \n", - " 17 -1 1 95104 models.common.BottleneckCSP [256, 128, 1, False] \n", - " 18 -1 1 32895 torch.nn.modules.conv.Conv2d [128, 255, 1, 1] \n", - " 19 -2 1 147712 models.common.Conv [128, 128, 3, 2] \n", - " 20 [-1, 14] 1 0 models.common.Concat [1] \n", - " 21 -1 1 313088 models.common.BottleneckCSP [256, 256, 1, False] \n", - " 22 -1 1 65535 torch.nn.modules.conv.Conv2d [256, 255, 1, 1] \n", - " 23 -2 1 590336 models.common.Conv [256, 256, 3, 2] \n", - " 24 [-1, 10] 1 0 models.common.Concat [1] \n", - " 25 -1 1 1248768 models.common.BottleneckCSP [512, 512, 1, False] \n", - " 26 -1 1 130815 torch.nn.modules.conv.Conv2d [512, 255, 1, 1] \n", - " 27 [-1, 22, 18] 1 0 models.yolo.Detect [80, [[116, 90, 156, 198, 373, 326], [30, 61, 62, 45, 59, 119], [10, 13, 16, 30, 33, 23]]]\n", - "Model Summary: 191 layers, 7.46816e+06 parameters, 7.46816e+06 gradients\n", - "\n", - "Optimizer groups: 62 .bias, 70 conv.weight, 59 other\n", - "/usr/local/lib/python3.6/dist-packages/torch/optim/lr_scheduler.py:123: UserWarning: Detected call of `lr_scheduler.step()` before `optimizer.step()`. In PyTorch 1.1.0 and later, you should call them in the opposite order: `optimizer.step()` before `lr_scheduler.step()`. Failure to do this will result in PyTorch skipping the first value of the learning rate schedule. See more details at https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate\n", - " \"https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate\", UserWarning)\n", - "Caching labels ../coco128/labels/train2017 (126 found, 0 missing, 2 empty, 0 duplicate, for 128 images): 100% 128/128 [00:00<00:00, 6343.82it/s]\n", - "Caching images (0.0GB): 100% 128/128 [00:00<00:00, 137.36it/s]\n", - "Caching labels ../coco128/labels/train2017 (126 found, 0 missing, 2 empty, 0 duplicate, for 128 images): 100% 128/128 [00:00<00:00, 4530.37it/s]\n", - "Caching images (0.0GB): 100% 128/128 [00:01<00:00, 124.18it/s]\n", - "\n", - "Analyzing anchors... Best Possible Recall (BPR) = 0.9968\n", - "Image sizes 320 train, 320 test\n", - "Using 2 dataloader workers\n", - "Starting training for 3 epochs...\n", - "\n", - " Epoch gpu_mem GIoU obj cls total targets img_size\n", - " 0/2 2.71G 0.2492 0.08469 0.01266 0.3466 469 320: 100% 4/4 [00:06<00:00, 1.56s/it]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 100% 4/4 [00:21<00:00, 5.39s/it]\n", - " all 128 929 0.0866 0.26 0.14 0.0587\n", - "\n", - " Epoch gpu_mem GIoU obj cls total targets img_size\n", - " 1/2 2.67G 0.245 0.07209 0.01212 0.3292 424 320: 100% 4/4 [00:02<00:00, 1.69it/s]\n", - " Class Images Targets P R mAP@.5 mAP@.5:.95: 0% 0/4 [00:00 Date: Tue, 7 Jul 2020 15:40:50 -0700 Subject: [PATCH 43/68] Initial model ensemble capability #318 --- detect.py | 7 +++---- models/experimental.py | 17 +++++++++++++++++ test.py | 27 ++++++++++++--------------- utils/datasets.py | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/detect.py b/detect.py index d02f0a92281..7b9d9b69b14 100644 --- a/detect.py +++ b/detect.py @@ -2,7 +2,7 @@ import torch.backends.cudnn as cudnn -from utils import google_utils +from models.experimental import * from utils.datasets import * from utils.utils import * @@ -20,8 +20,7 @@ def detect(save_img=False): half = device.type != 'cpu' # half precision only supported on CUDA # Load model - google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model + model = attempt_load(weights, map_location=device) # load FP32 model imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size if half: model.half() # to FP16 @@ -137,7 +136,7 @@ def detect(save_img=False): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path') + parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)') parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') diff --git a/models/experimental.py b/models/experimental.py index 146a61b67a4..32a88f28464 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -1,6 +1,7 @@ # This file contains experimental modules from models.common import * +from utils import google_utils class CrossConv(nn.Module): @@ -119,3 +120,19 @@ def forward(self, x, augment=False): for module in self: y.append(module(x, augment)[0]) return torch.cat(y, 1), None # ensembled inference output, train output + + +def attempt_load(weights, map_location=None): + # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a + model = Ensemble() + for w in weights if isinstance(weights, list) else [weights]: + google_utils.attempt_download(w) + model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model + + if len(model) == 1: + return model[-1] # return model + else: + print('Ensemble created with %s\n' % weights) + for k in ['names', 'stride']: + setattr(model, k, getattr(model[-1], k)) + return model # return ensemble diff --git a/test.py b/test.py index 1cfae959128..a39ac9e4127 100644 --- a/test.py +++ b/test.py @@ -1,9 +1,8 @@ import argparse import json -from utils import google_utils +from models.experimental import * from utils.datasets import * -from utils.utils import * def test(data, @@ -20,28 +19,26 @@ def test(data, dataloader=None, merge=False): # Initialize/load model and set device - if model is None: - training = False - merge = opt.merge # use Merge NMS + training = model is not None + if training: # called by train.py + device = next(model.parameters()).device # get model device + + else: # called directly device = torch_utils.select_device(opt.device, batch_size=batch_size) + merge = opt.merge # use Merge NMS # Remove previous for f in glob.glob('test_batch*.jpg'): os.remove(f) # Load model - google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float().fuse().to(device) # load to FP32 + model = attempt_load(weights, map_location=device) # load FP32 model imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: # model = nn.DataParallel(model) - else: # called by train.py - training = True - device = next(model.parameters()).device # get model device - # Half half = device.type != 'cpu' and torch.cuda.device_count() == 1 # half precision only supported on single-GPU if half: @@ -56,11 +53,11 @@ def test(data, niou = iouv.numel() # Dataloader - if dataloader is None: # not training + if not training: img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images - dataloader = create_dataloader(path, imgsz, batch_size, int(max(model.stride)), opt, + dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt, hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0] seen = 0 @@ -193,7 +190,7 @@ def test(data, if save_json and map50 and len(jdict): imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files] f = 'detections_val2017_%s_results.json' % \ - (weights.split(os.sep)[-1].replace('.pt', '') if weights else '') # filename + (weights.split(os.sep)[-1].replace('.pt', '') if isinstance(weights, str) else '') # filename print('\nCOCO mAP with pycocotools... saving %s...' % f) with open(f, 'w') as file: json.dump(jdict, file) @@ -226,7 +223,7 @@ def test(data, if __name__ == '__main__': parser = argparse.ArgumentParser(prog='test.py') - parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path') + parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)') parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path') parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch') parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') diff --git a/utils/datasets.py b/utils/datasets.py index 1ebd709482f..5c88f37e2e2 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -48,7 +48,7 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa rect=rect, # rectangular training cache_images=cache, single_cls=opt.single_cls, - stride=stride, + stride=int(stride), pad=pad) batch_size = min(batch_size, len(dataset)) From a9918fbf2b6648bbb4bac3e13f14217a54ee28a4 Mon Sep 17 00:00:00 2001 From: Laurentiu Diaconu Date: Wed, 8 Jul 2020 09:43:33 +0300 Subject: [PATCH 44/68] updated coreml conversion to do pixel scaling between 0.0 and 1.0 --- models/export.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/export.py b/models/export.py index c11c0a39119..990c86e9231 100644 --- a/models/export.py +++ b/models/export.py @@ -61,7 +61,8 @@ import coremltools as ct print('\nStarting CoreML export with coremltools %s...' % ct.__version__) - model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape)]) # convert + # convert model from torchscript and apply pixel scaling as per detect.py + model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape, scale=1/255.0, bias=[0, 0, 0])]) f = opt.weights.replace('.pt', '.mlmodel') # filename model.save(f) print('CoreML export success, saved as %s' % f) From 1b9e28e72ea63c89053bba2e4bcb0d91aa691fce Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 10:21:30 -0700 Subject: [PATCH 45/68] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1100495b9c0..bca726aa33f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ Cython numpy==1.17 opencv-python -torch>=1.4 +torch>=1.5.1 matplotlib pillow tensorboard From 16f6834486a2a15a1b25c48042eeb1d8ce3841f8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 14:23:34 -0700 Subject: [PATCH 46/68] update train.py and experimental.py --- models/experimental.py | 5 ++++- train.py | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/models/experimental.py b/models/experimental.py index 32a88f28464..a22f6bbf60e 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -119,7 +119,10 @@ def forward(self, x, augment=False): y = [] for module in self: y.append(module(x, augment)[0]) - return torch.cat(y, 1), None # ensembled inference output, train output + # y = torch.stack(y).max(0)[0] # max ensemble + # y = torch.cat(y, 1) # nms ensemble + y = torch.stack(y).mean(0) # mean ensemble + return y, None # inference, train output def attempt_load(weights, map_location=None): diff --git a/train.py b/train.py index 61ed84ea8fa..c97e96bbd08 100644 --- a/train.py +++ b/train.py @@ -101,11 +101,13 @@ def train(hyp): optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay optimizer.add_param_group({'params': pg2}) # add pg2 (biases) + print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0))) + del pg0, pg1, pg2 + # Scheduler https://arxiv.org/pdf/1812.01187.pdf lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) - print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0))) - del pg0, pg1, pg2 + # plot_lr_scheduler(optimizer, scheduler, epochs) # Load Model google_utils.attempt_download(weights) @@ -147,12 +149,7 @@ def train(hyp): if mixed_precision: model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0) - - scheduler.last_epoch = start_epoch - 1 # do not move - # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 - # plot_lr_scheduler(optimizer, scheduler, epochs) - - # Initialize distributed training + # Distributed training if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): dist.init_process_group(backend='nccl', # distributed backend init_method='tcp://127.0.0.1:9999', # init method @@ -198,9 +195,10 @@ def train(hyp): # Start training t0 = time.time() nb = len(dataloader) # number of batches - n_burn = max(3 * nb, 1e3) # burn-in iterations, max(3 epochs, 1k iterations) + nw = max(3 * nb, 1e3) # number of warmup iterations, max(3 epochs, 1k iterations) maps = np.zeros(nc) # mAP per class results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification' + scheduler.last_epoch = start_epoch - 1 # do not move print('Image sizes %g train, %g test' % (imgsz, imgsz_test)) print('Using %g dataloader workers' % dataloader.num_workers) print('Starting training for %g epochs...' % epochs) @@ -225,9 +223,9 @@ def train(hyp): ni = i + nb * epoch # number integrated batches (since train start) imgs = imgs.to(device).float() / 255.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0 - # Burn-in - if ni <= n_burn: - xi = [0, n_burn] # x interp + # Warmup + if ni <= nw: + xi = [0, nw] # x interp # model.gr = np.interp(ni, xi, [0.0, 1.0]) # giou loss ratio (obj_loss = 1.0 or giou) accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round()) for j, x in enumerate(optimizer.param_groups): From c654d18f0015c396fb01b0a89fe588831e07ae9b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:11:22 -0700 Subject: [PATCH 47/68] Update utils.py --- utils/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index 35be9ea1797..d9d8eb87c6b 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -1034,7 +1034,7 @@ def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max return mosaic -def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir='./'): +def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''): # Plot LR simulating training for full epochs optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals y = [] @@ -1048,7 +1048,7 @@ def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir='./'): plt.xlim(0, epochs) plt.ylim(0) plt.tight_layout() - plt.savefig(os.path.join(save_dir, 'LR.png'), dpi=200) + plt.savefig(Path(save_dir) / 'LR.png', dpi=200) def plot_test_txt(): # from utils.utils import *; plot_test() @@ -1113,7 +1113,7 @@ def plot_study_txt(f='study.txt', x=None): # from utils.utils import *; plot_st plt.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_labels(labels, save_dir= '.'): +def plot_labels(labels, save_dir= ''): # plot dataset labels c, b = labels[:, 0], labels[:, 1:].transpose() # classees, boxes @@ -1134,7 +1134,7 @@ def hist2d(x, y, n=100): ax[2].scatter(b[2], b[3], c=hist2d(b[2], b[3], 90), cmap='jet') ax[2].set_xlabel('width') ax[2].set_ylabel('height') - plt.savefig(os.path.join(save_dir,'labels.png'), dpi=200) + plt.savefig(Path(save_dir) / 'labels.png', dpi=200) plt.close() @@ -1180,7 +1180,7 @@ def plot_results_overlay(start=0, stop=0): # from utils.utils import *; plot_re fig.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= '.'): # from utils.utils import *; plot_results() +def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= ''): # from utils.utils import *; plot_results() # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov5#reproduce-our-training fig, ax = plt.subplots(2, 5, figsize=(12, 6)) ax = ax.ravel() @@ -1190,7 +1190,7 @@ def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= '.'): os.system('rm -rf storage.googleapis.com') files = ['https://storage.googleapis.com/%s/results%g.txt' % (bucket, x) for x in id] else: - files = glob.glob(os.path.join(save_dir,'results*.txt')) + glob.glob('../../Downloads/results*.txt') + files = glob.glob(str(Path(save_dir) / 'results*.txt')) + glob.glob('../../Downloads/results*.txt') for fi, f in enumerate(files): try: results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T @@ -1211,4 +1211,4 @@ def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= '.'): fig.tight_layout() ax[1].legend() - fig.savefig('results.png', dpi=200) + fig.savefig(Path(save_dir) / 'results.png', dpi=200) From cbe39a1dbbf205a0d6eee4bc05c5b3270e6658a0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:14:32 -0700 Subject: [PATCH 48/68] Update utils.py --- utils/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index d9d8eb87c6b..0f58b2b98ef 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -36,12 +36,12 @@ def init_seeds(seed=0): np.random.seed(seed) torch_utils.init_seeds(seed=seed) + def get_latest_run(search_dir = './runs'): - # get path to most recent 'last.pt' in run dirs - # assumes most recently saved 'last.pt' is the desired weights to --resume from + # Return path to most recent 'last.pt' in /runs (i.e. to --resume from) last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) - latest = max(last_list, key = os.path.getctime) - return latest + return max(last_list, key = os.path.getctime) + def check_git_status(): # Suggest 'git pull' if repo is out of date From c3d3e6b77609eff7dd0c097bb04dc870739ee64c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:27:52 -0700 Subject: [PATCH 49/68] Update test.py --- test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test.py b/test.py index f3d0ec65613..18f8e5825ad 100644 --- a/test.py +++ b/test.py @@ -18,8 +18,7 @@ def test(data, verbose=False, model=None, dataloader=None, - fast=False, - save_dir='.', + save_dir='', merge=False): # Initialize/load model and set device @@ -29,7 +28,7 @@ def test(data, device = torch_utils.select_device(opt.device, batch_size=batch_size) # Remove previous - for f in glob.glob(f'{save_dir}/test_batch*.jpg'): + for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')): os.remove(f) # Load model @@ -163,10 +162,11 @@ def test(data, # Plot images if batch_i < 1: - f = os.path.join(save_dir, 'test_batch%g_gt.jpg' % batch_i) # filename - plot_images(img, targets, paths, f, names) # ground truth - f = os.path.join(save_dir,'test_batch%g_pred.jpg' % batch_i) - plot_images(img, output_to_target(output, width, height), paths, f, names) # predictions + + f = Path(save_dir) / ('test_batch%g_gt.jpg' % batch_i) # filename + plot_images(img, targets, paths, str(f), names) # ground truth + f = Path(save_dir) / ('test_batch%g_pred.jpg' % batch_i) + plot_images(img, output_to_target(output, width, height), paths, str(f), names) # predictions # Compute statistics stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy From 22ab1c295f813147a93bed366ddf7768c0975919 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:29:31 -0700 Subject: [PATCH 50/68] Update test.py --- test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test.py b/test.py index 18f8e5825ad..0e1f8292358 100644 --- a/test.py +++ b/test.py @@ -162,7 +162,6 @@ def test(data, # Plot images if batch_i < 1: - f = Path(save_dir) / ('test_batch%g_gt.jpg' % batch_i) # filename plot_images(img, targets, paths, str(f), names) # ground truth f = Path(save_dir) / ('test_batch%g_pred.jpg' % batch_i) From 6b134d93c51c774c09df2c10c228d35fc2201e46 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:58:13 -0700 Subject: [PATCH 51/68] Update train.py --- train.py | 59 +++++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/train.py b/train.py index 34b4950c72d..b704218c7f2 100644 --- a/train.py +++ b/train.py @@ -44,11 +44,8 @@ def train(hyp): - #write all results to the tb log_dir, so all data from one run is together - log_dir = tb_writer.log_dir - - #weights dir unique to each experiment - wdir = os.path.join(log_dir, 'weights') + os.sep # weights dir + log_dir = tb_writer.log_dir # run directory + wdir = str(Path(log_dir) / 'weights') + os.sep # weights directory os.makedirs(wdir, exist_ok=True) last = wdir + 'last.pt' @@ -92,8 +89,8 @@ def train(hyp): else: pg0.append(v) # all else - if hyp['optimizer'] =='adam': - optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) #use default beta2, adjust beta1 for Adam momentum per momentum adjustments in https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR + if hyp['optimizer'] == 'adam': # https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR + optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum else: optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) @@ -148,7 +145,7 @@ def train(hyp): scheduler.last_epoch = start_epoch - 1 # do not move # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 - plot_lr_scheduler(optimizer, scheduler, epochs, save_dir = log_dir) + plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir) # Initialize distributed training if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): @@ -177,11 +174,10 @@ def train(hyp): model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights model.names = data_dict['names'] - #save hyperparamter and training options in run folder - with open(os.path.join(log_dir, 'hyp.yaml'), 'w') as f: + # Save run settings + with open(Path(log_dir) / 'hyp.yaml', 'w') as f: yaml.dump(hyp, f, sort_keys=False) - - with open(os.path.join(log_dir, 'opt.yaml'), 'w') as f: + with open(Path(log_dir) / 'opt.yaml', 'w') as f: yaml.dump(vars(opt), f, sort_keys=False) # Class frequency @@ -189,14 +185,10 @@ def train(hyp): c = torch.tensor(labels[:, 0]) # classes # cf = torch.bincount(c.long(), minlength=nc) + 1. # model._initialize_biases(cf.to(device)) - - #always plot labels to log_dir plot_labels(labels, save_dir=log_dir) - if tb_writer: tb_writer.add_histogram('classes', c, 0) - # Check anchors if not opt.noautoanchor: check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) @@ -284,7 +276,7 @@ def train(hyp): # Plot if ni < 3: - f = os.path.join(log_dir, 'train_batch%g.jpg' % ni) # filename + f = str(Path(log_dir) / ('train_batch%g.jpg' % ni)) # filename result = plot_images(images=imgs, targets=targets, paths=paths, fname=f) if tb_writer and result is not None: tb_writer.add_image(f, result, dataformats='HWC', global_step=epoch) @@ -358,7 +350,7 @@ def train(hyp): # Finish if not opt.evolve: - plot_results(save_dir = log_dir) # save as results.png + plot_results(save_dir=log_dir) # save as results.png print('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) dist.destroy_process_group() if device.type != 'cpu' and torch.cuda.device_count() > 1 else None torch.cuda.empty_cache() @@ -368,14 +360,14 @@ def train(hyp): if __name__ == '__main__': check_git_status() parser = argparse.ArgumentParser() - parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model cfg path[*.yaml]') - parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data cfg path [*.yaml]') - parser.add_argument('--hyp', type=str, default='',help='hyp cfg path [*.yaml].') + parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model.yaml path') + parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path') + parser.add_argument('--hyp', type=str, default='', help='hyp.yaml path (optional)') parser.add_argument('--epochs', type=int, default=300) parser.add_argument('--batch-size', type=int, default=16) - parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes. Assumes square imgs.') + parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes') parser.add_argument('--rect', action='store_true', help='rectangular training') - parser.add_argument('--resume', nargs='?', const = 'get_last', default=False, help='resume training from given path/to/last.pt, or most recent run if blank.') + parser.add_argument('--resume', nargs='?', const = 'get_last', default=False, help='resume from given path/to/last.pt, or most recent run if blank.') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check') @@ -387,20 +379,15 @@ def train(hyp): parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') - opt = parser.parse_args() - - # use given path/to/last.pt or find most recent run if no path given - last = get_latest_run() if opt.resume == 'get_last' else opt.resume + + last = get_latest_run() if opt.resume == 'get_last' else opt.resume # resume from most recent run if last and not opt.weights: print(f'Resuming training from {last}') opt.weights = last if opt.resume and not opt.weights else opt.weights - - opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file - opt.hyp = check_file(opt.hyp) if opt.hyp else '' #check file - + opt.hyp = check_file(opt.hyp) if opt.hyp else '' # check file print(opt) opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 sizes (train, test) device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size) @@ -410,16 +397,10 @@ def train(hyp): # Train if not opt.evolve: tb_writer = SummaryWriter(comment=opt.name) - - #updates hyp defaults from hyp.yaml - if opt.hyp: + if opt.hyp: # update hyps with open(opt.hyp) as f: - updated_hyp = yaml.load(f, Loader=yaml.FullLoader) - hyp.update(updated_hyp) + hyp.update(yaml.load(f, Loader=yaml.FullLoader)) - # Print focal loss if gamma > 0 - if hyp['fl_gamma']: - print('Using FocalLoss(gamma=%g)' % hyp['fl_gamma']) print(f'Beginning training with {hyp}\n\n') print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') From abb024de04813208fbcf8ae175775caf6bfe123a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 16:59:06 -0700 Subject: [PATCH 52/68] Delete new_hyp.yaml --- new_hyp.yaml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 new_hyp.yaml diff --git a/new_hyp.yaml b/new_hyp.yaml deleted file mode 100644 index 498a4e12e54..00000000000 --- a/new_hyp.yaml +++ /dev/null @@ -1,19 +0,0 @@ -optimizer: 'adam' -anchor_t: 10.0 -cls: 0.58 -cls_pw: 1.0 -degrees: 20.0 -fl_gamma: 1.0 -giou: 0.15 -hsv_h: 0.014 -hsv_s: 0.68 -hsv_v: 0.36 -iou_t: 0.2 -lr0: 0.001 -momentum: 0.900 -obj: 1.0 -obj_pw: 1.0 -scale: 0.5 -shear: 0.0 -translate: 0.0 -weight_decay: 0.000625 \ No newline at end of file From bf6f41567ae4e2953bca7a7042d8ae78db82a3bc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Jul 2020 17:21:00 -0700 Subject: [PATCH 53/68] hyperparameter printout update --- test.py | 1 - train.py | 19 +++++++++---------- utils/utils.py | 11 ++++++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/test.py b/test.py index 1638a5ee403..ef97d26c194 100644 --- a/test.py +++ b/test.py @@ -19,7 +19,6 @@ def test(data, dataloader=None, save_dir='', merge=False): - # Initialize/load model and set device training = model is not None if training: # called by train.py diff --git a/train.py b/train.py index c4d4db08d64..f543da27a6a 100644 --- a/train.py +++ b/train.py @@ -20,9 +20,8 @@ print('Apex recommended for faster mixed precision training: https://github.com/NVIDIA/apex') mixed_precision = False # not installed - # Hyperparameters -hyp = {'optimizer': 'SGD', # ['adam', 'SGD', None] if none, default is SGD +hyp = {'optimizer': 'SGD', # ['adam', 'SGD', None] if none, default is SGD 'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) 'momentum': 0.937, # SGD momentum/Adam beta1 'weight_decay': 5e-4, # optimizer weight decay @@ -44,6 +43,7 @@ def train(hyp): + print(f'Hyperparameters {hyp}') log_dir = tb_writer.log_dir # run directory wdir = str(Path(log_dir) / 'weights') + os.sep # weights directory @@ -90,7 +90,7 @@ def train(hyp): pg0.append(v) # all else if hyp['optimizer'] == 'adam': # https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR - optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum + optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum else: optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) @@ -176,7 +176,7 @@ def train(hyp): yaml.dump(hyp, f, sort_keys=False) with open(Path(log_dir) / 'opt.yaml', 'w') as f: yaml.dump(vars(opt), f, sort_keys=False) - + # Class frequency labels = np.concatenate(dataset.labels, 0) c = torch.tensor(labels[:, 0]) # classes @@ -365,7 +365,8 @@ def train(hyp): parser.add_argument('--batch-size', type=int, default=16) parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes') parser.add_argument('--rect', action='store_true', help='rectangular training') - parser.add_argument('--resume', nargs='?', const = 'get_last', default=False, help='resume from given path/to/last.pt, or most recent run if blank.') + parser.add_argument('--resume', nargs='?', const='get_last', default=False, + help='resume from given path/to/last.pt, or most recent run if blank.') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check') @@ -378,14 +379,14 @@ def train(hyp): parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') opt = parser.parse_args() - + last = get_latest_run() if opt.resume == 'get_last' else opt.resume # resume from most recent run if last and not opt.weights: print(f'Resuming training from {last}') opt.weights = last if opt.resume and not opt.weights else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file - opt.hyp = check_file(opt.hyp) if opt.hyp else '' # check file + opt.hyp = check_file(opt.hyp) if opt.hyp else '' # check file print(opt) opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 sizes (train, test) device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size) @@ -394,14 +395,12 @@ def train(hyp): # Train if not opt.evolve: + print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') tb_writer = SummaryWriter(comment=opt.name) if opt.hyp: # update hyps with open(opt.hyp) as f: hyp.update(yaml.load(f, Loader=yaml.FullLoader)) - print(f'Beginning training with {hyp}\n\n') - print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') - train(hyp) # Evolve hyperparameters (optional) diff --git a/utils/utils.py b/utils/utils.py index 47767940347..2de29171198 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -37,10 +37,10 @@ def init_seeds(seed=0): torch_utils.init_seeds(seed=seed) -def get_latest_run(search_dir = './runs'): +def get_latest_run(search_dir='./runs'): # Return path to most recent 'last.pt' in /runs (i.e. to --resume from) - last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) - return max(last_list, key = os.path.getctime) + last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) + return max(last_list, key=os.path.getctime) def check_git_status(): @@ -1113,7 +1113,7 @@ def plot_study_txt(f='study.txt', x=None): # from utils.utils import *; plot_st plt.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_labels(labels, save_dir= ''): +def plot_labels(labels, save_dir=''): # plot dataset labels c, b = labels[:, 0], labels[:, 1:].transpose() # classees, boxes @@ -1180,7 +1180,8 @@ def plot_results_overlay(start=0, stop=0): # from utils.utils import *; plot_re fig.savefig(f.replace('.txt', '.png'), dpi=200) -def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir= ''): # from utils.utils import *; plot_results() +def plot_results(start=0, stop=0, bucket='', id=(), labels=(), + save_dir=''): # from utils.utils import *; plot_results() # Plot training 'results*.txt' as seen in https://github.com/ultralytics/yolov5#reproduce-our-training fig, ax = plt.subplots(2, 5, figsize=(12, 6)) ax = ax.ravel() From 94342acbe18fd6246183bc833390a6184b8831af Mon Sep 17 00:00:00 2001 From: lorenzomammana Date: Thu, 9 Jul 2020 11:52:12 +0200 Subject: [PATCH 54/68] Handle multiple datasets --- utils/datasets.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index 1ebd709482f..d96ae41c616 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -280,16 +280,28 @@ class LoadImagesAndLabels(Dataset): # for training/testing def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False, cache_images=False, single_cls=False, stride=32, pad=0.0): try: - path = str(Path(path)) # os-agnostic - parent = str(Path(path).parent) + os.sep - if os.path.isfile(path): # file - with open(path, 'r') as f: - f = f.read().splitlines() - f = [x.replace('./', parent) if x.startswith('./') else x for x in f] # local to global path - elif os.path.isdir(path): # folder - f = glob.iglob(path + os.sep + '*.*') + if type(path) is list: + # Multiple datasets handler + f = [] + for subpath in path: + with open(subpath, 'r') as t: + subpath = str(Path(subpath)) # os-agnostic + parent = str(Path(subpath).parent) + os.sep + t = t.read().splitlines() + t = [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path + f += t + path = str(Path(path[0])) # from now on treat multiple datasets as single else: - raise Exception('%s does not exist' % path) + path = str(Path(path)) # os-agnostic + parent = str(Path(path).parent) + os.sep + if os.path.isfile(path): # file + with open(path, 'r') as f: + f = f.read().splitlines() + f = [x.replace('./', parent) if x.startswith('./') else x for x in f] # local to global path + elif os.path.isdir(path): # folder + f = glob.iglob(path + os.sep + '*.*') + else: + raise Exception('%s does not exist' % path) self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] except: raise Exception('Error loading data from %s. See %s' % (path, help_url)) From 54a9e4f8764aae695e42d62869563f2a9bbe8ec4 Mon Sep 17 00:00:00 2001 From: lorenzomammana Date: Thu, 9 Jul 2020 19:39:28 +0200 Subject: [PATCH 55/68] Refactor code to reduce duplication --- utils/datasets.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index d96ae41c616..d3bc5747df9 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -280,32 +280,31 @@ class LoadImagesAndLabels(Dataset): # for training/testing def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False, cache_images=False, single_cls=False, stride=32, pad=0.0): try: - if type(path) is list: - # Multiple datasets handler - f = [] - for subpath in path: + f = [] + for subpath in path if isinstance(path, list) else [path]: + subpath = str(Path(subpath)) # os-agnostic + parent = str(Path(subpath).parent) + os.sep + if os.path.isfile(subpath): # file with open(subpath, 'r') as t: - subpath = str(Path(subpath)) # os-agnostic - parent = str(Path(subpath).parent) + os.sep t = t.read().splitlines() t = [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path f += t - path = str(Path(path[0])) # from now on treat multiple datasets as single - else: - path = str(Path(path)) # os-agnostic - parent = str(Path(path).parent) + os.sep - if os.path.isfile(path): # file - with open(path, 'r') as f: - f = f.read().splitlines() - f = [x.replace('./', parent) if x.startswith('./') else x for x in f] # local to global path - elif os.path.isdir(path): # folder - f = glob.iglob(path + os.sep + '*.*') + elif os.path.isdir(subpath): # folder + f = glob.iglob(subpath + os.sep + '*.*') + # Maybe change this to f += glob.glob, this should allow handling also multiple folders else: - raise Exception('%s does not exist' % path) + raise Exception('%s does not exist' % subpath) self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] except: + # Maybe avoid handling bare exceptions raise Exception('Error loading data from %s. See %s' % (path, help_url)) + # Still need to do this for compatibility with the .npy and shape file saves + if isinstance(path, list): + path = str(Path(path[0])) + else: + path = str(Path(path)) + n = len(self.img_files) assert n > 0, 'No images found in %s. See %s' % (path, help_url) bi = np.floor(np.arange(n) / batch_size).astype(np.int) # batch index From f9bab6b12761015b8bda6625c9bb8ec7ac35ec78 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 13:36:23 -0700 Subject: [PATCH 56/68] Update datasets.py --- utils/datasets.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index d3bc5747df9..0d8852cc97e 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -295,16 +295,11 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r else: raise Exception('%s does not exist' % subpath) self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] + path = subpath except: # Maybe avoid handling bare exceptions raise Exception('Error loading data from %s. See %s' % (path, help_url)) - # Still need to do this for compatibility with the .npy and shape file saves - if isinstance(path, list): - path = str(Path(path[0])) - else: - path = str(Path(path)) - n = len(self.img_files) assert n > 0, 'No images found in %s. See %s' % (path, help_url) bi = np.floor(np.arange(n) / batch_size).astype(np.int) # batch index From 47bf1730fa9217a2682ef727347520c4e3cba98b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 13:45:55 -0700 Subject: [PATCH 57/68] Update datasets.py --- utils/datasets.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index 0d8852cc97e..bd42bc091fa 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -281,21 +281,19 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r cache_images=False, single_cls=False, stride=32, pad=0.0): try: f = [] - for subpath in path if isinstance(path, list) else [path]: - subpath = str(Path(subpath)) # os-agnostic - parent = str(Path(subpath).parent) + os.sep - if os.path.isfile(subpath): # file - with open(subpath, 'r') as t: + for p in path if isinstance(path, list) else [path]: + p = str(Path(p)) # os-agnostic + parent = str(Path(p).parent) + os.sep + if os.path.isfile(p): # file + with open(p, 'r') as t: t = t.read().splitlines() - t = [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path - f += t - elif os.path.isdir(subpath): # folder - f = glob.iglob(subpath + os.sep + '*.*') - # Maybe change this to f += glob.glob, this should allow handling also multiple folders + f += [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path + elif os.path.isdir(p): # folder + f += glob.iglob(p + os.sep + '*.*') else: - raise Exception('%s does not exist' % subpath) + raise Exception('%s does not exist' % p) + path = p # *.npy dir self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] - path = subpath except: # Maybe avoid handling bare exceptions raise Exception('Error loading data from %s. See %s' % (path, help_url)) From 13a09fa68a073dd6504e1f3fd66b7176e5995cf9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 13:47:20 -0700 Subject: [PATCH 58/68] Update datasets.py --- utils/datasets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/datasets.py b/utils/datasets.py index bd42bc091fa..c8619f9e968 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -295,7 +295,6 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r path = p # *.npy dir self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] except: - # Maybe avoid handling bare exceptions raise Exception('Error loading data from %s. See %s' % (path, help_url)) n = len(self.img_files) From 9d631408a2f343ca2f1ebf5817360609b6eb4a24 Mon Sep 17 00:00:00 2001 From: Alex Stoken Date: Thu, 9 Jul 2020 16:18:55 -0500 Subject: [PATCH 59/68] Move hyp and opt yaml save to top of train() Fixes bug where scaled values were saved in hyp.yaml, which would cause continuity issues with --resume --- train.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/train.py b/train.py index f543da27a6a..be931eaac79 100644 --- a/train.py +++ b/train.py @@ -52,6 +52,12 @@ def train(hyp): best = wdir + 'best.pt' results_file = log_dir + os.sep + 'results.txt' + # Save run settings + with open(Path(log_dir) / 'hyp.yaml', 'w') as f: + yaml.dump(hyp, f, sort_keys=False) + with open(Path(log_dir) / 'opt.yaml', 'w') as f: + yaml.dump(vars(opt), f, sort_keys=False) + epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 weights = opt.weights # initial training weights @@ -171,12 +177,6 @@ def train(hyp): model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights model.names = data_dict['names'] - # Save run settings - with open(Path(log_dir) / 'hyp.yaml', 'w') as f: - yaml.dump(hyp, f, sort_keys=False) - with open(Path(log_dir) / 'opt.yaml', 'w') as f: - yaml.dump(vars(opt), f, sort_keys=False) - # Class frequency labels = np.concatenate(dataset.labels, 0) c = torch.tensor(labels[:, 0]) # classes From 24c5a941f06e6c85ae6e69f4ee247124675a2a07 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 15:09:06 -0700 Subject: [PATCH 60/68] --resume EMA fix #292 --- train.py | 4 ++-- utils/torch_utils.py | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/train.py b/train.py index be931eaac79..77e090f5eb4 100644 --- a/train.py +++ b/train.py @@ -163,6 +163,7 @@ def train(hyp): dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt, hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect) mlc = np.concatenate(dataset.labels, 0)[:, 0].max() # max label class + nb = len(dataloader) # number of batches assert mlc < nc, 'Label class %g exceeds nc=%g in %s. Correct your labels or your model.' % (mlc, nc, opt.cfg) # Testloader @@ -191,11 +192,10 @@ def train(hyp): check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) # Exponential moving average - ema = torch_utils.ModelEMA(model) + ema = torch_utils.ModelEMA(model, updates=start_epoch * nb / accumulate) # Start training t0 = time.time() - nb = len(dataloader) # number of batches nw = max(3 * nb, 1e3) # number of warmup iterations, max(3 epochs, 1k iterations) maps = np.zeros(nc) # mAP per class results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification' diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 6baa9d5061e..59f9268d260 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -191,15 +191,11 @@ class ModelEMA: I've tested with the sequence in my own train.py for torch.DataParallel, apex.DDP, and single-GPU. """ - def __init__(self, model, decay=0.9999, device=''): + def __init__(self, model, decay=0.9999, updates=0): # Create EMA - self.ema = deepcopy(model.module if is_parallel(model) else model) # FP32 EMA - self.ema.eval() - self.updates = 0 # number of EMA updates + self.ema = deepcopy(model.module if is_parallel(model) else model).eval() # FP32 EMA + self.updates = updates # number of EMA updates self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs) - self.device = device # perform ema on different device from model if set - if device: - self.ema.to(device) for p in self.ema.parameters(): p.requires_grad_(False) From 72d5b58b9a8ccc42943576f25441035d73968f15 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 15:16:57 -0700 Subject: [PATCH 61/68] disable LR plot to suppress warning message --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 77e090f5eb4..39d23928f0d 100644 --- a/train.py +++ b/train.py @@ -108,7 +108,7 @@ def train(hyp): # Scheduler https://arxiv.org/pdf/1812.01187.pdf lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) - plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir) + # plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir) # Load Model google_utils.attempt_download(weights) From 603ea0bfdc9b0db3b367fbe7cac5ba6f9a50a49f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 15:58:07 -0700 Subject: [PATCH 62/68] update log_dir to runs/exp #107 --- train.py | 2 +- utils/utils.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index 39d23928f0d..4943336e31e 100644 --- a/train.py +++ b/train.py @@ -396,7 +396,7 @@ def train(hyp): # Train if not opt.evolve: print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') - tb_writer = SummaryWriter(comment=opt.name) + tb_writer = SummaryWriter(log_dir=increment_dir('runs/exp', opt.name)) if opt.hyp: # update hyps with open(opt.hyp) as f: hyp.update(yaml.load(f, Loader=yaml.FullLoader)) diff --git a/utils/utils.py b/utils/utils.py index 2de29171198..486dca6463f 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -904,6 +904,16 @@ def output_to_target(output, width, height): return np.array(targets) +def increment_dir(dir, comment=''): + # Increments a directory runs/exp1 --> runs/exp2_comment + n = 0 # number + d = sorted(glob.glob(dir + '*')) # directories + if len(d): + d = d[-1].replace(dir, '') + n = int(d[:d.find('_')]) + 1 # increment + return dir + str(n) + ('_' + comment if comment else '') + + # Plotting functions --------------------------------------------------------------------------------------------------- def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5): # https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy From 2110f5ee89c6a9475f3fe760a6abd36ff041deab Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 16:03:10 -0700 Subject: [PATCH 63/68] update log_dir to runs/exp #107 --- utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.py b/utils/utils.py index 486dca6463f..439a4e03469 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -910,7 +910,7 @@ def increment_dir(dir, comment=''): d = sorted(glob.glob(dir + '*')) # directories if len(d): d = d[-1].replace(dir, '') - n = int(d[:d.find('_')]) + 1 # increment + n = int(d[:d.find('_')] if '_' in d else d) + 1 # increment return dir + str(n) + ('_' + comment if comment else '') From dd33d2ab77bb65845bb12a99fba81fabc03a457d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 16:28:20 -0700 Subject: [PATCH 64/68] Update datasets.py --- utils/datasets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index c8619f9e968..9777e5c61b1 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -294,8 +294,8 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r raise Exception('%s does not exist' % p) path = p # *.npy dir self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] - except: - raise Exception('Error loading data from %s. See %s' % (path, help_url)) + except Exception as e: + raise Exception('Error loading data from %s: %s\nSee %s' % (path, e, help_url)) n = len(self.img_files) assert n > 0, 'No images found in %s. See %s' % (path, help_url) From cb527d3af93bf30b5c3d6ca2a01d29975b325ba3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 17:03:12 -0700 Subject: [PATCH 65/68] new nc=len(names) check --- train.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index 4943336e31e..6bfa4286b1a 100644 --- a/train.py +++ b/train.py @@ -57,7 +57,7 @@ def train(hyp): yaml.dump(hyp, f, sort_keys=False) with open(Path(log_dir) / 'opt.yaml', 'w') as f: yaml.dump(vars(opt), f, sort_keys=False) - + epochs = opt.epochs # 300 batch_size = opt.batch_size # 64 weights = opt.weights # initial training weights @@ -68,7 +68,8 @@ def train(hyp): data_dict = yaml.load(f, Loader=yaml.FullLoader) # model dict train_path = data_dict['train'] test_path = data_dict['val'] - nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes + nc, names = (1, ['item']) if opt.single_cls else (int(data_dict['nc']), data_dict['names']) # number classes, names + assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check # Remove previous results for f in glob.glob('*_batch*.jpg') + glob.glob(results_file): From e16e9e43e1b4b8f9b9819fe81d537c00ec6d606e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 17:10:43 -0700 Subject: [PATCH 66/68] new nc=len(names) check --- train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index 6bfa4286b1a..bba0883ca8d 100644 --- a/train.py +++ b/train.py @@ -76,7 +76,7 @@ def train(hyp): os.remove(f) # Create model - model = Model(opt.cfg, nc=data_dict['nc']).to(device) + model = Model(opt.cfg, nc=nc).to(device) # Image sizes gs = int(max(model.stride)) # grid size (max stride) @@ -177,7 +177,7 @@ def train(hyp): model.hyp = hyp # attach hyperparameters to model model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights - model.names = data_dict['names'] + model.names = names # Class frequency labels = np.concatenate(dataset.labels, 0) From f310ca3bff9ff8880d2ce3b92c12ba99376f3625 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 17:36:26 -0700 Subject: [PATCH 67/68] Update issue templates --- .github/ISSUE_TEMPLATE/-question.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/-question.md diff --git a/.github/ISSUE_TEMPLATE/-question.md b/.github/ISSUE_TEMPLATE/-question.md new file mode 100644 index 00000000000..2c22aea70a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-question.md @@ -0,0 +1,13 @@ +--- +name: "❓Question" +about: Ask a general question +title: '' +labels: question +assignees: '' + +--- + +## ❔Question + + +## Additional context From 520f5de6f0febf30dd3545793f528c3478c18299 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 9 Jul 2020 20:07:16 -0700 Subject: [PATCH 68/68] Label caching foundational re-write #306 --- utils/datasets.py | 105 ++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index 338607bd0b5..406744a8465 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -26,6 +26,11 @@ break +def get_hash(files): + # Returns a single hash value of a list of files + return sum(os.path.getsize(f) for f in files) + + def exif_size(img): # Returns exif-corrected PIL size s = img.size # (width, height) @@ -280,7 +285,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False, cache_images=False, single_cls=False, stride=32, pad=0.0): try: - f = [] + f = [] # image files for p in path if isinstance(path, list) else [path]: p = str(Path(p)) # os-agnostic parent = str(Path(p).parent) + os.sep @@ -292,7 +297,6 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r f += glob.iglob(p + os.sep + '*.*') else: raise Exception('%s does not exist' % p) - path = p # *.npy dir self.img_files = [x.replace('/', os.sep) for x in f if os.path.splitext(x)[-1].lower() in img_formats] except Exception as e: raise Exception('Error loading data from %s: %s\nSee %s' % (path, e, help_url)) @@ -314,20 +318,22 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r self.stride = stride # Define labels - self.label_files = [x.replace('images', 'labels').replace(os.path.splitext(x)[-1], '.txt') - for x in self.img_files] - - # Read image shapes (wh) - sp = path.replace('.txt', '') + '.shapes' # shapefile path - try: - with open(sp, 'r') as f: # read existing shapefile - s = [x.split() for x in f.read().splitlines()] - assert len(s) == n, 'Shapefile out of sync' - except: - s = [exif_size(Image.open(f)) for f in tqdm(self.img_files, desc='Reading image shapes')] - np.savetxt(sp, s, fmt='%g') # overwrites existing (if any) + self.label_files = [x.replace('images', 'labels').replace(os.path.splitext(x)[-1], '.txt') for x in + self.img_files] + + # Check cache + cache_path = str(Path(self.label_files[0]).parent) + '.cache' # cached labels + if os.path.isfile(cache_path): + cache = torch.load(cache_path) # load + if cache['hash'] != get_hash(self.label_files + self.img_files): # dataset changed + cache = self.cache_labels(cache_path) # re-cache + else: + cache = self.cache_labels(cache_path) # cache - self.shapes = np.array(s, dtype=np.float64) + # Get labels + labels, shapes = zip(*[cache[x] for x in self.img_files]) + self.shapes = np.array(shapes, dtype=np.float64) + self.labels = list(labels) # Rectangular Training https://github.com/ultralytics/yolov3/issues/232 if self.rect: @@ -353,33 +359,11 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int) * stride # Cache labels - self.imgs = [None] * n - self.labels = [np.zeros((0, 5), dtype=np.float32)] * n create_datasubset, extract_bounding_boxes, labels_loaded = False, False, False nm, nf, ne, ns, nd = 0, 0, 0, 0, 0 # number missing, found, empty, datasubset, duplicate - np_labels_path = str(Path(self.label_files[0]).parent) + '.npy' # saved labels in *.npy file - if os.path.isfile(np_labels_path): - s = np_labels_path # print string - x = np.load(np_labels_path, allow_pickle=True) - if len(x) == n: - self.labels = x - labels_loaded = True - else: - s = path.replace('images', 'labels') - pbar = tqdm(self.label_files) for i, file in enumerate(pbar): - if labels_loaded: - l = self.labels[i] - # np.savetxt(file, l, '%g') # save *.txt from *.npy file - else: - try: - with open(file, 'r') as f: - l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) - except: - nm += 1 # print('missing labels for image %s' % self.img_files[i]) # file missing - continue - + l = self.labels[i] # label if l.shape[0]: assert l.shape[1] == 5, '> 5 label columns: %s' % file assert (l >= 0).all(), 'negative labels: %s' % file @@ -425,15 +409,13 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r ne += 1 # print('empty labels for image %s' % self.img_files[i]) # file empty # os.system("rm '%s' '%s'" % (self.img_files[i], self.label_files[i])) # remove - pbar.desc = 'Caching labels %s (%g found, %g missing, %g empty, %g duplicate, for %g images)' % ( - s, nf, nm, ne, nd, n) - assert nf > 0 or n == 20288, 'No labels found in %s. See %s' % (os.path.dirname(file) + os.sep, help_url) - if not labels_loaded and n > 1000: - print('Saving labels to %s for faster future loading' % np_labels_path) - np.save(np_labels_path, self.labels) # save for next time + pbar.desc = 'Scanning labels %s (%g found, %g missing, %g empty, %g duplicate, for %g images)' % ( + cache_path, nf, nm, ne, nd, n) + assert nf > 0, 'No labels found in %s. See %s' % (os.path.dirname(file) + os.sep, help_url) # Cache images into memory for faster training (WARNING: large datasets may exceed system RAM) - if cache_images: # if training + self.imgs = [None] * n + if cache_images: gb = 0 # Gigabytes of cached images pbar = tqdm(range(len(self.img_files)), desc='Caching images') self.img_hw0, self.img_hw = [None] * n, [None] * n @@ -442,15 +424,30 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r gb += self.imgs[i].nbytes pbar.desc = 'Caching images (%.1fGB)' % (gb / 1E9) - # Detect corrupted images https://medium.com/joelthchao/programmatically-detect-corrupted-image-8c1b2006c3d3 - detect_corrupted_images = False - if detect_corrupted_images: - from skimage import io # conda install -c conda-forge scikit-image - for file in tqdm(self.img_files, desc='Detecting corrupted images'): - try: - _ = io.imread(file) - except: - print('Corrupted image detected: %s' % file) + def cache_labels(self, path='labels.cache'): + # Cache dataset labels, check images and read shapes + x = {} # dict + pbar = tqdm(zip(self.img_files, self.label_files), desc='Scanning images', total=len(self.img_files)) + for (img, label) in pbar: + try: + l = [] + image = Image.open(img) + image.verify() # PIL verify + # _ = io.imread(img) # skimage verify (from skimage import io) + shape = exif_size(image) # image size + if os.path.isfile(label): + with open(label, 'r') as f: + l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) # labels + if len(l) == 0: + l = np.zeros((0, 5), dtype=np.float32) + x[img] = [l, shape] + except Exception as e: + x[img] = None + print('WARNING: %s: %s' % (img, e)) + + x['hash'] = get_hash(self.label_files + self.img_files) + torch.save(x, path) # save for next time + return x def __len__(self): return len(self.img_files)