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
- .dockerignore +1 -1
- .gitignore +0 -1
- train.py +18 -14
- utils/general.py +29 -21
- utils/loggers/__init__.py +2 -3
- utils/plots.py +25 -25
.dockerignore
CHANGED
@@ -8,7 +8,7 @@ coco
|
|
8 |
storage.googleapis.com
|
9 |
|
10 |
data/samples/*
|
11 |
-
**/results*.
|
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,
|
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 |
-
|
|
|
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 |
-
|
478 |
-
|
|
|
|
|
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 |
-
|
539 |
if opt.bucket:
|
540 |
-
os.system(f'gsutil cp gs://{opt.bucket}/evolve.
|
541 |
|
542 |
for _ in range(opt.evolve): # generations to evolve
|
543 |
-
if
|
544 |
# Select parent(s)
|
545 |
parent = 'single' # parent selection method: 'single' or 'weighted'
|
546 |
-
x = np.loadtxt('
|
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(),
|
579 |
|
580 |
# Plot results
|
581 |
-
|
582 |
-
print(f'Hyperparameter evolution
|
583 |
-
f
|
|
|
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(
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
|
|
624 |
|
|
|
625 |
if bucket:
|
626 |
-
url = 'gs
|
627 |
-
if gsutil_getsize(url) > (os.path.getsize(
|
628 |
-
os.system('gsutil cp
|
|
|
|
|
|
|
|
|
|
|
629 |
|
630 |
-
|
631 |
-
|
632 |
-
|
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 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
|
|
|
|
|
|
643 |
yaml.safe_dump(hyp, f, sort_keys=False)
|
644 |
|
645 |
if bucket:
|
646 |
-
os.system('gsutil cp
|
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,
|
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(
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|