glenn-jocher commited on
Commit
e78aeac
1 Parent(s): 4103ce9

Evolve in CSV format (#4307)

Browse files

* Update evolution to CSV format

* Update

* Update

* Update

* Update

* Update

* reset args

* reset args

* reset args

* plot_results() fix

* Cleanup

* Cleanup2

Files changed (6) hide show
  1. .dockerignore +1 -1
  2. .gitignore +0 -1
  3. train.py +18 -14
  4. utils/general.py +29 -21
  5. utils/loggers/__init__.py +2 -3
  6. utils/plots.py +25 -25
.dockerignore CHANGED
@@ -8,7 +8,7 @@ coco
8
  storage.googleapis.com
9
 
10
  data/samples/*
11
- **/results*.txt
12
  *.jpg
13
 
14
  # Neural Network weights -----------------------------------------------------------------------------------------------
 
8
  storage.googleapis.com
9
 
10
  data/samples/*
11
+ **/results*.csv
12
  *.jpg
13
 
14
  # Neural Network weights -----------------------------------------------------------------------------------------------
.gitignore CHANGED
@@ -30,7 +30,6 @@ data/*
30
  !data/images/bus.jpg
31
  !data/*.sh
32
 
33
- results*.txt
34
  results*.csv
35
 
36
  # Datasets -------------------------------------------------------------------------------------------------------------
 
30
  !data/images/bus.jpg
31
  !data/*.sh
32
 
 
33
  results*.csv
34
 
35
  # Datasets -------------------------------------------------------------------------------------------------------------
train.py CHANGED
@@ -37,7 +37,7 @@ from utils.general import labels_to_class_weights, increment_path, labels_to_ima
37
  check_requirements, print_mutation, set_logging, one_cycle, colorstr, methods
38
  from utils.downloads import attempt_download
39
  from utils.loss import ComputeLoss
40
- from utils.plots import plot_labels, plot_evolution
41
  from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, de_parallel
42
  from utils.loggers.wandb.wandb_utils import check_wandb_resume
43
  from utils.metrics import fitness
@@ -367,7 +367,8 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary
367
  fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
368
  if fi > best_fitness:
369
  best_fitness = fi
370
- callbacks.on_fit_epoch_end(mloss, results, lr, epoch, best_fitness, fi)
 
371
 
372
  # Save model
373
  if (not nosave) or (final_epoch and not evolve): # if save
@@ -464,7 +465,7 @@ def main(opt):
464
  check_requirements(requirements=FILE.parent / 'requirements.txt', exclude=['thop'])
465
 
466
  # Resume
467
- if opt.resume and not check_wandb_resume(opt): # resume an interrupted run
468
  ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run() # specified or most recent path
469
  assert os.path.isfile(ckpt), 'ERROR: --resume checkpoint does not exist'
470
  with open(Path(ckpt).parent.parent / 'opt.yaml') as f:
@@ -474,8 +475,10 @@ def main(opt):
474
  else:
475
  opt.data, opt.cfg, opt.hyp = check_file(opt.data), check_file(opt.cfg), check_file(opt.hyp) # check files
476
  assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
477
- opt.name = 'evolve' if opt.evolve else opt.name
478
- opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok or opt.evolve))
 
 
479
 
480
  # DDP mode
481
  device = select_device(opt.device, batch_size=opt.batch_size)
@@ -533,17 +536,17 @@ def main(opt):
533
  hyp = yaml.safe_load(f) # load hyps dict
534
  if 'anchors' not in hyp: # anchors commented in hyp.yaml
535
  hyp['anchors'] = 3
536
- opt.noval, opt.nosave = True, True # only val/save final epoch
537
  # ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
538
- yaml_file = Path(opt.save_dir) / 'hyp_evolved.yaml' # save best result here
539
  if opt.bucket:
540
- os.system(f'gsutil cp gs://{opt.bucket}/evolve.txt .') # download evolve.txt if exists
541
 
542
  for _ in range(opt.evolve): # generations to evolve
543
- if Path('evolve.txt').exists(): # if evolve.txt exists: select best hyps and mutate
544
  # Select parent(s)
545
  parent = 'single' # parent selection method: 'single' or 'weighted'
546
- x = np.loadtxt('evolve.txt', ndmin=2)
547
  n = min(5, len(x)) # number of previous results to consider
548
  x = x[np.argsort(-fitness(x))][:n] # top n mutations
549
  w = fitness(x) - fitness(x).min() + 1E-6 # weights (sum > 0)
@@ -575,12 +578,13 @@ def main(opt):
575
  results = train(hyp.copy(), opt, device)
576
 
577
  # Write mutation results
578
- print_mutation(hyp.copy(), results, yaml_file, opt.bucket)
579
 
580
  # Plot results
581
- plot_evolution(yaml_file)
582
- print(f'Hyperparameter evolution complete. Best results saved as: {yaml_file}\n'
583
- f'Command to train a new model with these hyperparameters: $ python train.py --hyp {yaml_file}')
 
584
 
585
 
586
  def run(**kwargs):
 
37
  check_requirements, print_mutation, set_logging, one_cycle, colorstr, methods
38
  from utils.downloads import attempt_download
39
  from utils.loss import ComputeLoss
40
+ from utils.plots import plot_labels, plot_evolve
41
  from utils.torch_utils import ModelEMA, select_device, intersect_dicts, torch_distributed_zero_first, de_parallel
42
  from utils.loggers.wandb.wandb_utils import check_wandb_resume
43
  from utils.metrics import fitness
 
367
  fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
368
  if fi > best_fitness:
369
  best_fitness = fi
370
+ log_vals = list(mloss) + list(results) + lr
371
+ callbacks.on_fit_epoch_end(log_vals, epoch, best_fitness, fi)
372
 
373
  # Save model
374
  if (not nosave) or (final_epoch and not evolve): # if save
 
465
  check_requirements(requirements=FILE.parent / 'requirements.txt', exclude=['thop'])
466
 
467
  # Resume
468
+ if opt.resume and not check_wandb_resume(opt) and not opt.evolve: # resume an interrupted run
469
  ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run() # specified or most recent path
470
  assert os.path.isfile(ckpt), 'ERROR: --resume checkpoint does not exist'
471
  with open(Path(ckpt).parent.parent / 'opt.yaml') as f:
 
475
  else:
476
  opt.data, opt.cfg, opt.hyp = check_file(opt.data), check_file(opt.cfg), check_file(opt.hyp) # check files
477
  assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
478
+ if opt.evolve:
479
+ opt.project = 'runs/evolve'
480
+ opt.exist_ok = opt.resume
481
+ opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
482
 
483
  # DDP mode
484
  device = select_device(opt.device, batch_size=opt.batch_size)
 
536
  hyp = yaml.safe_load(f) # load hyps dict
537
  if 'anchors' not in hyp: # anchors commented in hyp.yaml
538
  hyp['anchors'] = 3
539
+ opt.noval, opt.nosave, save_dir = True, True, Path(opt.save_dir) # only val/save final epoch
540
  # ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
541
+ evolve_yaml, evolve_csv = save_dir / 'hyp_evolve.yaml', save_dir / 'evolve.csv'
542
  if opt.bucket:
543
+ os.system(f'gsutil cp gs://{opt.bucket}/evolve.csv {save_dir}') # download evolve.csv if exists
544
 
545
  for _ in range(opt.evolve): # generations to evolve
546
+ if evolve_csv.exists(): # if evolve.csv exists: select best hyps and mutate
547
  # Select parent(s)
548
  parent = 'single' # parent selection method: 'single' or 'weighted'
549
+ x = np.loadtxt(evolve_csv, ndmin=2, delimiter=',', skiprows=1)
550
  n = min(5, len(x)) # number of previous results to consider
551
  x = x[np.argsort(-fitness(x))][:n] # top n mutations
552
  w = fitness(x) - fitness(x).min() + 1E-6 # weights (sum > 0)
 
578
  results = train(hyp.copy(), opt, device)
579
 
580
  # Write mutation results
581
+ print_mutation(results, hyp.copy(), save_dir, opt.bucket)
582
 
583
  # Plot results
584
+ plot_evolve(evolve_csv)
585
+ print(f'Hyperparameter evolution finished\n'
586
+ f"Results saved to {colorstr('bold', save_dir)}"
587
+ f'Use best hyperparameters example: $ python train.py --hyp {evolve_yaml}')
588
 
589
 
590
  def run(**kwargs):
utils/general.py CHANGED
@@ -615,35 +615,43 @@ def strip_optimizer(f='best.pt', s=''): # from utils.general import *; strip_op
615
  print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")
616
 
617
 
618
- def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
619
- # Print mutation results to evolve.txt (for use with train.py --evolve)
620
- a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys
621
- b = '%10.3g' * len(hyp) % tuple(hyp.values()) # hyperparam values
622
- c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
623
- print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))
 
624
 
 
625
  if bucket:
626
- url = 'gs://%s/evolve.txt' % bucket
627
- if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):
628
- os.system('gsutil cp %s .' % url) # download evolve.txt if larger than local
 
 
 
 
 
629
 
630
- with open('evolve.txt', 'a') as f: # append result
631
- f.write(c + b + '\n')
632
- x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0) # load unique rows
633
- x = x[np.argsort(-fitness(x))] # sort
634
- np.savetxt('evolve.txt', x, '%10.3g') # save sort by fitness
635
 
636
  # Save yaml
637
- for i, k in enumerate(hyp.keys()):
638
- hyp[k] = float(x[0, i + 7])
639
- with open(yaml_file, 'w') as f:
640
- results = tuple(x[0, :7])
641
- c = '%10.4g' * len(results) % results # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
642
- f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')
 
 
 
643
  yaml.safe_dump(hyp, f, sort_keys=False)
644
 
645
  if bucket:
646
- os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket)) # upload
647
 
648
 
649
  def apply_classifier(x, model, img, im0):
 
615
  print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")
616
 
617
 
618
+ def print_mutation(results, hyp, save_dir, bucket):
619
+ evolve_csv, results_csv, evolve_yaml = save_dir / 'evolve.csv', save_dir / 'results.csv', save_dir / 'hyp_evolve.yaml'
620
+ keys = ('metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',
621
+ 'val/box_loss', 'val/obj_loss', 'val/cls_loss') + tuple(hyp.keys()) # [results + hyps]
622
+ keys = tuple(x.strip() for x in keys)
623
+ vals = results + tuple(hyp.values())
624
+ n = len(keys)
625
 
626
+ # Download (optional)
627
  if bucket:
628
+ url = f'gs://{bucket}/evolve.csv'
629
+ if gsutil_getsize(url) > (os.path.getsize(evolve_csv) if os.path.exists(evolve_csv) else 0):
630
+ os.system(f'gsutil cp {url} {save_dir}') # download evolve.csv if larger than local
631
+
632
+ # Log to evolve.csv
633
+ s = '' if evolve_csv.exists() else (('%20s,' * n % keys).rstrip(',') + '\n') # add header
634
+ with open(evolve_csv, 'a') as f:
635
+ f.write(s + ('%20.5g,' * n % vals).rstrip(',') + '\n')
636
 
637
+ # Print to screen
638
+ print(colorstr('evolve: ') + ', '.join(f'{x.strip():>20s}' for x in keys))
639
+ print(colorstr('evolve: ') + ', '.join(f'{x:20.5g}' for x in vals), end='\n\n\n')
 
 
640
 
641
  # Save yaml
642
+ with open(evolve_yaml, 'w') as f:
643
+ data = pd.read_csv(evolve_csv)
644
+ data = data.rename(columns=lambda x: x.strip()) # strip keys
645
+ i = np.argmax(fitness(data.values[:, :7])) #
646
+ f.write(f'# YOLOv5 Hyperparameter Evolution Results\n' +
647
+ f'# Best generation: {i}\n' +
648
+ f'# Last generation: {len(data)}\n' +
649
+ f'# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) + '\n' +
650
+ f'# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n')
651
  yaml.safe_dump(hyp, f, sort_keys=False)
652
 
653
  if bucket:
654
+ os.system(f'gsutil cp {evolve_csv} {evolve_yaml} gs://{bucket}') # upload
655
 
656
 
657
  def apply_classifier(x, model, img, im0):
utils/loggers/__init__.py CHANGED
@@ -95,9 +95,8 @@ class Loggers():
95
  files = sorted(self.save_dir.glob('val*.jpg'))
96
  self.wandb.log({"Validation": [wandb.Image(str(f), caption=f.name) for f in files]})
97
 
98
- def on_fit_epoch_end(self, mloss, results, lr, epoch, best_fitness, fi):
99
  # Callback runs at the end of each fit (train+val) epoch
100
- vals = list(mloss) + list(results) + lr
101
  x = {k: v for k, v in zip(self.keys, vals)} # dict
102
  if self.csv:
103
  file = self.save_dir / 'results.csv'
@@ -123,7 +122,7 @@ class Loggers():
123
  def on_train_end(self, last, best, plots, epoch):
124
  # Callback runs on training end
125
  if plots:
126
- plot_results(dir=self.save_dir) # save results.png
127
  files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]]
128
  files = [(self.save_dir / f) for f in files if (self.save_dir / f).exists()] # filter
129
 
 
95
  files = sorted(self.save_dir.glob('val*.jpg'))
96
  self.wandb.log({"Validation": [wandb.Image(str(f), caption=f.name) for f in files]})
97
 
98
+ def on_fit_epoch_end(self, vals, epoch, best_fitness, fi):
99
  # Callback runs at the end of each fit (train+val) epoch
 
100
  x = {k: v for k, v in zip(self.keys, vals)} # dict
101
  if self.csv:
102
  file = self.save_dir / 'results.csv'
 
122
  def on_train_end(self, last, best, plots, epoch):
123
  # Callback runs on training end
124
  if plots:
125
+ plot_results(file=self.save_dir / 'results.csv') # save results.png
126
  files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]]
127
  files = [(self.save_dir / f) for f in files if (self.save_dir / f).exists()] # filter
128
 
utils/plots.py CHANGED
@@ -325,30 +325,6 @@ def plot_labels(labels, names=(), save_dir=Path('')):
325
  plt.close()
326
 
327
 
328
- def plot_evolution(yaml_file='data/hyp.finetune.yaml'): # from utils.plots import *; plot_evolution()
329
- # Plot hyperparameter evolution results in evolve.txt
330
- with open(yaml_file) as f:
331
- hyp = yaml.safe_load(f)
332
- x = np.loadtxt('evolve.txt', ndmin=2)
333
- f = fitness(x)
334
- # weights = (f - f.min()) ** 2 # for weighted results
335
- plt.figure(figsize=(10, 12), tight_layout=True)
336
- matplotlib.rc('font', **{'size': 8})
337
- for i, (k, v) in enumerate(hyp.items()):
338
- y = x[:, i + 7]
339
- # mu = (y * weights).sum() / weights.sum() # best weighted result
340
- mu = y[f.argmax()] # best single result
341
- plt.subplot(6, 5, i + 1)
342
- plt.scatter(y, f, c=hist2d(y, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
343
- plt.plot(mu, f.max(), 'k+', markersize=15)
344
- plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters
345
- if i % 5 != 0:
346
- plt.yticks([])
347
- print('%15s: %.3g' % (k, mu))
348
- plt.savefig('evolve.png', dpi=200)
349
- print('\nPlot saved as evolve.png')
350
-
351
-
352
  def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
353
  # Plot iDetection '*.txt' per-image logs. from utils.plots import *; profile_idetection()
354
  ax = plt.subplots(2, 4, figsize=(12, 6), tight_layout=True)[1].ravel()
@@ -381,7 +357,31 @@ def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
381
  plt.savefig(Path(save_dir) / 'idetection_profile.png', dpi=200)
382
 
383
 
384
- def plot_results(file='', dir=''):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  # Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv')
386
  save_dir = Path(file).parent if file else Path(dir)
387
  fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True)
 
325
  plt.close()
326
 
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  def profile_idetection(start=0, stop=0, labels=(), save_dir=''):
329
  # Plot iDetection '*.txt' per-image logs. from utils.plots import *; profile_idetection()
330
  ax = plt.subplots(2, 4, figsize=(12, 6), tight_layout=True)[1].ravel()
 
357
  plt.savefig(Path(save_dir) / 'idetection_profile.png', dpi=200)
358
 
359
 
360
+ def plot_evolve(evolve_csv=Path('path/to/evolve.csv')): # from utils.plots import *; plot_evolve()
361
+ # Plot evolve.csv hyp evolution results
362
+ data = pd.read_csv(evolve_csv)
363
+ keys = [x.strip() for x in data.columns]
364
+ x = data.values
365
+ f = fitness(x)
366
+ j = np.argmax(f) # max fitness index
367
+ plt.figure(figsize=(10, 12), tight_layout=True)
368
+ matplotlib.rc('font', **{'size': 8})
369
+ for i, k in enumerate(keys[7:]):
370
+ v = x[:, 7 + i]
371
+ mu = v[j] # best single result
372
+ plt.subplot(6, 5, i + 1)
373
+ plt.scatter(v, f, c=hist2d(v, f, 20), cmap='viridis', alpha=.8, edgecolors='none')
374
+ plt.plot(mu, f.max(), 'k+', markersize=15)
375
+ plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters
376
+ if i % 5 != 0:
377
+ plt.yticks([])
378
+ print('%15s: %.3g' % (k, mu))
379
+ f = evolve_csv.with_suffix('.png') # filename
380
+ plt.savefig(f, dpi=200)
381
+ print(f'Saved {f}')
382
+
383
+
384
+ def plot_results(file='path/to/results.csv', dir=''):
385
  # Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv')
386
  save_dir = Path(file).parent if file else Path(dir)
387
  fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True)