From 696e3ab1207578492d4bdaa7f0411983ab678cd8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 7 Dec 2020 05:27:01 +0100 Subject: [PATCH 001/349] Add classifier.py --- classifier.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 classifier.py diff --git a/classifier.py b/classifier.py new file mode 100644 index 000000000000..8a7ced171c54 --- /dev/null +++ b/classifier.py @@ -0,0 +1,196 @@ +import argparse +import logging +import os +from pathlib import Path + +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim +import torch.optim.lr_scheduler as lr_scheduler +import torchvision +import torchvision.transforms as T +from torch.cuda import amp +from tqdm import tqdm + +from models.common import Classify +from utils.general import set_logging, check_file, increment_path +from utils.torch_utils import model_info, select_device + +# Settings +logger = logging.getLogger(__name__) +set_logging() + + +# Show images +def imshow(img): + import matplotlib.pyplot as plt + import numpy as np + + plt.imshow(np.transpose((img / 2 + 0.5).numpy(), (1, 2, 0))) # unnormalize + plt.savefig('images.jpg') + + +def train(): + data, bs, epochs, nw = opt.data, opt.batch_size, opt.epochs, opt.workers + + # Download Dataset + if not Path(f'../{data}').is_dir(): + f = 'tmp.zip' + torch.hub.download_url_to_file(f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip', f) + os.system(f'unzip -q {f} -d ../ && rm {f}') # unzip + + # Transforms + trainform = T.Compose([T.RandomGrayscale(p=0.01), + T.RandomHorizontalFlip(p=0.5), + T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), + shear=(-1, 1, -1, 1), fillcolor=(114, 114, 114)), + # T.Resize([128, 128]), + T.ToTensor(), + T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] + testform = T.Compose(trainform.transforms[-2:]) + + # Dataloaders + trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) + trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) + testset = torchvision.datasets.ImageFolder(root=f'../{data}/test', transform=testform) + testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=False, num_workers=nw) + names = trainset.classes + nc = len(names) + print(f'Training {opt.model} on {data} dataset with {nc} classes...') + + # Show images + # images, labels = iter(trainloader).next() + # imshow(torchvision.utils.make_grid(images[:16])) + # print(' '.join('%5s' % names[labels[j]] for j in range(16))) + + # Model + if opt.model.startswith('yolov5'): + # YOLOv5 Classifier + model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True) + model.model = model.model[:8] + m = model.model[-1] # last layer + ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module + c = Classify(ch, nc) # Classify() + c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type + model.model[-1] = c # replace + elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): + model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=True) + model.classifier = nn.Linear(model.classifier.in_features, nc) + else: # try torchvision + model = torchvision.models.__dict__[opt.model](pretrained=True) + model.fc = nn.Linear(model.fc.weight.shape[1], nc) + + # print(model) + + model_info(model) + + # Optimizer + lr0 = 0.0001 * bs # intial lr + lrf = 0.01 # final lr (fraction of lr0) + if opt.adam: + optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) + else: + optimizer = optim.SGD(model.parameters(), lr=lr0, momentum=0.9, nesterov=True) + + # Scheduler + lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine + scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) + + # Train + model = model.to(device) + criterion = nn.CrossEntropyLoss() # loss function + # scaler = amp.GradScaler(enabled=cuda) + print(f"\n{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") + for epoch in range(epochs): # loop over the dataset multiple times + mloss = 0. # mean loss + model.train() + pbar = tqdm(enumerate(trainloader), total=len(trainloader)) # progress bar + for i, (images, labels) in pbar: + images, labels = images.to(device), labels.to(device) + images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) + + # Forward + with amp.autocast(enabled=cuda): + loss = criterion(model(images), labels) + + # Backward + loss.backward() # scaler.scale(loss).backward() + + # Optimize + optimizer.step() # scaler.step(optimizer); scaler.update() + optimizer.zero_grad() + + # Print + mloss += loss.item() + mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) + pbar.desc = f"{'%s/%s' % (epoch + 1, epochs):10s}{mem:10s}{mloss / (i + 1):<12.3g}" + + # Test + if i == len(pbar) - 1: + test(model, testloader, names, criterion, pbar=pbar) # test + + # Test + scheduler.step() + + # Show predictions + # images, labels = iter(testloader).next() + # predicted = torch.max(model(images), 1)[1] + # imshow(torchvision.utils.make_grid(images)) + # print('GroundTruth: ', ' '.join('%5s' % names[labels[j]] for j in range(4))) + # print('Predicted: ', ' '.join('%5s' % names[predicted[j]] for j in range(4))) + + +def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): + model.eval() + pred, targets, loss = [], [], 0 + with torch.no_grad(): + for images, labels in dataloader: + images, labels = images.to(device), labels.to(device) + images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) + y = model(images) + pred.append(torch.max(y, 1)[1]) + targets.append(labels) + if criterion: + loss += criterion(y, labels) + + pred, targets = torch.cat(pred), torch.cat(targets) + correct = (targets == pred).float() + + if pbar: + pbar.desc += f"{loss / len(dataloader):<12.3g}{correct.mean().item():<12.3g}" + + if verbose: # all classes + print('%10s' * 3 % ('class', 'number', 'accuracy')) + print('%10s%10s%10.5g' % ('all', correct.shape[0], correct.mean().item())) + for i, c in enumerate(names): + t = correct[targets == i] + print('%10s%10s%10.5g' % (c, t.shape[0], t.mean().item())) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') + parser.add_argument('--data', type=str, default='cifar100', help='cifar10, cifar100 or mnist') + parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path') + parser.add_argument('--epochs', type=int, default=20) + parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') + parser.add_argument('--img-size', nargs='+', type=int, default=[128, 128], help='[train, test] image sizes') + parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') + parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') + parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') + parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') + parser.add_argument('--workers', type=int, default=4, help='maximum number of dataloader workers') + parser.add_argument('--project', default='runs/train', help='save to project/name') + parser.add_argument('--name', default='exp', help='save to project/name') + parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') + opt = parser.parse_args() + + device = select_device(opt.device, batch_size=opt.batch_size) + cuda = device.type != 'cpu' + opt.hyp = check_file(opt.hyp) # check files + opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 if 1 + opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run + + train() From 04fddf507fcecace0ef1842f841517a4c0fdbdb3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 10 Feb 2021 13:29:23 -0800 Subject: [PATCH 002/349] Update classifier.py --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 8a7ced171c54..a805c87f406f 100644 --- a/classifier.py +++ b/classifier.py @@ -68,7 +68,7 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True) + model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) model.model = model.model[:8] m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module From f0b630a34795ab7dd572cafe390674fd05de0316 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:00:22 -0800 Subject: [PATCH 003/349] Implement checkpointing --- classifier.py | 50 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/classifier.py b/classifier.py index a805c87f406f..913b9f1bdaf9 100644 --- a/classifier.py +++ b/classifier.py @@ -1,9 +1,10 @@ import argparse import logging +import math import os +from copy import deepcopy from pathlib import Path -import math import torch import torch.nn as nn import torch.nn.functional as F @@ -16,7 +17,7 @@ from models.common import Classify from utils.general import set_logging, check_file, increment_path -from utils.torch_utils import model_info, select_device +from utils.torch_utils import model_info, select_device, is_parallel # Settings logger = logging.getLogger(__name__) @@ -33,12 +34,18 @@ def imshow(img): def train(): - data, bs, epochs, nw = opt.data, opt.batch_size, opt.epochs, opt.workers + save_dir, data, bs, epochs, nw = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, opt.workers + + # Directories + wdir = save_dir / 'weights' + wdir.mkdir(parents=True, exist_ok=True) # make dir + last, best = wdir / 'last.pt', wdir / 'best.pt' # Download Dataset if not Path(f'../{data}').is_dir(): - f = 'tmp.zip' - torch.hub.download_url_to_file(f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip', f) + url, f = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip', 'tmp.zip' + print(f'Downloading {url}...') + torch.hub.download_url_to_file(url, f) os.system(f'unzip -q {f} -d ../ && rm {f}') # unzip # Transforms @@ -102,6 +109,7 @@ def train(): model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function # scaler = amp.GradScaler(enabled=cuda) + best_fitness = 0. print(f"\n{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times mloss = 0. # mean loss @@ -129,11 +137,29 @@ def train(): # Test if i == len(pbar) - 1: - test(model, testloader, names, criterion, pbar=pbar) # test + fitness = test(model, testloader, names, criterion, pbar=pbar) # test - # Test + # Scheduler scheduler.step() + # # Update best fitness + if fitness > best_fitness: + best_fitness = fitness + + # Save model + final_epoch = epoch + 1 == epochs + if (not opt.nosave) or final_epoch: + ckpt = {'epoch': epoch, + 'best_fitness': best_fitness, + 'model': deepcopy(model.module if is_parallel(model) else model).half(), + 'optimizer': None} + + # Save last, best and delete + torch.save(ckpt, last) + if best_fitness == fitness: + torch.save(ckpt, best) + del ckpt + # Show predictions # images, labels = iter(testloader).next() # predicted = torch.max(model(images), 1)[1] @@ -161,22 +187,26 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): if pbar: pbar.desc += f"{loss / len(dataloader):<12.3g}{correct.mean().item():<12.3g}" + accuracy = correct.mean().item() if verbose: # all classes print('%10s' * 3 % ('class', 'number', 'accuracy')) - print('%10s%10s%10.5g' % ('all', correct.shape[0], correct.mean().item())) + print('%10s%10s%10.5g' % ('all', correct.shape[0], accuracy)) for i, c in enumerate(names): t = correct[targets == i] print('%10s%10s%10.5g' % (c, t.shape[0], t.mean().item())) + return accuracy + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='cifar100', help='cifar10, cifar100 or mnist') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100 or mnist') parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--img-size', nargs='+', type=int, default=[128, 128], help='[train, test] image sizes') + parser.add_argument('--img-size', nargs='+', type=int, default=[32, 32], help='[train, test] image sizes') + parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') From 655675b605de90655cad0b492300e916503cf865 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:04:04 -0800 Subject: [PATCH 004/349] Usage example --- classifier.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/classifier.py b/classifier.py index 913b9f1bdaf9..96bc377e78fb 100644 --- a/classifier.py +++ b/classifier.py @@ -1,3 +1,6 @@ +# YOLOv5 classifier training +# Usage: python classifier.py --model yolov5s --data mnist --epochs 10 --img 128 + import argparse import logging import math From 05b8c6a5012755048aecb66acbc449614941de6f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:05:15 -0800 Subject: [PATCH 005/349] fillcolor deprecated to fill --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 96bc377e78fb..ead60571cb94 100644 --- a/classifier.py +++ b/classifier.py @@ -55,7 +55,7 @@ def train(): trainform = T.Compose([T.RandomGrayscale(p=0.01), T.RandomHorizontalFlip(p=0.5), T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), - shear=(-1, 1, -1, 1), fillcolor=(114, 114, 114)), + shear=(-1, 1, -1, 1), fill=(114, 114, 114)), # T.Resize([128, 128]), T.ToTensor(), T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] From bbcc482416dd75f762411976f911948e14818adc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:09:03 -0800 Subject: [PATCH 006/349] workers to max os.cpu_count() --- classifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index ead60571cb94..696506dcc895 100644 --- a/classifier.py +++ b/classifier.py @@ -37,7 +37,8 @@ def imshow(img): def train(): - save_dir, data, bs, epochs, nw = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, opt.workers + save_dir, data, bs, epochs, nw = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, \ + min(os.cpu_count(), opt.workers) # Directories wdir = save_dir / 'weights' @@ -62,7 +63,7 @@ def train(): testform = T.Compose(trainform.transforms[-2:]) # Dataloaders - trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) + trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transformd=trainform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) testset = torchvision.datasets.ImageFolder(root=f'../{data}/test', transform=testform) testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=False, num_workers=nw) From 7e818dddec08b06dcb3a4c805b418f3b54399521 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:10:30 -0800 Subject: [PATCH 007/349] transform bug fix --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 696506dcc895..a8ff7313a156 100644 --- a/classifier.py +++ b/classifier.py @@ -63,7 +63,7 @@ def train(): testform = T.Compose(trainform.transforms[-2:]) # Dataloaders - trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transformd=trainform) + trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) testset = torchvision.datasets.ImageFolder(root=f'../{data}/test', transform=testform) testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=False, num_workers=nw) From ecceb2b06e27610a095b5144788f478d7280de64 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:13:49 -0800 Subject: [PATCH 008/349] Update logging for start/end training --- classifier.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classifier.py b/classifier.py index a8ff7313a156..629b7613ab64 100644 --- a/classifier.py +++ b/classifier.py @@ -114,6 +114,7 @@ def train(): criterion = nn.CrossEntropyLoss() # loss function # scaler = amp.GradScaler(enabled=cuda) best_fitness = 0. + print(f'Starting training for {epochs} epochs...') print(f"\n{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times mloss = 0. # mean loss @@ -164,6 +165,10 @@ def train(): torch.save(ckpt, best) del ckpt + # Train complete + if final_epoch: + print(f'Training Complete. Results saved to {save_dir}.') + # Show predictions # images, labels = iter(testloader).next() # predicted = torch.max(model(images), 1)[1] From 3e27ba7fc08c4977f114d99c6218e257933df82f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 15:22:14 -0800 Subject: [PATCH 009/349] cleanup --- classifier.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 629b7613ab64..a1d4371a7589 100644 --- a/classifier.py +++ b/classifier.py @@ -93,8 +93,7 @@ def train(): model = torchvision.models.__dict__[opt.model](pretrained=True) model.fc = nn.Linear(model.fc.weight.shape[1], nc) - # print(model) - + # print(model) # debug model_info(model) # Optimizer @@ -147,7 +146,7 @@ def train(): # Scheduler scheduler.step() - # # Update best fitness + # Best fitness if fitness > best_fitness: best_fitness = fitness @@ -167,7 +166,7 @@ def train(): # Train complete if final_epoch: - print(f'Training Complete. Results saved to {save_dir}.') + print(f'Training complete. Results saved to {save_dir}.') # Show predictions # images, labels = iter(testloader).next() From 75e2637eb54c897c2690f8d5475d23d84faa860c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 16:59:25 -0800 Subject: [PATCH 010/349] print(f'') --- classifier.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index a1d4371a7589..8442d8a15641 100644 --- a/classifier.py +++ b/classifier.py @@ -113,8 +113,8 @@ def train(): criterion = nn.CrossEntropyLoss() # loss function # scaler = amp.GradScaler(enabled=cuda) best_fitness = 0. - print(f'Starting training for {epochs} epochs...') - print(f"\n{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") + print(f'\nStarting training for {epochs} epochs...') + print(f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times mloss = 0. # mean loss model.train() @@ -197,11 +197,11 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): accuracy = correct.mean().item() if verbose: # all classes - print('%10s' * 3 % ('class', 'number', 'accuracy')) - print('%10s%10s%10.5g' % ('all', correct.shape[0], accuracy)) + print(f"{'class':10s}{'number':10s}{'accuracy':10s}") + print(f"{'all':10s}{correct.shape[0]:10s}{accuracy:10.5g}") for i, c in enumerate(names): t = correct[targets == i] - print('%10s%10s%10.5g' % (c, t.shape[0], t.mean().item())) + print(f"{c:10s}{t.shape[0]:10s}{t.mean().item():10.5g}") return accuracy From 90e414bb3a294fd51b1957db7074035f66ae5b53 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:08:01 -0800 Subject: [PATCH 011/349] update print --- classifier.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index 8442d8a15641..f21234f794a5 100644 --- a/classifier.py +++ b/classifier.py @@ -37,8 +37,8 @@ def imshow(img): def train(): - save_dir, data, bs, epochs, nw = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, \ - min(os.cpu_count(), opt.workers) + save_dir, data, bs, epochs, nw, imgsz = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, \ + min(os.cpu_count(), opt.workers), opt.img_size # Directories wdir = save_dir / 'weights' @@ -111,10 +111,13 @@ def train(): # Train model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function - # scaler = amp.GradScaler(enabled=cuda) best_fitness = 0. - print(f'\nStarting training for {epochs} epochs...') - print(f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") + # scaler = amp.GradScaler(enabled=cuda) + print(f'Image sizes {imgsz} train, {imgsz} test\n' + f'Using {nw} dataloader workers\n' + f'Logging results to {save_dir}\n' + f'Starting training for {epochs} epochs...\n\n' + f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times mloss = 0. # mean loss model.train() From 4c34fba17a4808af6e585aee8ca24cccad2d86a0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:16:08 -0800 Subject: [PATCH 012/349] update print --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f21234f794a5..ea2366a134e8 100644 --- a/classifier.py +++ b/classifier.py @@ -113,7 +113,7 @@ def train(): criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0. # scaler = amp.GradScaler(enabled=cuda) - print(f'Image sizes {imgsz} train, {imgsz} test\n' + print(f'Image sizes {imgsz[0]} train, {imgsz[1]} test\n' f'Using {nw} dataloader workers\n' f'Logging results to {save_dir}\n' f'Starting training for {epochs} epochs...\n\n' From 8fec5567724315be4dfdb7d5da790927d706c037 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:22:02 -0800 Subject: [PATCH 013/349] T.Resize() --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index ea2366a134e8..b4db41650a5e 100644 --- a/classifier.py +++ b/classifier.py @@ -57,10 +57,10 @@ def train(): T.RandomHorizontalFlip(p=0.5), T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), shear=(-1, 1, -1, 1), fill=(114, 114, 114)), - # T.Resize([128, 128]), + T.Resize([imgsz[0], imgsz[0]]), T.ToTensor(), T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] - testform = T.Compose(trainform.transforms[-2:]) + testform = T.Compose(trainform.transforms[-3:]) # Dataloaders trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) @@ -124,7 +124,7 @@ def train(): pbar = tqdm(enumerate(trainloader), total=len(trainloader)) # progress bar for i, (images, labels) in pbar: images, labels = images.to(device), labels.to(device) - images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) + # images = F.interpolate(images, scale_factor=imgsz[0]/images.shape[2], mode='bilinear', align_corners=False) # Forward with amp.autocast(enabled=cuda): @@ -185,7 +185,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): with torch.no_grad(): for images, labels in dataloader: images, labels = images.to(device), labels.to(device) - images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) + # images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) y = model(images) pred.append(torch.max(y, 1)[1]) targets.append(labels) From 0b5197b21186d73ccbba2cd5b0666e1e9970188f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:29:45 -0800 Subject: [PATCH 014/349] lambda x: --- classifier.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index b4db41650a5e..641de489501c 100644 --- a/classifier.py +++ b/classifier.py @@ -57,7 +57,8 @@ def train(): T.RandomHorizontalFlip(p=0.5), T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), shear=(-1, 1, -1, 1), fill=(114, 114, 114)), - T.Resize([imgsz[0], imgsz[0]]), + # T.Resize([imgsz, imgsz]), # very slow + lambda x: F.interpolate(x, size=(imgsz, imgsz), mode='bilinear', align_corners=False), T.ToTensor(), T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] testform = T.Compose(trainform.transforms[-3:]) @@ -113,7 +114,7 @@ def train(): criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0. # scaler = amp.GradScaler(enabled=cuda) - print(f'Image sizes {imgsz[0]} train, {imgsz[1]} test\n' + print(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' f'Logging results to {save_dir}\n' f'Starting training for {epochs} epochs...\n\n' @@ -124,7 +125,7 @@ def train(): pbar = tqdm(enumerate(trainloader), total=len(trainloader)) # progress bar for i, (images, labels) in pbar: images, labels = images.to(device), labels.to(device) - # images = F.interpolate(images, scale_factor=imgsz[0]/images.shape[2], mode='bilinear', align_corners=False) + # images = F.interpolate(images, scale_factor=imgsz/images.shape[2], mode='bilinear', align_corners=False) # Forward with amp.autocast(enabled=cuda): @@ -216,7 +217,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--img-size', nargs='+', type=int, default=[32, 32], help='[train, test] image sizes') + parser.add_argument('--img-size', type=int, default=32, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') From d3d9ef7de9dbf49e7eebabd7d2283a30b95cba33 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:30:39 -0800 Subject: [PATCH 015/349] remove extend --- classifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/classifier.py b/classifier.py index 641de489501c..d4ce95498ef8 100644 --- a/classifier.py +++ b/classifier.py @@ -232,7 +232,6 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): device = select_device(opt.device, batch_size=opt.batch_size) cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files - opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size))) # extend to 2 if 1 opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run train() From 7b6c0cd523705f9d7ec39a52b8bce7180d0e83ec Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:32:11 -0800 Subject: [PATCH 016/349] reorder T.Compose() --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index d4ce95498ef8..fb1aec37b5f4 100644 --- a/classifier.py +++ b/classifier.py @@ -58,8 +58,8 @@ def train(): T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), shear=(-1, 1, -1, 1), fill=(114, 114, 114)), # T.Resize([imgsz, imgsz]), # very slow - lambda x: F.interpolate(x, size=(imgsz, imgsz), mode='bilinear', align_corners=False), T.ToTensor(), + lambda x: F.interpolate(x, size=(imgsz, imgsz), mode='bilinear', align_corners=False), T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] testform = T.Compose(trainform.transforms[-3:]) From 47bc633917f688628b3a93bedff5d82b609da971 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:42:15 -0800 Subject: [PATCH 017/349] resize() --- classifier.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index fb1aec37b5f4..d99d04ded33e 100644 --- a/classifier.py +++ b/classifier.py @@ -10,7 +10,6 @@ import torch import torch.nn as nn -import torch.nn.functional as F import torch.optim as optim import torch.optim.lr_scheduler as lr_scheduler import torchvision @@ -59,9 +58,8 @@ def train(): shear=(-1, 1, -1, 1), fill=(114, 114, 114)), # T.Resize([imgsz, imgsz]), # very slow T.ToTensor(), - lambda x: F.interpolate(x, size=(imgsz, imgsz), mode='bilinear', align_corners=False), T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] - testform = T.Compose(trainform.transforms[-3:]) + testform = T.Compose(trainform.transforms[-2:]) # Dataloaders trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) @@ -124,8 +122,7 @@ def train(): model.train() pbar = tqdm(enumerate(trainloader), total=len(trainloader)) # progress bar for i, (images, labels) in pbar: - images, labels = images.to(device), labels.to(device) - # images = F.interpolate(images, scale_factor=imgsz/images.shape[2], mode='bilinear', align_corners=False) + images, labels = resize(images.to(device)), labels.to(device) # Forward with amp.autocast(enabled=cuda): @@ -185,7 +182,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): pred, targets, loss = [], [], 0 with torch.no_grad(): for images, labels in dataloader: - images, labels = images.to(device), labels.to(device) + images, labels = resize(images.to(device)), labels.to(device) # images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) y = model(images) pred.append(torch.max(y, 1)[1]) @@ -233,5 +230,6 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run + resize = torch.nn.Upsample(size=(opt.img_size, opt.img_size), mode='bilinear', align_corners=False) # image resize train() From a9333a57a80c011f8e271f33658e7fa04fa15750 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:45:06 -0800 Subject: [PATCH 018/349] cleanup --- classifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/classifier.py b/classifier.py index d99d04ded33e..acbd17ccddc5 100644 --- a/classifier.py +++ b/classifier.py @@ -183,7 +183,6 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): with torch.no_grad(): for images, labels in dataloader: images, labels = resize(images.to(device)), labels.to(device) - # images = F.interpolate(images, scale_factor=4, mode='bilinear', align_corners=False) y = model(images) pred.append(torch.max(y, 1)[1]) targets.append(labels) From 98f905869c2c952e2b6921522e287e0830ad4cb4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:47:36 -0800 Subject: [PATCH 019/349] add checks --- classifier.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index acbd17ccddc5..566de6cef2be 100644 --- a/classifier.py +++ b/classifier.py @@ -18,7 +18,7 @@ from tqdm import tqdm from models.common import Classify -from utils.general import set_logging, check_file, increment_path +from utils.general import set_logging, check_file, increment_path, check_git_status, check_requirements from utils.torch_utils import model_info, select_device, is_parallel # Settings @@ -225,10 +225,16 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') opt = parser.parse_args() + # Checks + check_git_status() + check_requirements() + + # Parameters device = select_device(opt.device, batch_size=opt.batch_size) cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run resize = torch.nn.Upsample(size=(opt.img_size, opt.img_size), mode='bilinear', align_corners=False) # image resize + # Train train() From 058d667dc953405f76e5c03b85cbd7c4eb70d128 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Mar 2021 17:54:19 -0800 Subject: [PATCH 020/349] --img-size 64 --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 566de6cef2be..c8e8c0a856db 100644 --- a/classifier.py +++ b/classifier.py @@ -213,7 +213,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--img-size', type=int, default=32, help='train, test image sizes (pixels)') + parser.add_argument('--img-size', type=int, default=64, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') From 95109a0ee3e2a2f218d65973db4ae3172bbf2129 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 19 May 2021 13:25:49 +0200 Subject: [PATCH 021/349] add comment --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c8e8c0a856db..ee06e210a500 100644 --- a/classifier.py +++ b/classifier.py @@ -85,7 +85,7 @@ def train(): c = Classify(ch, nc) # Classify() c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type model.model[-1] = c # replace - elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): + elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=True) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision From ad50c810077c72429433d0294474f53d6d6cd80f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 19 May 2021 20:39:55 +0200 Subject: [PATCH 022/349] Turn on YOLOv5 gradients --- classifier.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classifier.py b/classifier.py index ee06e210a500..b9e77525fdcc 100644 --- a/classifier.py +++ b/classifier.py @@ -85,6 +85,8 @@ def train(): c = Classify(ch, nc) # Classify() c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type model.model[-1] = c # replace + for p in model.parameters(): + p.requires_grad = True # for training elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=True) model.classifier = nn.Linear(model.classifier.in_features, nc) From 76259b15b0644500ccd9b97285104d33e773be4b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 9 Oct 2021 14:40:42 -0700 Subject: [PATCH 023/349] Hyp fix --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index b9e77525fdcc..0285a5a806a8 100644 --- a/classifier.py +++ b/classifier.py @@ -212,7 +212,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100 or mnist') - parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path') + parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--img-size', type=int, default=64, help='train, test image sizes (pixels)') From 136640eee86b529b6419e6e9b4c7c008aea9b6a8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 9 Oct 2021 15:22:12 -0700 Subject: [PATCH 024/349] Improve doctstring, add usage examples --- classifier.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 0285a5a806a8..72656c41ac0d 100644 --- a/classifier.py +++ b/classifier.py @@ -1,5 +1,33 @@ -# YOLOv5 classifier training -# Usage: python classifier.py --model yolov5s --data mnist --epochs 10 --img 128 +# YOLOv5 🚀 by Ultralytics, GPL-3.0 license +""" +Train a YOLOv5 classifier model on a classification dataset + +Usage-train: + $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 + +Usage-inference: + import cv2 + import numpy as np + import torch + import torch.nn.functional as F + + # Functions + resize = torch.nn.Upsample(size=(128, 128), mode='bilinear', align_corners=False) + normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std + + # Model + model = torch.load('runs/train/exp2/weights/best.pt')['model'].cpu().float() + + # Image + im = cv2.imread('../mnist/test/0/10.png')[::-1] # HWC, BGR to RGB + im = np.ascontiguousarray(np.asarray(im).transpose((2, 0, 1))) # HWC to CHW + im = torch.tensor(im).unsqueeze(0) / 255.0 # to Tensor, to BCWH, rescale + im = resize(normalize(im)) + + # Inference + results = model(im) + p = F.softmax(results, dim=1) # probabilities +""" import argparse import logging From 07ae9f799af7c60eedf3bb99de760c6108221ba6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 6 Dec 2021 20:50:38 +0100 Subject: [PATCH 025/349] DetectMultiBackend fix --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 72656c41ac0d..99de918da156 100644 --- a/classifier.py +++ b/classifier.py @@ -106,8 +106,8 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) - model.model = model.model[:8] + model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False).model + model.model = model.model[:8] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module c = Classify(ch, nc) # Classify() From 2ed726baff3729b0c575232c855d0f4696fef943 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 6 Dec 2021 20:55:05 +0100 Subject: [PATCH 026/349] pbar fix --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 99de918da156..4034a36da35c 100644 --- a/classifier.py +++ b/classifier.py @@ -140,7 +140,7 @@ def train(): # Train model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function - best_fitness = 0. + best_fitness = 0.0 # scaler = amp.GradScaler(enabled=cuda) print(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' @@ -148,10 +148,10 @@ def train(): f'Starting training for {epochs} epochs...\n\n' f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times - mloss = 0. # mean loss + mloss = 0.0 # mean loss model.train() - pbar = tqdm(enumerate(trainloader), total=len(trainloader)) # progress bar - for i, (images, labels) in pbar: + pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') + for i, (images, labels) in pbar: # progress bar images, labels = resize(images.to(device)), labels.to(device) # Forward From 91e13b24f4446eb0b4f2cf516463e4c0fa49bad9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Dec 2021 16:15:31 +0100 Subject: [PATCH 027/349] Smart DetectMultiBackend() unwrapping --- classifier.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 4034a36da35c..36eaac05dd22 100644 --- a/classifier.py +++ b/classifier.py @@ -45,7 +45,7 @@ from torch.cuda import amp from tqdm import tqdm -from models.common import Classify +from models.common import Classify, DetectMultiBackend from utils.general import set_logging, check_file, increment_path, check_git_status, check_requirements from utils.torch_utils import model_info, select_device, is_parallel @@ -106,7 +106,9 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False).model + model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) + if isinstance(model, DetectMultiBackend): + model = model.model # unwrap DetectMultiBackend model.model = model.model[:8] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module From 1207356411b494161f423de3360653be5d1645e0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Dec 2021 16:33:16 +0100 Subject: [PATCH 028/349] Implement scaled loss backward --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 36eaac05dd22..9535ab66b454 100644 --- a/classifier.py +++ b/classifier.py @@ -143,7 +143,7 @@ def train(): model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 - # scaler = amp.GradScaler(enabled=cuda) + scaler = amp.GradScaler(enabled=cuda) print(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' f'Logging results to {save_dir}\n' @@ -161,7 +161,7 @@ def train(): loss = criterion(model(images), labels) # Backward - loss.backward() # scaler.scale(loss).backward() + scaler.scale(loss).backward() # loss.backward() # Optimize optimizer.step() # scaler.step(optimizer); scaler.update() From 3feecfaeb7f52f27e7280265a2fca7e00c88fdf3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Dec 2021 17:19:58 +0100 Subject: [PATCH 029/349] Improve stability by disabling AMP --- classifier.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 9535ab66b454..263466439ed2 100644 --- a/classifier.py +++ b/classifier.py @@ -138,12 +138,14 @@ def train(): # Scheduler lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) + # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, + # final_div_factor=1 / 25 / lrf) # Train model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 - scaler = amp.GradScaler(enabled=cuda) + # scaler = amp.GradScaler(enabled=cuda) print(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' f'Logging results to {save_dir}\n' @@ -157,11 +159,11 @@ def train(): images, labels = resize(images.to(device)), labels.to(device) # Forward - with amp.autocast(enabled=cuda): + with amp.autocast(enabled=False): # stability issues when enabled loss = criterion(model(images), labels) # Backward - scaler.scale(loss).backward() # loss.backward() + loss.backward() # scaler.scale(loss).backward() # Optimize optimizer.step() # scaler.step(optimizer); scaler.update() From 3e0008d53d185118115aa9791e230aa261465477 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 8 Dec 2021 17:48:05 +0100 Subject: [PATCH 030/349] Add --adam to Usage: example --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 263466439ed2..ecf04d5212d3 100644 --- a/classifier.py +++ b/classifier.py @@ -3,7 +3,7 @@ Train a YOLOv5 classifier model on a classification dataset Usage-train: - $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 + $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 --adam Usage-inference: import cv2 From 14af287e6cfa6ba4822dfac4457f29174100f638 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 11 Dec 2021 13:00:42 +0100 Subject: [PATCH 031/349] Add FILE ROOT globals --- classifier.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/classifier.py b/classifier.py index ecf04d5212d3..da9f45b2a886 100644 --- a/classifier.py +++ b/classifier.py @@ -30,9 +30,9 @@ """ import argparse -import logging import math import os +import sys from copy import deepcopy from pathlib import Path @@ -45,23 +45,16 @@ from torch.cuda import amp from tqdm import tqdm +FILE = Path(__file__).resolve() +ROOT = FILE.parents[0] # YOLOv5 root directory +if str(ROOT) not in sys.path: + sys.path.append(str(ROOT)) # add ROOT to PATH +ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative + from models.common import Classify, DetectMultiBackend -from utils.general import set_logging, check_file, increment_path, check_git_status, check_requirements +from utils.general import download, check_file, increment_path, check_git_status, check_requirements from utils.torch_utils import model_info, select_device, is_parallel -# Settings -logger = logging.getLogger(__name__) -set_logging() - - -# Show images -def imshow(img): - import matplotlib.pyplot as plt - import numpy as np - - plt.imshow(np.transpose((img / 2 + 0.5).numpy(), (1, 2, 0))) # unnormalize - plt.savefig('images.jpg') - def train(): save_dir, data, bs, epochs, nw, imgsz = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, \ @@ -73,11 +66,10 @@ def train(): last, best = wdir / 'last.pt', wdir / 'best.pt' # Download Dataset - if not Path(f'../{data}').is_dir(): - url, f = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip', 'tmp.zip' - print(f'Downloading {url}...') - torch.hub.download_url_to_file(url, f) - os.system(f'unzip -q {f} -d ../ && rm {f}') # unzip + data_dir = FILE.parents[1] / 'datasets' / data + if not data_dir.is_dir(): + url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' + download(url, dir=data_dir.parent) # Transforms trainform = T.Compose([T.RandomGrayscale(p=0.01), @@ -90,9 +82,9 @@ def train(): testform = T.Compose(trainform.transforms[-2:]) # Dataloaders - trainset = torchvision.datasets.ImageFolder(root=f'../{data}/train', transform=trainform) + trainset = torchvision.datasets.ImageFolder(root=data_dir / 'train', transform=trainform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) - testset = torchvision.datasets.ImageFolder(root=f'../{data}/test', transform=testform) + testset = torchvision.datasets.ImageFolder(root=data_dir / 'test', transform=testform) testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=False, num_workers=nw) names = trainset.classes nc = len(names) @@ -240,6 +232,14 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return accuracy +def imshow(img): + # Show images + import matplotlib.pyplot as plt + import numpy as np + plt.imshow(np.transpose((img / 2 + 0.5).numpy(), (1, 2, 0))) # de-normalize + plt.savefig('images.jpg') + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') From ecd0f6d9b97b65bab5eda27abcd4187cb2af2a9f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 11 Dec 2021 13:36:02 +0100 Subject: [PATCH 032/349] Add mnist-fashion --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index da9f45b2a886..33bc5bf1dc27 100644 --- a/classifier.py +++ b/classifier.py @@ -243,7 +243,7 @@ def imshow(img): if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100 or mnist') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') From ea3d1310103c90a05a927438368761dbba87bcd4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 11 Dec 2021 14:34:12 +0100 Subject: [PATCH 033/349] Update default --workers 8 --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 33bc5bf1dc27..f1c6d63855ea 100644 --- a/classifier.py +++ b/classifier.py @@ -52,13 +52,13 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from models.common import Classify, DetectMultiBackend -from utils.general import download, check_file, increment_path, check_git_status, check_requirements +from utils.general import NUM_THREADS, download, check_file, increment_path, check_git_status, check_requirements from utils.torch_utils import model_info, select_device, is_parallel def train(): save_dir, data, bs, epochs, nw, imgsz = Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, \ - min(os.cpu_count(), opt.workers), opt.img_size + min(NUM_THREADS, opt.workers), opt.img_size # Directories wdir = save_dir / 'weights' @@ -253,7 +253,7 @@ def imshow(img): parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--workers', type=int, default=4, help='maximum number of dataloader workers') + parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') From 3a2417430ab5c3c1ed0dd7c747ff91bac8f84b9d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 16:04:43 +0100 Subject: [PATCH 034/349] Update show --- classifier.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index f1c6d63855ea..0956f80d8ee1 100644 --- a/classifier.py +++ b/classifier.py @@ -196,11 +196,13 @@ def train(): print(f'Training complete. Results saved to {save_dir}.') # Show predictions - # images, labels = iter(testloader).next() - # predicted = torch.max(model(images), 1)[1] - # imshow(torchvision.utils.make_grid(images)) - # print('GroundTruth: ', ' '.join('%5s' % names[labels[j]] for j in range(4))) - # print('Predicted: ', ' '.join('%5s' % names[predicted[j]] for j in range(4))) + show = False + if show: + images, labels = iter(testloader).next() + predicted = torch.max(model(images), 1)[1] + imshow(torchvision.utils.make_grid(images)) + print('GroundTruth: ', ' '.join(f'{names[labels[j]]:5s}' for j in range(4))) + print('Predicted: ', ' '.join(f'{names[predicted[j]]:5s}' for j in range(4))) def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): From 664468eecf617710cd7fcb84994566d867e57202 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 16:14:44 +0100 Subject: [PATCH 035/349] Update --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 0956f80d8ee1..fd19c5ca8aa6 100644 --- a/classifier.py +++ b/classifier.py @@ -199,6 +199,7 @@ def train(): show = False if show: images, labels = iter(testloader).next() + images = resize(images.to(device)) predicted = torch.max(model(images), 1)[1] imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join(f'{names[labels[j]]:5s}' for j in range(4))) @@ -238,7 +239,7 @@ def imshow(img): # Show images import matplotlib.pyplot as plt import numpy as np - plt.imshow(np.transpose((img / 2 + 0.5).numpy(), (1, 2, 0))) # de-normalize + plt.imshow(np.transpose((img / 2 + 0.5).cpu().numpy(), (1, 2, 0))) # de-normalize plt.savefig('images.jpg') From 63f319ac3dbf9c6c7e525172e44a5cb9c2c91447 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 16:34:56 +0100 Subject: [PATCH 036/349] Update --- classifier.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/classifier.py b/classifier.py index fd19c5ca8aa6..5811aeee67b3 100644 --- a/classifier.py +++ b/classifier.py @@ -52,7 +52,8 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from models.common import Classify, DetectMultiBackend -from utils.general import NUM_THREADS, download, check_file, increment_path, check_git_status, check_requirements +from utils.general import NUM_THREADS, download, check_file, increment_path, check_git_status, check_requirements, \ + colorstr from utils.torch_utils import model_info, select_device, is_parallel @@ -85,15 +86,14 @@ def train(): trainset = torchvision.datasets.ImageFolder(root=data_dir / 'train', transform=trainform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) testset = torchvision.datasets.ImageFolder(root=data_dir / 'test', transform=testform) - testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=False, num_workers=nw) + testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=True, num_workers=nw) names = trainset.classes nc = len(names) print(f'Training {opt.model} on {data} dataset with {nc} classes...') # Show images - # images, labels = iter(trainloader).next() - # imshow(torchvision.utils.make_grid(images[:16])) - # print(' '.join('%5s' % names[labels[j]] for j in range(16))) + images, labels = iter(trainloader).next() + imshow(torchvision.utils.make_grid(images[:16]), labels[:16], names=names, f=save_dir / 'train_images.jpg') # Model if opt.model.startswith('yolov5'): @@ -140,7 +140,7 @@ def train(): # scaler = amp.GradScaler(enabled=cuda) print(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' - f'Logging results to {save_dir}\n' + f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times @@ -195,15 +195,11 @@ def train(): if final_epoch: print(f'Training complete. Results saved to {save_dir}.') - # Show predictions - show = False - if show: + # Show predictions images, labels = iter(testloader).next() images = resize(images.to(device)) - predicted = torch.max(model(images), 1)[1] - imshow(torchvision.utils.make_grid(images)) - print('GroundTruth: ', ' '.join(f'{names[labels[j]]:5s}' for j in range(4))) - print('Predicted: ', ' '.join(f'{names[predicted[j]]:5s}' for j in range(4))) + pred = torch.max(model(images), 1)[1] + imshow(torchvision.utils.make_grid(images), labels, pred, names, f=save_dir / 'test_pred.jpg') def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): @@ -235,12 +231,17 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return accuracy -def imshow(img): +def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): # Show images import matplotlib.pyplot as plt import numpy as np + names = names or [f'class{i}' for i in range(1000)] plt.imshow(np.transpose((img / 2 + 0.5).cpu().numpy(), (1, 2, 0))) # de-normalize - plt.savefig('images.jpg') + plt.savefig(f) + if labels is not None: + print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) + if pred is not None: + print('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) if __name__ == '__main__': From 73db0effecd348bc458cc6378cc05a80bb01372f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 16:35:48 +0100 Subject: [PATCH 037/349] Update --- classifier.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classifier.py b/classifier.py index 5811aeee67b3..1c12c1a06509 100644 --- a/classifier.py +++ b/classifier.py @@ -242,6 +242,7 @@ def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) if pred is not None: print('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) + print(f'imshow() results saved to {f}') if __name__ == '__main__': From 49ff485e5e4e00736aa56401618699e7f7202b9c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 16:39:58 +0100 Subject: [PATCH 038/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 1c12c1a06509..ea579006b9e4 100644 --- a/classifier.py +++ b/classifier.py @@ -242,7 +242,7 @@ def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) if pred is not None: print('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) - print(f'imshow() results saved to {f}') + print(colorstr('imshow(): ') + f"results saved to {f}") if __name__ == '__main__': From a3f21ad15423331ce231e16290864bb0d29b6968 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 17:05:21 +0100 Subject: [PATCH 039/349] Update --- classifier.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/classifier.py b/classifier.py index ea579006b9e4..3b452bf0c750 100644 --- a/classifier.py +++ b/classifier.py @@ -93,7 +93,7 @@ def train(): # Show images images, labels = iter(trainloader).next() - imshow(torchvision.utils.make_grid(images[:16]), labels[:16], names=names, f=save_dir / 'train_images.jpg') + imshow(images[:64], labels[:64], names=names, f=save_dir / 'train_images.jpg') # Model if opt.model.startswith('yolov5'): @@ -199,7 +199,7 @@ def train(): images, labels = iter(testloader).next() images = resize(images.to(device)) pred = torch.max(model(images), 1)[1] - imshow(torchvision.utils.make_grid(images), labels, pred, names, f=save_dir / 'test_pred.jpg') + imshow(images, labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): @@ -231,18 +231,31 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return accuracy -def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): +def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): # Show images import matplotlib.pyplot as plt - import numpy as np + names = names or [f'class{i}' for i in range(1000)] - plt.imshow(np.transpose((img / 2 + 0.5).cpu().numpy(), (1, 2, 0))) # de-normalize - plt.savefig(f) - if labels is not None: + blocks = torch.chunk(denormalize(img).cpu(), len(img), dim=0) # select batch index 0, block by channels + n = min(len(blocks), nmax) # number of plots + fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols + ax = ax.ravel() + plt.subplots_adjust(wspace=0.05, hspace=0.05) + for i in range(n): + ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0))) # cmap='gray' + ax[i].axis('off') + if labels is not None: + s = names[labels[i]] + f' - {names[pred[i]]}' if pred is not None else '' + ax[i].set_title(s) + + plt.savefig(f, dpi=300, bbox_inches='tight') + plt.close() + print(colorstr('imshow(): ') + f"results saved to {f}") + + if verbose and labels is not None: print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) - if pred is not None: + if verbose and pred is not None: print('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) - print(colorstr('imshow(): ') + f"results saved to {f}") if __name__ == '__main__': @@ -258,7 +271,7 @@ def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') + parser.add_argument('--workers', type=int, default=0, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') @@ -273,7 +286,11 @@ def imshow(img, labels=None, pred=None, names=None, f=Path('images.jpg')): cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run + + # Functions resize = torch.nn.Upsample(size=(opt.img_size, opt.img_size), mode='bilinear', align_corners=False) # image resize + normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std + denormalize = lambda x, mean=0.5, std=0.25: x * std + mean # Train train() From b5954cc30a15e3383f756e9f699b363c4ac5f14e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 17:06:23 +0100 Subject: [PATCH 040/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 3b452bf0c750..f3b1514ccded 100644 --- a/classifier.py +++ b/classifier.py @@ -245,7 +245,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0))) # cmap='gray' ax[i].axis('off') if labels is not None: - s = names[labels[i]] + f' - {names[pred[i]]}' if pred is not None else '' + s = names[labels[i]] + f'—{names[pred[i]]}' if pred is not None else '' ax[i].set_title(s) plt.savefig(f, dpi=300, bbox_inches='tight') From 5e09d0424cda13e2fec394abb3bac991e041c7e2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 17:08:15 +0100 Subject: [PATCH 041/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f3b1514ccded..07408ebda990 100644 --- a/classifier.py +++ b/classifier.py @@ -245,7 +245,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0))) # cmap='gray' ax[i].axis('off') if labels is not None: - s = names[labels[i]] + f'—{names[pred[i]]}' if pred is not None else '' + s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') ax[i].set_title(s) plt.savefig(f, dpi=300, bbox_inches='tight') From 1ec653b06bc4e2653060834b4dd98b843d48b058 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 17:14:20 +0100 Subject: [PATCH 042/349] Update --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 07408ebda990..8d6302edeb9c 100644 --- a/classifier.py +++ b/classifier.py @@ -93,7 +93,7 @@ def train(): # Show images images, labels = iter(trainloader).next() - imshow(images[:64], labels[:64], names=names, f=save_dir / 'train_images.jpg') + imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') # Model if opt.model.startswith('yolov5'): @@ -199,7 +199,7 @@ def train(): images, labels = iter(testloader).next() images = resize(images.to(device)) pred = torch.max(model(images), 1)[1] - imshow(images, labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): @@ -232,11 +232,11 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): - # Show images + # Show classification image grid with labels (optional) and predictions (optional) import matplotlib.pyplot as plt names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(denormalize(img).cpu(), len(img), dim=0) # select batch index 0, block by channels + blocks = torch.chunk(img.cpu(), len(img), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols ax = ax.ravel() From 6650720fbf5322adf8006b73f47fd14bf2487349 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 17:55:16 +0100 Subject: [PATCH 043/349] Update --- classifier.py | 59 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/classifier.py b/classifier.py index 8d6302edeb9c..cc4b807916c0 100644 --- a/classifier.py +++ b/classifier.py @@ -6,27 +6,12 @@ $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 --adam Usage-inference: - import cv2 - import numpy as np - import torch - import torch.nn.functional as F - - # Functions - resize = torch.nn.Upsample(size=(128, 128), mode='bilinear', align_corners=False) - normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std - - # Model - model = torch.load('runs/train/exp2/weights/best.pt')['model'].cpu().float() + from classifier import * - # Image - im = cv2.imread('../mnist/test/0/10.png')[::-1] # HWC, BGR to RGB - im = np.ascontiguousarray(np.asarray(im).transpose((2, 0, 1))) # HWC to CHW - im = torch.tensor(im).unsqueeze(0) / 255.0 # to Tensor, to BCWH, rescale - im = resize(normalize(im)) - - # Inference - results = model(im) - p = F.softmax(results, dim=1) # probabilities + model = torch.load('path/to/best.pt', map_location=torch.device('cpu'))['model'].float() + files = Path('../datasets/mnist/test/9').glob('*.png') + for f in list(files[:10]): + classify(model, size=128, file=f) """ import argparse @@ -231,6 +216,35 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return accuracy +def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False): + # YOLOv5 classification model inference + import cv2 + import numpy as np + import torch.nn.functional as F + + resize = torch.nn.Upsample(size=(size, size), mode='bilinear', align_corners=False) # image resize + normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std + + # Image + im = cv2.imread(str(file))[..., ::-1] # HWC, BGR to RGB + im = np.ascontiguousarray(np.asarray(im).transpose((2, 0, 1))) # HWC to CHW + im = torch.tensor(im).float().unsqueeze(0) / 255.0 # to Tensor, to BCWH, rescale + im = resize(normalize(im)) + + # Inference + results = model(im) + p = F.softmax(results, dim=1) # probabilities + i = p.argmax() # max index + print(f'{file} prediction: {i} ({p[0, i]:.2f})') + + # Plot + if plot: + denormalize = lambda x, mean=0.5, std=0.25: x * std + mean + imshow(denormalize(im), f=Path(file).name) + + return p + + def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) import matplotlib.pyplot as plt @@ -238,8 +252,9 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa names = names or [f'class{i}' for i in range(1000)] blocks = torch.chunk(img.cpu(), len(img), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots - fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols - ax = ax.ravel() + m = min(8, round(n ** 0.5)) # 8 x 8 default + fig, ax = plt.subplots(math.ceil(n / m), m, tight_layout=True) # 8 rows x n/8 cols + ax = ax.ravel() if m > 1 else [ax] plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0))) # cmap='gray' From d5fc52132a721dcaaf7fecf27ef992219a663cad Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:04:04 +0100 Subject: [PATCH 044/349] list fix --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index cc4b807916c0..857f59fbf458 100644 --- a/classifier.py +++ b/classifier.py @@ -8,9 +8,9 @@ Usage-inference: from classifier import * - model = torch.load('path/to/best.pt', map_location=torch.device('cpu'))['model'].float() - files = Path('../datasets/mnist/test/9').glob('*.png') - for f in list(files[:10]): + model = torch.load('best.pt', map_location=torch.device('cpu'))['model'].float() + files = Path('../datasets/mnist/test/7').glob('*.png') + for f in list(files)[:10]: classify(model, size=128, file=f) """ From ecce7c86c7f7adbfe99f47c3f68d07d8f4023fb8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:04:52 +0100 Subject: [PATCH 045/349] update comments --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 857f59fbf458..2cbf47469eb1 100644 --- a/classifier.py +++ b/classifier.py @@ -8,9 +8,9 @@ Usage-inference: from classifier import * - model = torch.load('best.pt', map_location=torch.device('cpu'))['model'].float() - files = Path('../datasets/mnist/test/7').glob('*.png') - for f in list(files)[:10]: + model = torch.load('path/to/best.pt', map_location=torch.device('cpu'))['model'].float() + files = Path('../datasets/mnist/test/7').glob('*.png') # images from dir + for f in list(files)[:10]: # first 10 images classify(model, size=128, file=f) """ From 35d12e6f6d1001ebf89939f9d72eb89cc1770ca6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:07:24 +0100 Subject: [PATCH 046/349] EOF fix --- hubconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hubconf.py b/hubconf.py index 406507dc094d..6bf4b0b0265f 100644 --- a/hubconf.py +++ b/hubconf.py @@ -140,4 +140,4 @@ def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=Tr results = model(imgs, size=320) # batched inference results.print() - results.save() \ No newline at end of file + results.save() From 8e71c9daaa5d7e2902ac3d2f19bb8a0dc20bfd65 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:17:52 +0100 Subject: [PATCH 047/349] Update --- classifier.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/classifier.py b/classifier.py index 2cbf47469eb1..0c40fa1be350 100644 --- a/classifier.py +++ b/classifier.py @@ -19,6 +19,7 @@ import os import sys from copy import deepcopy +from datetime import datetime from pathlib import Path import torch @@ -39,7 +40,11 @@ from models.common import Classify, DetectMultiBackend from utils.general import NUM_THREADS, download, check_file, increment_path, check_git_status, check_requirements, \ colorstr -from utils.torch_utils import model_info, select_device, is_parallel +from utils.torch_utils import model_info, select_device, de_parallel + +# Functions +normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std +denormalize = lambda x, mean=0.5, std=0.25: x * std + mean def train(): @@ -167,8 +172,9 @@ def train(): if (not opt.nosave) or final_epoch: ckpt = {'epoch': epoch, 'best_fitness': best_fitness, - 'model': deepcopy(model.module if is_parallel(model) else model).half(), - 'optimizer': None} + 'model': deepcopy(de_parallel(model)).half(), + 'optimizer': None, # optimizer.state_dict() + 'date': datetime.now().isoformat()} # Save last, best and delete torch.save(ckpt, last) @@ -223,7 +229,6 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False import torch.nn.functional as F resize = torch.nn.Upsample(size=(size, size), mode='bilinear', align_corners=False) # image resize - normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std # Image im = cv2.imread(str(file))[..., ::-1] # HWC, BGR to RGB @@ -301,11 +306,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run - - # Functions resize = torch.nn.Upsample(size=(opt.img_size, opt.img_size), mode='bilinear', align_corners=False) # image resize - normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std - denormalize = lambda x, mean=0.5, std=0.25: x * std + mean # Train train() From 5f9a7d568eead1c6090d0ffd4e10a6822366f55c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:22:02 +0100 Subject: [PATCH 048/349] Update --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 0c40fa1be350..6853b9181d0a 100644 --- a/classifier.py +++ b/classifier.py @@ -270,7 +270,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa plt.savefig(f, dpi=300, bbox_inches='tight') plt.close() - print(colorstr('imshow(): ') + f"results saved to {f}") + print(colorstr('imshow: ') + f"examples saved to {f}") if verbose and labels is not None: print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) @@ -284,14 +284,14 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) - parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--img-size', type=int, default=64, help='train, test image sizes (pixels)') + parser.add_argument('--batch-size', type=int, default=256, help='total batch size for all GPUs') + parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--workers', type=int, default=0, help='max dataloader workers (per RANK in DDP mode)') + parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') From 6c25c791243e82996e0ce7c91344333296b239d4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:45:38 +0100 Subject: [PATCH 049/349] Update backbone --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 6853b9181d0a..03a1db039073 100644 --- a/classifier.py +++ b/classifier.py @@ -91,7 +91,7 @@ def train(): model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - model.model = model.model[:8] # backbone + model.model = model.model[:13] if opt.model.endswith('6') else model.model[:11] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module c = Classify(ch, nc) # Classify() @@ -280,7 +280,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') + parser.add_argument('--model', type=str, default='yolov5s6', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) @@ -291,7 +291,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') + parser.add_argument('--workers', type=int, default=0, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') From b7419a08ced2c70556a93af37d9dc98dab93673b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:48:58 +0100 Subject: [PATCH 050/349] Update batch-size --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 03a1db039073..b18363a7013f 100644 --- a/classifier.py +++ b/classifier.py @@ -284,7 +284,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) - parser.add_argument('--batch-size', type=int, default=256, help='total batch size for all GPUs') + parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') From 87aa1731e4e6192b851512cde3153ba31b87c724 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:52:26 +0100 Subject: [PATCH 051/349] Update backbone --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index b18363a7013f..c6cd4a3fa3cd 100644 --- a/classifier.py +++ b/classifier.py @@ -91,7 +91,7 @@ def train(): model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - model.model = model.model[:13] if opt.model.endswith('6') else model.model[:11] # backbone + model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum([x.in_channels for x in m.m]) # ch into module c = Classify(ch, nc) # Classify() @@ -280,7 +280,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--model', type=str, default='yolov5s6', help='initial weights path') + parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) From 8c7c796387f070ab2356eb25c28a2aa4bfebde13 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 19 Dec 2021 18:56:31 +0100 Subject: [PATCH 052/349] Update workers --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c6cd4a3fa3cd..0f7755a0d56c 100644 --- a/classifier.py +++ b/classifier.py @@ -291,7 +291,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--workers', type=int, default=0, help='max dataloader workers (per RANK in DDP mode)') + parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') From d6851f1c1ac43383cd340ddb39d734428b819e4c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 2 Jan 2022 13:26:10 -0800 Subject: [PATCH 053/349] Add AdamW optimmizer --- train.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index e2cd5ec85c09..304c001b6547 100644 --- a/train.py +++ b/train.py @@ -22,7 +22,7 @@ import yaml from torch.cuda import amp from torch.nn.parallel import DistributedDataParallel as DDP -from torch.optim import SGD, Adam, lr_scheduler +from torch.optim import SGD, Adam, AdamW, lr_scheduler from tqdm import tqdm FILE = Path(__file__).resolve() @@ -155,8 +155,10 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter): # weight (with decay) g1.append(v.weight) - if opt.adam: + if opt.optimizer == 'Adam': optimizer = Adam(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum + elif opt.optimizer == 'AdamW': + optimizer = AdamW(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum else: optimizer = SGD(g0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) @@ -460,7 +462,7 @@ def parse_opt(known=False): 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 multi-class data as single-class') - parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') + parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer') parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name') From 0c59f45ca4ca8f58f363c4b831a970af4e754494 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 2 Jan 2022 13:33:31 -0800 Subject: [PATCH 054/349] Merge master --- classifier.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 24f6cb8e614c..e5261a2c1968 100644 --- a/classifier.py +++ b/classifier.py @@ -112,8 +112,10 @@ def train(): # Optimizer lr0 = 0.0001 * bs # intial lr lrf = 0.01 # final lr (fraction of lr0) - if opt.adam: + if opt.optimizer == 'Adam': optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) + elif opt.optimizer == 'AdamW': + optimizer = optim.AdamW(model.parameters(), lr=lr0 / 10) else: optimizer = optim.SGD(model.parameters(), lr=lr0, momentum=0.9, nesterov=True) @@ -287,7 +289,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') - parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer') + parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') From 2c1d424d50d2573746735fd57a43236fc3b4d47f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 2 Jan 2022 15:19:32 -0800 Subject: [PATCH 055/349] Default to Adam --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index e5261a2c1968..c903aa3e0a66 100644 --- a/classifier.py +++ b/classifier.py @@ -2,10 +2,10 @@ """ Train a YOLOv5 classifier model on a classification dataset -Usage-train: - $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 --optimizer AdamW +Usage - train: + $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 -Usage-inference: +Usage - inference: from classifier import * model = torch.load('path/to/best.pt', map_location=torch.device('cpu'))['model'].float() @@ -289,7 +289,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') - parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer') + parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='Adam', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') From d1e2938e2fcfb6faebe74a8b7a680d7959960f2a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 19 Mar 2022 21:36:47 +0100 Subject: [PATCH 056/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c903aa3e0a66..18b39301f56c 100644 --- a/classifier.py +++ b/classifier.py @@ -284,7 +284,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') - parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path') + parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') From f92e631a3efc7cf5200ed7222cada02328d20bc1 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Mon, 20 Jun 2022 18:03:41 +0530 Subject: [PATCH 057/349] [Classifier]: Allow training from scratch (#8264) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/classifier.py b/classifier.py index 1c13b0328a6d..15f31b97b066 100644 --- a/classifier.py +++ b/classifier.py @@ -48,9 +48,8 @@ def train(): - save_dir, data, bs, epochs, nw, imgsz = \ - Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(NUM_THREADS, opt.workers), opt.img_size - + save_dir, data, bs, epochs, nw, imgsz, pretrained = \ + Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(NUM_THREADS, opt.workers), opt.img_size, not opt.from_scratch # Directories wdir = save_dir / 'weights' wdir.mkdir(parents=True, exist_ok=True) # make dir @@ -88,7 +87,7 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=True, autoshape=False) + model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone @@ -100,10 +99,10 @@ def train(): for p in model.parameters(): p.requires_grad = True # for training elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 - model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=True) + model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=pretrained) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision - model = torchvision.models.__dict__[opt.model](pretrained=True) + model = torchvision.models.__dict__[opt.model](pretrained=pretrained) model.fc = nn.Linear(model.fc.weight.shape[1], nc) # print(model) # debug @@ -288,7 +287,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=20) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--img-size', type=int, default=128, help='train, test image sizes (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='Adam', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') @@ -298,6 +297,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') + parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') opt = parser.parse_args() # Checks From 472ac189c1fe8683dce8afe7ff88d6f752d4e880 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Mon, 27 Jun 2022 01:08:37 +0530 Subject: [PATCH 058/349] [Classifier]: Support custom dataloader and augmentation (#8292) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 54 +++++++++++++++++++++++++----------------- utils/augmentations.py | 49 ++++++++++++++++++++++++++++++++++++++ utils/dataloaders.py | 45 ++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 23 deletions(-) diff --git a/classifier.py b/classifier.py index 15f31b97b066..c4e8e6340ee8 100644 --- a/classifier.py +++ b/classifier.py @@ -27,10 +27,11 @@ import torch.optim as optim import torch.optim.lr_scheduler as lr_scheduler import torchvision -import torchvision.transforms as T from torch.cuda import amp from tqdm import tqdm +from utils.dataloaders import create_classification_dataloader + FILE = Path(__file__).resolve() ROOT = FILE.parents[0] # YOLOv5 root directory if str(ROOT) not in sys.path: @@ -42,14 +43,18 @@ increment_path) from utils.torch_utils import de_parallel, model_info, select_device +WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) +LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) + # Functions -normalize = lambda x, mean=0.5, std=0.25: (x - mean) / std -denormalize = lambda x, mean=0.5, std=0.25: x * std + mean +normalize = lambda x, mean=0.449, std=0.226: (x - mean) / std # TODO: replace with IMAGENET_MEAN, IMAGENET_STD +denormalize = lambda x, mean=0.449, std=0.226: x * std + mean def train(): save_dir, data, bs, epochs, nw, imgsz, pretrained = \ - Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(NUM_THREADS, opt.workers), opt.img_size, not opt.from_scratch + Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(NUM_THREADS, opt.workers), opt.imgsz, \ + not opt.from_scratch # Directories wdir = save_dir / 'weights' wdir.mkdir(parents=True, exist_ok=True) # make dir @@ -61,21 +66,20 @@ def train(): url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) - # Transforms - trainform = T.Compose([ - T.RandomGrayscale(p=0.01), - T.RandomHorizontalFlip(p=0.5), - T.RandomAffine(degrees=1, translate=(.2, .2), scale=(1 / 1.5, 1.5), shear=(-1, 1, -1, 1), fill=(114, 114, 114)), - # T.Resize([imgsz, imgsz]), # very slow - T.ToTensor(), - T.Normalize((0.5, 0.5, 0.5), (0.25, 0.25, 0.25))]) # PILImage from [0, 1] to [-1, 1] - testform = T.Compose(trainform.transforms[-2:]) - # Dataloaders - trainset = torchvision.datasets.ImageFolder(root=data_dir / 'train', transform=trainform) - trainloader = torch.utils.data.DataLoader(trainset, batch_size=bs, shuffle=True, num_workers=nw) - testset = torchvision.datasets.ImageFolder(root=data_dir / 'test', transform=testform) - testloader = torch.utils.data.DataLoader(testset, batch_size=bs, shuffle=True, num_workers=nw) + trainloader, trainset = create_classification_dataloader(path=data_dir / 'train', + imgsz=imgsz, + batch_size=bs, + augment=True, + rank=LOCAL_RANK, + workers=nw) + testloader, testset = create_classification_dataloader(path=data_dir / 'test', + imgsz=imgsz, + batch_size=bs, + augment=False, + rank=LOCAL_RANK, + workers=nw) + names = trainset.classes nc = len(names) print(f'Training {opt.model} on {data} dataset with {nc} classes...') @@ -155,7 +159,7 @@ def train(): # Print mloss += loss.item() mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{'%s/%s' % (epoch + 1, epochs):10s}{mem:10s}{mloss / (i + 1):<12.3g}" + pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss / (i + 1):<12.3g}" # Test if i == len(pbar) - 1: @@ -246,7 +250,6 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False # Plot if plot: - denormalize = lambda x, mean=0.5, std=0.25: x * std + mean imshow(denormalize(im), f=Path(file).name) return p @@ -254,6 +257,7 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) + import cv2 import matplotlib.pyplot as plt names = names or [f'class{i}' for i in range(1000)] @@ -264,7 +268,13 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa ax = ax.ravel() if m > 1 else [ax] plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): - ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0))) # cmap='gray' + im = blocks[i].squeeze().permute((1, 2, 0)).numpy() + + # TODO: Replace line with permanent normalize(), denormalize() updates based on IMAGENET_MEAN/STD + img_n = cv2.normalize(src=im, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U) + # TODO: Replace line with permanent normalize(), denormalize() updates based on IMAGENET_MEAN/STD + + ax[i].imshow(img_n) ax[i].axis('off') if labels is not None: s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') @@ -309,7 +319,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run - resize = torch.nn.Upsample(size=(opt.img_size, opt.img_size), mode='bilinear', align_corners=False) # image resize + resize = torch.nn.Upsample(size=(opt.imgsz, opt.imgsz), mode='bilinear', align_corners=False) # image resize # Train train() diff --git a/utils/augmentations.py b/utils/augmentations.py index 3f764c06ae3b..adbec3cfff65 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -8,10 +8,14 @@ import cv2 import numpy as np +import torchvision.transforms as T from utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box from utils.metrics import bbox_ioa +IMAGENET_MEAN = 0.485, 0.456, 0.406 # RGB mean +IMAGENET_STD = 0.229, 0.224, 0.225 # RGB standard deviation + class Albumentations: # YOLOv5 Albumentations class (optional, only used if package is installed) @@ -282,3 +286,48 @@ def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16): w2, h2 = box2[2] - box2[0], box2[3] - box2[1] ar = np.maximum(w2 / (h2 + eps), h2 / (w2 + eps)) # aspect ratio return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr) # candidates + + +def classify_albumentations(augment=True, + size=224, + scale=(0.08, 1.0), + hflip=0.5, + vflip=0.0, + jitter=0.4, + mean=IMAGENET_MEAN, + std=IMAGENET_STD, + auto_aug=False): + # YOLOv5 classification Albumentations (optional, only used if package is installed) + try: + import albumentations as A + from albumentations.pytorch import ToTensorV2 + check_version(A.__version__, '1.0.3', hard=True) # version requirement + if augment: # Resize and crop + T = [A.RandomResizedCrop(height=size, width=size, scale=scale)] + if auto_aug: + # TODO: implement AugMix, AutoAug & RandAug in albumentation + LOGGER.info(colorstr('augmentations: ') + 'auto augmentations are currently not supported') + else: + if hflip > 0: + T += [A.HorizontalFlip(p=hflip)] + if vflip > 0: + T += [A.VerticalFlip(p=vflip)] + if jitter > 0: + color_jitter = (float(jitter),) * 3 # repeat value for brightness, contrast, satuaration, 0 hue + T += [A.ColorJitter(*color_jitter, 0)] + else: # Use fixed crop for eval set (reproducibility) + T = [A.SmallestMaxSize(max_size=size), A.CenterCrop(height=size, width=size)] + T += [A.Normalize(mean=mean, std=std), ToTensorV2()] # Normalize and convert to Tensor + + LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in T if x.p)) + return A.Compose(T) + + except ImportError: # package not installed, skip + pass + except Exception as e: + LOGGER.info(colorstr('albumentations: ') + f'{e}') + + +def classify_transforms(): + # Transforms to apply if albumentations not installed + return T.Compose([T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 4690f0c2e81e..5adc5914ad28 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -21,12 +21,15 @@ import numpy as np import torch import torch.nn.functional as F +import torchvision +import torchvision.transforms as T import yaml from PIL import ExifTags, Image, ImageOps from torch.utils.data import DataLoader, Dataset, dataloader, distributed from tqdm import tqdm -from utils.augmentations import Albumentations, augment_hsv, copy_paste, letterbox, mixup, random_perspective +from utils.augmentations import (Albumentations, augment_hsv, classify_albumentations, classify_transforms, copy_paste, + letterbox, mixup, random_perspective) from utils.general import (DATASETS_DIR, LOGGER, NUM_THREADS, check_dataset, check_requirements, check_yaml, clean_str, cv2, segments2boxes, xyn2xy, xywh2xyxy, xywhn2xyxy, xyxy2xywhn) from utils.torch_utils import torch_distributed_zero_first @@ -1085,3 +1088,43 @@ def _hub_ops(f, max_dim=1920): if verbose: print(json.dumps(stats, indent=2, sort_keys=False)) return stats + + +# Classification dataloaders ------------------------------------------------------------------------------------------- +class ClassificationDataset(torchvision.datasets.ImageFolder): + """ + YOLOv5 Classification Dataset. + Arguments + root: Dataset path + transform: torchvision transforms, used by default + album_transform: Albumentations transforms, used if installed + """ + + def __init__(self, root, torch_transforms, album_transforms=None): + super().__init__(root=root) + self.torch_transforms = torch_transforms + self.album_transforms = album_transforms + + def __getitem__(self, idx): + path, target = self.samples[idx] + if self.album_transforms: + sample = self.album_transforms(image=cv2.imread(path)[..., ::-1])["image"] + else: + sample = self.torch_transforms(self.loader(path)) + return sample, target + + +def create_classification_dataloader( + path, + imgsz=224, + batch_size=16, + augment=True, + cache=False, # TODO + rank=-1, + workers=8, + shuffle=True): + # Returns Dataloader object to be used with YOLOv5 Classifier + dataset = ClassificationDataset(root=path, + torch_transforms=classify_transforms(), + album_transforms=classify_albumentations(augment, imgsz) if augment else None) + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=workers), dataset From 0c75407a67c414fd190af17a63c3b0de8a4388fb Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Mon, 27 Jun 2022 16:41:39 +0530 Subject: [PATCH 059/349] [Classifier]: Replace print with LOGGER (#8348) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * replace print with logger Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/classifier.py b/classifier.py index c4e8e6340ee8..2ec44f679d8f 100644 --- a/classifier.py +++ b/classifier.py @@ -39,7 +39,7 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from models.common import Classify, DetectMultiBackend -from utils.general import (NUM_THREADS, check_file, check_git_status, check_requirements, colorstr, download, +from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, colorstr, download, increment_path) from utils.torch_utils import de_parallel, model_info, select_device @@ -82,7 +82,7 @@ def train(): names = trainset.classes nc = len(names) - print(f'Training {opt.model} on {data} dataset with {nc} classes...') + LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') # Show images images, labels = iter(trainloader).next() @@ -133,11 +133,11 @@ def train(): criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 # scaler = amp.GradScaler(enabled=cuda) - print(f'Image sizes {imgsz} train, {imgsz} test\n' - f'Using {nw} dataloader workers\n' - f"Logging results to {colorstr('bold', save_dir)}\n" - f'Starting training for {epochs} epochs...\n\n' - f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") + LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' + f'Using {nw} dataloader workers\n' + f"Logging results to {colorstr('bold', save_dir)}\n" + f'Starting training for {epochs} epochs...\n\n' + f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times mloss = 0.0 # mean loss model.train() @@ -190,7 +190,7 @@ def train(): # Train complete if final_epoch: - print(f'Training complete. Results saved to {save_dir}.') + LOGGER.info(f'Training complete. Results saved to {save_dir}.') # Show predictions images, labels = iter(testloader).next() @@ -219,11 +219,11 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): accuracy = correct.mean().item() if verbose: # all classes - print(f"{'class':10s}{'number':10s}{'accuracy':10s}") - print(f"{'all':10s}{correct.shape[0]:10s}{accuracy:10.5g}") + LOGGER.info(f"{'class':10s}{'number':10s}{'accuracy':10s}") + LOGGER.info(f"{'all':10s}{correct.shape[0]:10s}{accuracy:10.5g}") for i, c in enumerate(names): t = correct[targets == i] - print(f"{c:10s}{t.shape[0]:10s}{t.mean().item():10.5g}") + LOGGER.info(f"{c:10s}{t.shape[0]:10s}{t.mean().item():10.5g}") return accuracy @@ -246,7 +246,7 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False results = model(im) p = F.softmax(results, dim=1) # probabilities i = p.argmax() # max index - print(f'{file} prediction: {i} ({p[0, i]:.2f})') + LOGGER.info(f'{file} prediction: {i} ({p[0, i]:.2f})') # Plot if plot: @@ -282,12 +282,12 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa plt.savefig(f, dpi=300, bbox_inches='tight') plt.close() - print(colorstr('imshow: ') + f"examples saved to {f}") + LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") if verbose and labels is not None: - print('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) + LOGGER.info('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) if verbose and pred is not None: - print('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) + LOGGER.info('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) if __name__ == '__main__': From 48be7aaf141556c257eb89727af3ac852792a274 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 27 Jun 2022 19:55:21 +0200 Subject: [PATCH 060/349] [Classifier]: Fix `normalize()` and `denormalize()` for ImageNet stats (#8365) * Update functions * Revert Usage change * Cleanup --- classifier.py | 37 ++++++++++++++----------------------- utils/augmentations.py | 13 +++++++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/classifier.py b/classifier.py index e9e1a914dfde..577b67f9e375 100644 --- a/classifier.py +++ b/classifier.py @@ -3,7 +3,7 @@ Train a YOLOv5 classifier model on a classification dataset Usage - train: - $ python classifier.py --model yolov5n --data mnist --epochs 1 --img 128 + $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 Usage - inference: from classifier import * @@ -23,6 +23,7 @@ from pathlib import Path import torch +import torch.hub as hub import torch.nn as nn import torch.optim as optim import torch.optim.lr_scheduler as lr_scheduler @@ -30,8 +31,6 @@ from torch.cuda import amp from tqdm import tqdm -from utils.dataloaders import create_classification_dataloader - FILE = Path(__file__).resolve() ROOT = FILE.parents[0] # YOLOv5 root directory if str(ROOT) not in sys.path: @@ -39,6 +38,8 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from models.common import Classify, DetectMultiBackend +from utils.augmentations import denormalize, normalize +from utils.dataloaders import create_classification_dataloader from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, colorstr, download, increment_path) from utils.torch_utils import de_parallel, model_info, select_device @@ -46,10 +47,6 @@ WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) -# Functions -normalize = lambda x, mean=0.449, std=0.226: (x - mean) / std # TODO: replace with IMAGENET_MEAN, IMAGENET_STD -denormalize = lambda x, mean=0.449, std=0.226: x * std + mean - def train(): save_dir, data, bs, epochs, nw, imgsz, pretrained = \ @@ -91,7 +88,7 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = torch.hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False) + model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=True) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone @@ -102,8 +99,8 @@ def train(): model.model[-1] = c # replace for p in model.parameters(): p.requires_grad = True # for training - elif opt.model in torch.hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 - model = torch.hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=pretrained) + elif opt.model in hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 + model = hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=pretrained) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision model = torchvision.models.__dict__[opt.model](pretrained=pretrained) @@ -240,17 +237,17 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False im = cv2.imread(str(file))[..., ::-1] # HWC, BGR to RGB im = np.ascontiguousarray(np.asarray(im).transpose((2, 0, 1))) # HWC to CHW im = torch.tensor(im).float().unsqueeze(0) / 255.0 # to Tensor, to BCWH, rescale - im = resize(normalize(im)) + im = resize(im) # Inference - results = model(im) + results = model(normalize(im)) p = F.softmax(results, dim=1) # probabilities i = p.argmax() # max index LOGGER.info(f'{file} prediction: {i} ({p[0, i]:.2f})') # Plot if plot: - imshow(denormalize(im), f=Path(file).name) + imshow(im, f=Path(file).name) return p @@ -268,13 +265,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa ax = ax.ravel() if m > 1 else [ax] plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): - im = blocks[i].squeeze().permute((1, 2, 0)).numpy() - - # TODO: Replace line with permanent normalize(), denormalize() updates based on IMAGENET_MEAN/STD - img_n = cv2.normalize(src=im, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U) - # TODO: Replace line with permanent normalize(), denormalize() updates based on IMAGENET_MEAN/STD - - ax[i].imshow(img_n) + ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) ax[i].axis('off') if labels is not None: s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') @@ -285,9 +276,9 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") if verbose and labels is not None: - LOGGER.info('True: ', ' '.join(f'{names[i]:3s}' for i in labels)) + LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels)) if verbose and pred is not None: - LOGGER.info('Predicted:', ' '.join(f'{names[i]:3s}' for i in pred)) + LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred)) if __name__ == '__main__': @@ -295,7 +286,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') - parser.add_argument('--epochs', type=int, default=20) + parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') diff --git a/utils/augmentations.py b/utils/augmentations.py index adbec3cfff65..9ff3b5ec2f07 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -9,6 +9,7 @@ import cv2 import numpy as np import torchvision.transforms as T +import torchvision.transforms.functional as TF from utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box from utils.metrics import bbox_ioa @@ -48,6 +49,18 @@ def __call__(self, im, labels, p=1.0): return im, labels +def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): + # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = (x - mean) / std + return TF.normalize(x, mean, std, inplace=True) + + +def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): + # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = x * std + mean + for i in range(3): + x[:, i] = x[:, i] * std[i] + mean[i] + return x + + def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5): # HSV color-space augmentation if hgain or sgain or vgain: From b8dd84d26d7580c5cecfb3c09f7e3cc348814802 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Mon, 27 Jun 2022 23:48:56 +0530 Subject: [PATCH 061/349] [Classifier]: Add metric logging (#8364) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add logging support * Add training time printout * Cleanup Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 11 ++++++++++- utils/loggers/__init__.py | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 577b67f9e375..c35292a5a078 100644 --- a/classifier.py +++ b/classifier.py @@ -18,6 +18,7 @@ import math import os import sys +import time from copy import deepcopy from datetime import datetime from pathlib import Path @@ -42,6 +43,7 @@ from utils.dataloaders import create_classification_dataloader from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, colorstr, download, increment_path) +from utils.loggers import GenericLogger from utils.torch_utils import de_parallel, model_info, select_device WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) @@ -57,6 +59,8 @@ def train(): wdir.mkdir(parents=True, exist_ok=True) # make dir last, best = wdir / 'last.pt', wdir / 'best.pt' + # Logger + logger = GenericLogger(opt=opt, console_logger=LOGGER) # Download Dataset data_dir = FILE.parents[1] / 'datasets' / data if not data_dir.is_dir(): @@ -126,6 +130,7 @@ def train(): # final_div_factor=1 / 25 / lrf) # Train + t0 = time.time() model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 @@ -169,6 +174,9 @@ def train(): if fitness > best_fitness: best_fitness = fitness + # Log metrics + logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}) + # Save model final_epoch = epoch + 1 == epochs if (not opt.nosave) or final_epoch: @@ -187,7 +195,8 @@ def train(): # Train complete if final_epoch: - LOGGER.info(f'Training complete. Results saved to {save_dir}.') + LOGGER.info(f'\nTraining complete {(time.time() - t0) / 3600:.3f} hours.' + f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions images, labels = iter(testloader).next() diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 42b696ba644f..93877d0cd3f4 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -185,3 +185,43 @@ def on_params_update(self, params): # params: A dict containing {param: value} pairs if self.wandb: self.wandb.wandb_run.config.update(params, allow_val_change=True) + + +class GenericLogger: + """ + YOLOv5 General purpose logger for non-task specific logging + Usage: from utils.loggers import GenericLogger; logger = GenericLogger(...) + Arguments + opt: Run arguments + console_logger: Console logger + include: loggers to include + """ + + def __init__(self, opt, console_logger, include=('tb', 'wandb')): + # init default loggers + self.save_dir = opt.save_dir + self.include = include + self.console_logger = console_logger + if 'tb' in self.include: + prefix = colorstr('TensorBoard: ') + self.console_logger.info( + f"{prefix}Start with 'tensorboard --logdir {self.save_dir.parent}', view at http://localhost:6006/") + self.tb = SummaryWriter(str(self.save_dir)) + + if wandb and 'wandb' in self.include: + self.wandb = wandb.init(project="YOLOv5-Classifier" if opt.project == "runs/train" else opt.project, + name=None if opt.name == "exp" else opt.name, + config=opt) + else: + self.wandb = None + + def log_metrics(self, metrics_dict): + """ + Log metrics dictionary to initialized loggers + """ + if self.wandb: + self.wandb.log(metrics_dict) + + if self.tb: + for k, v in metrics_dict.items(): + self.tb.add_scalar(k, v) From a91d342bddf15c3ffc92079e4359b841690f1345 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Wed, 29 Jun 2022 16:18:23 +0530 Subject: [PATCH 062/349] [Classifier] Fix test set augmentations (#8385) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add logging support * Add training time printout * Cleanup * fix: test set not sugmented if album is installed Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index d0a43f434aa5..953cde748e62 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1133,5 +1133,5 @@ def create_classification_dataloader( # Returns Dataloader object to be used with YOLOv5 Classifier dataset = ClassificationDataset(root=path, torch_transforms=classify_transforms(), - album_transforms=classify_albumentations(augment, imgsz) if augment else None) + album_transforms=classify_albumentations(augment, imgsz)) return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=workers), dataset From c7b1a3c6c3f1f40b0fd97576f6dfdcc57bcacd66 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Wed, 29 Jun 2022 22:30:08 +0530 Subject: [PATCH 063/349] [Classifier]: Progress bar for validation (#8387) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add logging support * Add training time printout * Cleanup * fix: test set not sugmented if album is installed * support progress bar for testing * revert unrelated changes * fix tqdm colab issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Single line * build on previous description * add validating note Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index c35292a5a078..912815107604 100644 --- a/classifier.py +++ b/classifier.py @@ -208,8 +208,11 @@ def train(): def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 + n = len(dataloader) # number of batches with torch.no_grad(): - for images, labels in dataloader: + desc = f'{pbar.desc}validating' + bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) + for images, labels in bar: images, labels = resize(images.to(device)), labels.to(device) y = model(images) pred.append(torch.max(y, 1)[1]) @@ -221,7 +224,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): correct = (targets == pred).float() if pbar: - pbar.desc += f"{loss / len(dataloader):<12.3g}{correct.mean().item():<12.3g}" + pbar.desc += f"{loss / n:<12.3g}{correct.mean().item():<12.3g}" accuracy = correct.mean().item() if verbose: # all classes From 566f9ba97f49935d6266fd2d9406fd240a940fe7 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Fri, 1 Jul 2022 00:51:43 +0530 Subject: [PATCH 064/349] [Classifier]: DDP support (#8398) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add logging support * Add training time printout * Cleanup * fix: test set not sugmented if album is installed * support progress bar for testing * revert unrelated changes * fix tqdm colab issue * Support DDP * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update * globals match train.py * shuffle vs sampler fix * move model to device earlier * comment model info * Add DDP-required --local_rank arg * Optimizer before DDP * Scheduler before DDP * Add DEBUG lines * Add DEBUG lines * Add DEBUG lines * Force reload = False * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add debug RANK * testloader rank=-1 * pbar fix * fitness into RANK -1 0 * fix final epoch referenced before assignment * Remove DEBUG comments * Restore model_info * Restore model_info * Restore model_info Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 132 +++++++++++++++++++++++++++---------------- utils/dataloaders.py | 18 ++++-- 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/classifier.py b/classifier.py index 912815107604..f757b9cfef87 100644 --- a/classifier.py +++ b/classifier.py @@ -24,12 +24,14 @@ from pathlib import Path import torch +import torch.distributed as dist import torch.hub as hub import torch.nn as nn import torch.optim as optim import torch.optim.lr_scheduler as lr_scheduler import torchvision from torch.cuda import amp +from torch.nn.parallel import DistributedDataParallel as DDP from tqdm import tqdm FILE = Path(__file__).resolve() @@ -41,13 +43,14 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, colorstr, download, - increment_path) +from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, check_version, + colorstr, download, increment_path) from utils.loggers import GenericLogger -from utils.torch_utils import de_parallel, model_info, select_device +from utils.torch_utils import de_parallel, model_info, select_device, torch_distributed_zero_first +LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html +RANK = int(os.getenv('RANK', -1)) WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) -LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) def train(): @@ -60,7 +63,7 @@ def train(): last, best = wdir / 'last.pt', wdir / 'best.pt' # Logger - logger = GenericLogger(opt=opt, console_logger=LOGGER) + logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None # Download Dataset data_dir = FILE.parents[1] / 'datasets' / data if not data_dir.is_dir(): @@ -70,16 +73,18 @@ def train(): # Dataloaders trainloader, trainset = create_classification_dataloader(path=data_dir / 'train', imgsz=imgsz, - batch_size=bs, + batch_size=bs // WORLD_SIZE, augment=True, rank=LOCAL_RANK, workers=nw) - testloader, testset = create_classification_dataloader(path=data_dir / 'test', - imgsz=imgsz, - batch_size=bs, - augment=False, - rank=LOCAL_RANK, - workers=nw) + + if RANK in {-1, 0}: + testloader, testset = create_classification_dataloader(path=data_dir / 'test', + imgsz=imgsz, + batch_size=bs // WORLD_SIZE * 2, + augment=False, + rank=-1, + workers=nw) names = trainset.classes nc = len(names) @@ -92,7 +97,7 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=True) + model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=False) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone @@ -109,9 +114,9 @@ def train(): else: # try torchvision model = torchvision.models.__dict__[opt.model](pretrained=pretrained) model.fc = nn.Linear(model.fc.weight.shape[1], nc) - - # print(model) # debug - model_info(model) + model = model.to(device) + if RANK in {-1, 0}: + model_info(model) # print(model) # Optimizer lr0 = 0.0001 * bs # intial lr @@ -129,9 +134,15 @@ def train(): # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, # final_div_factor=1 / 25 / lrf) + # DDP mode + if cuda and RANK != -1: + if check_version(torch.__version__, '1.11.0'): + model = DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK, static_graph=True) + else: + model = DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) + # Train t0 = time.time() - model = model.to(device) criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 # scaler = amp.GradScaler(enabled=cuda) @@ -143,9 +154,13 @@ def train(): for epoch in range(epochs): # loop over the dataset multiple times mloss = 0.0 # mean loss model.train() - pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') + if RANK != -1: + trainloader.sampler.set_epoch(epoch) + pbar = enumerate(trainloader) + if RANK in {-1, 0}: + pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') for i, (images, labels) in pbar: # progress bar - images, labels = resize(images.to(device)), labels.to(device) + images, labels = images.to(device), labels.to(device) # Forward with amp.autocast(enabled=False): # stability issues when enabled @@ -159,48 +174,51 @@ def train(): optimizer.zero_grad() # Print - mloss += loss.item() - mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss / (i + 1):<12.3g}" + if RANK in {-1, 0}: + mloss += loss.item() + mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) + pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss / (i + 1):<12.3g}" # Test - if i == len(pbar) - 1: + if RANK in {-1, 0} and i == len(pbar) - 1: fitness = test(model, testloader, names, criterion, pbar=pbar) # test # Scheduler scheduler.step() - # Best fitness - if fitness > best_fitness: - best_fitness = fitness - # Log metrics - logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}) - - # Save model - final_epoch = epoch + 1 == epochs - if (not opt.nosave) or final_epoch: - ckpt = { - 'epoch': epoch, - 'best_fitness': best_fitness, - 'model': deepcopy(de_parallel(model)).half(), - 'optimizer': None, # optimizer.state_dict() - 'date': datetime.now().isoformat()} - - # Save last, best and delete - torch.save(ckpt, last) - if best_fitness == fitness: - torch.save(ckpt, best) - del ckpt + if RANK in {-1, 0}: + # Best fitness + if fitness > best_fitness: + best_fitness = fitness + + # Log + logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}) + + # Save model + final_epoch = epoch + 1 == epochs + if (not opt.nosave) or final_epoch: + ckpt = { + 'epoch': epoch, + 'best_fitness': best_fitness, + 'model': deepcopy(de_parallel(model)).half(), + 'optimizer': None, # optimizer.state_dict() + 'date': datetime.now().isoformat()} + + # Save last, best and delete + torch.save(ckpt, last) + if best_fitness == fitness: + torch.save(ckpt, best) + del ckpt # Train complete - if final_epoch: + if RANK in {-1, 0} and final_epoch: LOGGER.info(f'\nTraining complete {(time.time() - t0) / 3600:.3f} hours.' f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions images, labels = iter(testloader).next() - images = resize(images.to(device)) + images = images.to(device) pred = torch.max(model(images), 1)[1] imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') @@ -213,7 +231,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): desc = f'{pbar.desc}validating' bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) for images, labels in bar: - images, labels = resize(images.to(device)), labels.to(device) + images, labels = images.to(device), labels.to(device) y = model(images) pred.append(torch.max(y, 1)[1]) targets.append(labels) @@ -311,18 +329,34 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') + parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') opt = parser.parse_args() # Checks - check_git_status() - check_requirements() + if RANK in {-1, 0}: + check_git_status() + check_requirements() # Parameters device = select_device(opt.device, batch_size=opt.batch_size) cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run + # TODO: Remove resize as redundant with augmentations resize = torch.nn.Upsample(size=(opt.imgsz, opt.imgsz), mode='bilinear', align_corners=False) # image resize + # DDP + if LOCAL_RANK != -1: + msg = 'is not compatible with YOLOv5 Multi-GPU DDP training' + # TODO assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size' + assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE' + assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command' + torch.cuda.set_device(LOCAL_RANK) + device = torch.device('cuda', LOCAL_RANK) + dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo") + # Train train() + if WORLD_SIZE > 1 and RANK == 0: + LOGGER.info('Destroying process group... ') + dist.destroy_process_group() diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 953cde748e62..972103b2f302 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1131,7 +1131,17 @@ def create_classification_dataloader( workers=8, shuffle=True): # Returns Dataloader object to be used with YOLOv5 Classifier - dataset = ClassificationDataset(root=path, - torch_transforms=classify_transforms(), - album_transforms=classify_albumentations(augment, imgsz)) - return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=workers), dataset + with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP + dataset = ClassificationDataset(root=path, + torch_transforms=classify_transforms(), + album_transforms=classify_albumentations(augment, imgsz)) + batch_size = min(batch_size, len(dataset)) + nd = torch.cuda.device_count() + nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) + sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) + return torch.utils.data.DataLoader(dataset, + batch_size=batch_size, + shuffle=shuffle and sampler is None, + num_workers=nw, + pin_memory=True, + sampler=sampler), dataset From 9aa7b6aad201719ce46275316cd675564adb8f55 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 30 Jun 2022 21:25:39 +0200 Subject: [PATCH 065/349] [Classifier]: Updated DDP Usage example (#8417) --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f757b9cfef87..5c4d15211c34 100644 --- a/classifier.py +++ b/classifier.py @@ -3,7 +3,8 @@ Train a YOLOv5 classifier model on a classification dataset Usage - train: - $ python path/to/classifier.py --model yolov5s --data mnist --epochs 5 --img 128 + $ python classifier.py --model yolov5s --data mnist --epochs 5 --img 128 + $ python -m torch.distributed.run --nproc_per_node 2 --master_port 1 classifier.py --ARGS --device 0,1 Usage - inference: from classifier import * From 6a0c013aa438fa5f2471d37628d34e517bed483b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 2 Jul 2022 15:50:27 +0200 Subject: [PATCH 066/349] [Classifier]: TensorBoard logging epoch fix (#8441) --- classifier.py | 2 +- utils/loggers/__init__.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 5c4d15211c34..8be26a200a83 100644 --- a/classifier.py +++ b/classifier.py @@ -194,7 +194,7 @@ def train(): best_fitness = fitness # Log - logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}) + logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}, epoch) # Save model final_epoch = epoch + 1 == epochs diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 93877d0cd3f4..c220d74c1bef 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -215,13 +215,11 @@ def __init__(self, opt, console_logger, include=('tb', 'wandb')): else: self.wandb = None - def log_metrics(self, metrics_dict): - """ - Log metrics dictionary to initialized loggers - """ + def log_metrics(self, metrics_dict, epoch): + # Log metrics dictionary to initialized loggers if self.wandb: self.wandb.log(metrics_dict) if self.tb: for k, v in metrics_dict.items(): - self.tb.add_scalar(k, v) + self.tb.add_scalar(k, v, epoch) From 858a1a3d585a3b6ae2ff0ca1f4dc7c2b0203abb1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 2 Jul 2022 16:02:22 +0200 Subject: [PATCH 067/349] [Classifier]: mean loss logging fix --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 8be26a200a83..ddba67604fae 100644 --- a/classifier.py +++ b/classifier.py @@ -176,9 +176,9 @@ def train(): # Print if RANK in {-1, 0}: - mloss += loss.item() + mloss = (mloss * i + loss.item()) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss / (i + 1):<12.3g}" + pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss:<12.3g}" # Test if RANK in {-1, 0} and i == len(pbar) - 1: From c5c0cfa95ca4e95684a611164bcab0bb47220498 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 2 Jul 2022 18:38:56 +0200 Subject: [PATCH 068/349] [Classifier]: add lr, val loss to logging (#8445) --- classifier.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/classifier.py b/classifier.py index ddba67604fae..c6669c3be1db 100644 --- a/classifier.py +++ b/classifier.py @@ -153,7 +153,7 @@ def train(): f'Starting training for {epochs} epochs...\n\n' f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") for epoch in range(epochs): # loop over the dataset multiple times - mloss = 0.0 # mean loss + tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() if RANK != -1: trainloader.sampler.set_epoch(epoch) @@ -176,13 +176,13 @@ def train(): # Print if RANK in {-1, 0}: - mloss = (mloss * i + loss.item()) / (i + 1) # update mean losses + tloss = (tloss * i + loss.item()) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{mloss:<12.3g}" + pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{tloss:<12.3g}" # Test if RANK in {-1, 0} and i == len(pbar) - 1: - fitness = test(model, testloader, names, criterion, pbar=pbar) # test + fitness, vloss = test(model, testloader, names, criterion, pbar=pbar) # test accuracy, loss # Scheduler scheduler.step() @@ -194,7 +194,8 @@ def train(): best_fitness = fitness # Log - logger.log_metrics({"Train_loss": mloss, "Accuracy": fitness}, epoch) + lr = optimizer.param_groups[0]['lr'] # learning rate + logger.log_metrics({"train/loss": tloss, "val/loss": vloss, "metrics/accuracy": fitness, "lr/0": lr}, epoch) # Save model final_epoch = epoch + 1 == epochs @@ -239,11 +240,12 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): if criterion: loss += criterion(y, labels) + loss /= n pred, targets = torch.cat(pred), torch.cat(targets) correct = (targets == pred).float() if pbar: - pbar.desc += f"{loss / n:<12.3g}{correct.mean().item():<12.3g}" + pbar.desc += f"{loss:<12.3g}{correct.mean().item():<12.3g}" accuracy = correct.mean().item() if verbose: # all classes @@ -253,7 +255,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): t = correct[targets == i] LOGGER.info(f"{c:10s}{t.shape[0]:10s}{t.mean().item():10.5g}") - return accuracy + return accuracy, loss def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False): From 5ac1deafd127613d00f37c1bb76c29c5b4642009 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 5 Jul 2022 14:00:06 +0200 Subject: [PATCH 069/349] [Classifier]: Use all layers up to 13 or 11 @AyushExel this updates the classifier models to use the full backbone. After this change I see a much more normal ~40% size reduction in YOLOv5s, from 7.2Mparams to 4.2Mparams on MNIST. We should test this branch first though on ImageNet before merging. --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c6669c3be1db..d506e60bac6e 100644 --- a/classifier.py +++ b/classifier.py @@ -101,7 +101,7 @@ def train(): model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=False) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone + model.model = model.model[:13] if opt.model.endswith('6') else model.model[:11] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else sum(x.in_channels for x in m.m) # ch into module c = Classify(ch, nc) # Classify() From b1c4ae0efa0ffe4809d2631dfb346284f4f227f2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 7 Jul 2022 14:29:12 +0200 Subject: [PATCH 070/349] Add init_seeds --- classifier.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c6669c3be1db..84ceb46ea7d7 100644 --- a/classifier.py +++ b/classifier.py @@ -45,7 +45,7 @@ from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, check_version, - colorstr, download, increment_path) + colorstr, download, increment_path, init_seeds) from utils.loggers import GenericLogger from utils.torch_utils import de_parallel, model_info, select_device, torch_distributed_zero_first @@ -65,6 +65,7 @@ def train(): # Logger logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None + # Download Dataset data_dir = FILE.parents[1] / 'datasets' / data if not data_dir.is_dir(): @@ -90,6 +91,7 @@ def train(): names = trainset.classes nc = len(names) LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') + init_seeds(1 + RANK) # Show images images, labels = iter(trainloader).next() From 52aef2cc0e4dfd3f72e1cc294be70125d7edbf3e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 12 Jul 2022 14:40:36 +0200 Subject: [PATCH 071/349] Add try except with hub load --- classifier.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 84ceb46ea7d7..0edc4a59f60b 100644 --- a/classifier.py +++ b/classifier.py @@ -100,7 +100,10 @@ def train(): # Model if opt.model.startswith('yolov5'): # YOLOv5 Classifier - model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=False) + try: + model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False) + except Exception: + model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=True) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone From c54631ceb30a50d8a31b00bd7645e1b77ccc1eff Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 12 Jul 2022 15:24:01 +0200 Subject: [PATCH 072/349] Add --cache --- classifier.py | 4 +++- utils/dataloaders.py | 25 +++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/classifier.py b/classifier.py index 0edc4a59f60b..7036b197b95d 100644 --- a/classifier.py +++ b/classifier.py @@ -77,6 +77,7 @@ def train(): imgsz=imgsz, batch_size=bs // WORLD_SIZE, augment=True, + cache=opt.cache, rank=LOCAL_RANK, workers=nw) @@ -85,6 +86,7 @@ def train(): imgsz=imgsz, batch_size=bs // WORLD_SIZE * 2, augment=False, + cache=opt.cache, rank=-1, workers=nw) @@ -330,7 +332,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='Adam', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') - parser.add_argument('--cache-images', action='store_true', help='cache images for faster training') + parser.add_argument('--cache', action='store_true', help='--cache images to disk for faster training') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 9b3becaf7bbd..f31367e315a2 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1109,18 +1109,26 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): album_transform: Albumentations transforms, used if installed """ - def __init__(self, root, torch_transforms, album_transforms=None): + def __init__(self, root, torch_transforms, album_transforms=None, cache=False): super().__init__(root=root) self.torch_transforms = torch_transforms self.album_transforms = album_transforms + self.cache = cache - def __getitem__(self, idx): - path, target = self.samples[idx] + def __getitem__(self, i): + f, j = self.samples[i] # filename, index if self.album_transforms: - sample = self.album_transforms(image=cv2.imread(path)[..., ::-1])["image"] + if self.cache: + fn = Path(f).with_suffix('.npy') # filename numpy + if not fn.exists(): # load npy + np.save(fn.as_posix(), cv2.imread(f)) + im = np.load(fn) + else: # read image + im = cv2.imread(f) # BGR + sample = self.album_transforms(image=im[..., ::-1])["image"] else: - sample = self.torch_transforms(self.loader(path)) - return sample, target + sample = self.torch_transforms(self.loader(f)) + return sample, j def create_classification_dataloader( @@ -1128,7 +1136,7 @@ def create_classification_dataloader( imgsz=224, batch_size=16, augment=True, - cache=False, # TODO + cache=False, rank=-1, workers=8, shuffle=True): @@ -1136,7 +1144,8 @@ def create_classification_dataloader( with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP dataset = ClassificationDataset(root=path, torch_transforms=classify_transforms(), - album_transforms=classify_albumentations(augment, imgsz)) + album_transforms=classify_albumentations(augment, imgsz), + cache=cache) batch_size = min(batch_size, len(dataset)) nd = torch.cuda.device_count() nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) From e995dadc9a162586e53d8b113e4f44f92cc9ae2a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 12 Jul 2022 16:14:10 +0200 Subject: [PATCH 073/349] Add --cache ram --- classifier.py | 2 +- utils/dataloaders.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 7036b197b95d..5da05b1377dd 100644 --- a/classifier.py +++ b/classifier.py @@ -332,7 +332,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='Adam', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') - parser.add_argument('--cache', action='store_true', help='--cache images to disk for faster training') + parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') parser.add_argument('--project', default='runs/train', help='save to project/name') diff --git a/utils/dataloaders.py b/utils/dataloaders.py index f31367e315a2..9ff834c4d34d 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1113,12 +1113,16 @@ def __init__(self, root, torch_transforms, album_transforms=None, cache=False): super().__init__(root=root) self.torch_transforms = torch_transforms self.album_transforms = album_transforms - self.cache = cache + self.cache_ram = cache is True or cache == 'ram' + self.cache_disk = cache == 'disk' + self.samples = [list(x) + [None, None] for x in self.samples] # filename, index, filename_npy, image def __getitem__(self, i): - f, j = self.samples[i] # filename, index + f, j, fn, im = self.samples[i] # filename, index, filename_npy, image if self.album_transforms: - if self.cache: + if self.cache_ram and im is None: + im = self.samples[i][3] = cv2.imread(f) + elif self.cache_disk: fn = Path(f).with_suffix('.npy') # filename numpy if not fn.exists(): # load npy np.save(fn.as_posix(), cv2.imread(f)) From c890d3f16315dab2db80d55e5d485d872ed5bb42 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 12 Jul 2022 16:24:07 +0200 Subject: [PATCH 074/349] Add --cache ram --- utils/dataloaders.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 9ff834c4d34d..29eaa916f2ef 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -86,7 +86,7 @@ def exif_transpose(image): 5: Image.TRANSPOSE, 6: Image.ROTATE_270, 7: Image.TRANSVERSE, - 8: Image.ROTATE_90,}.get(orientation) + 8: Image.ROTATE_90, }.get(orientation) if method is not None: image = image.transpose(method) del exif[0x0112] @@ -1115,7 +1115,7 @@ def __init__(self, root, torch_transforms, album_transforms=None, cache=False): self.album_transforms = album_transforms self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' - self.samples = [list(x) + [None, None] for x in self.samples] # filename, index, filename_npy, image + self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im def __getitem__(self, i): f, j, fn, im = self.samples[i] # filename, index, filename_npy, image @@ -1123,7 +1123,7 @@ def __getitem__(self, i): if self.cache_ram and im is None: im = self.samples[i][3] = cv2.imread(f) elif self.cache_disk: - fn = Path(f).with_suffix('.npy') # filename numpy + # fn = Path(f).with_suffix('.npy') # filename numpy if not fn.exists(): # load npy np.save(fn.as_posix(), cv2.imread(f)) im = np.load(fn) From e9dc67aeafe2743eb352d929a462a04be4727dba Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 12 Jul 2022 23:51:03 +0200 Subject: [PATCH 075/349] Add get_imagenet.sh --- data/scripts/get_imagenet.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 data/scripts/get_imagenet.sh diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh new file mode 100644 index 000000000000..5c5cb15d0fe1 --- /dev/null +++ b/data/scripts/get_imagenet.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# YOLOv5 🚀 by Ultralytics, GPL-3.0 license +# Download ILSVRC2012 ImageNet dataset https://image-net.org +# Example usage: bash data/scripts/get_imagenet.sh +# parent +# ├── yolov5 +# └── datasets +# └── ImageNet ← downloads here + +## 1. Download the data +# get ILSVRC2012_img_val.tar (about 6.3 GB). MD5: 29b22e2961454d5413ddabcf34fc5622 +wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar +# get ILSVRC2012_img_train.tar (about 138 GB). MD5: 1d675b47d978889d74fa0da5fadfb00e +wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar + +## 2. Extract the training data: +mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train +tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar +find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done +cd .. + +## 3. Extract the validation data and move images to subfolders: +mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar +wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash + +## 4. Delete corrupted image (optional: (PNG under JPEG name that may cause some dataloaders to fail) +# rm train/n04266014/n04266014_10835.JPEG + +## 5. TFRecords (optional) +# wget https://raw.githubusercontent.com/tensorflow/models/master/research/slim/datasets/imagenet_lsvrc_2015_synsets.txt \ No newline at end of file From 9ce5254792593f242032e2a480e041a67c6da032 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 00:05:07 +0200 Subject: [PATCH 076/349] Update get_imagenet.sh --- data/scripts/get_imagenet.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index 5c5cb15d0fe1..d9c7747be455 100644 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -7,24 +7,24 @@ # └── datasets # └── ImageNet ← downloads here -## 1. Download the data +## 1. Download data # get ILSVRC2012_img_val.tar (about 6.3 GB). MD5: 29b22e2961454d5413ddabcf34fc5622 wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # get ILSVRC2012_img_train.tar (about 138 GB). MD5: 1d675b47d978889d74fa0da5fadfb00e wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar -## 2. Extract the training data: +## 2. Extract training data: mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done cd .. -## 3. Extract the validation data and move images to subfolders: +## 3. Extract validation data and move images to subfolders: mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash -## 4. Delete corrupted image (optional: (PNG under JPEG name that may cause some dataloaders to fail) +## 4. Delete corrupted image (optional: PNG under JPEG name that may cause some dataloaders to fail) # rm train/n04266014/n04266014_10835.JPEG ## 5. TFRecords (optional) -# wget https://raw.githubusercontent.com/tensorflow/models/master/research/slim/datasets/imagenet_lsvrc_2015_synsets.txt \ No newline at end of file +# wget https://raw.githubusercontent.com/tensorflow/models/master/research/slim/datasets/imagenet_lsvrc_2015_synsets.txt From 892a6a4113e1c72334a4cbc5376092a9e2a36da8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 13:53:05 +0200 Subject: [PATCH 077/349] Update get_imagenet.sh --- data/scripts/get_imagenet.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index d9c7747be455..c3fc7852b5b9 100644 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -5,26 +5,30 @@ # parent # ├── yolov5 # └── datasets -# └── ImageNet ← downloads here +# └── imagenet ← downloads here -## 1. Download data -# get ILSVRC2012_img_val.tar (about 6.3 GB). MD5: 29b22e2961454d5413ddabcf34fc5622 -wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar -# get ILSVRC2012_img_train.tar (about 138 GB). MD5: 1d675b47d978889d74fa0da5fadfb00e -wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar +# Download +d='./imagenet' # unzip directory +mkdir $d && cd $d +wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # 6.3G, 50000 images +wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # 138G, 1281167 images -## 2. Extract training data: +# Extract train mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar -find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done +find . -name "*.tar" | while read NAME; do + mkdir -p "${NAME%.tar}" + tar -xvf "${NAME}" -C "${NAME%.tar}" + rm -f "${NAME}" +done cd .. -## 3. Extract validation data and move images to subfolders: +# Extract val and move images into subdirectories mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash -## 4. Delete corrupted image (optional: PNG under JPEG name that may cause some dataloaders to fail) +# Delete corrupted image (optional: PNG under JPEG name that may cause dataloaders to fail) # rm train/n04266014/n04266014_10835.JPEG -## 5. TFRecords (optional) +# TFRecords (optional) # wget https://raw.githubusercontent.com/tensorflow/models/master/research/slim/datasets/imagenet_lsvrc_2015_synsets.txt From 0d8af080ba6f08e4664953a6ef9ed8fe87da434f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 14:31:37 +0200 Subject: [PATCH 078/349] add data/val option for imagenet --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 5da05b1377dd..84c486386a78 100644 --- a/classifier.py +++ b/classifier.py @@ -82,7 +82,8 @@ def train(): workers=nw) if RANK in {-1, 0}: - testloader, testset = create_classification_dataloader(path=data_dir / 'test', + test_dir = data_dir / ('test' if (data_dir / 'test').exists() else 'val') # data/test or data/val + testloader, testset = create_classification_dataloader(path=test_dir, imgsz=imgsz, batch_size=bs // WORLD_SIZE * 2, augment=False, From 6b15879fd7cac2c0f0fb57b043b4f4eeb89ae4f4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 14:33:14 +0200 Subject: [PATCH 079/349] add data/val option for imagenet --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 84c486386a78..891d4251d5cb 100644 --- a/classifier.py +++ b/classifier.py @@ -82,7 +82,7 @@ def train(): workers=nw) if RANK in {-1, 0}: - test_dir = data_dir / ('test' if (data_dir / 'test').exists() else 'val') # data/test or data/val + test_dir = data_dir / 'test' if (data_dir / 'test').exists() else data_dir / 'val' # data/test or data/val testloader, testset = create_classification_dataloader(path=test_dir, imgsz=imgsz, batch_size=bs // WORLD_SIZE * 2, From 66e0f96a1a8dc5df420a1ed5b20290ef7ed241a4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 14:43:20 +0200 Subject: [PATCH 080/349] update nw ceiling --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 29eaa916f2ef..ce5683de6ea1 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1152,7 +1152,7 @@ def create_classification_dataloader( cache=cache) batch_size = min(batch_size, len(dataset)) nd = torch.cuda.device_count() - nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) + nw = min([2 * os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) return torch.utils.data.DataLoader(dataset, batch_size=batch_size, From d4b319261f884a22448a1d50c022af007d659827 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 14:52:05 +0200 Subject: [PATCH 081/349] update nw ceiling --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 891d4251d5cb..d1e4acd83b1c 100644 --- a/classifier.py +++ b/classifier.py @@ -56,7 +56,7 @@ def train(): save_dir, data, bs, epochs, nw, imgsz, pretrained = \ - Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(NUM_THREADS, opt.workers), opt.imgsz, \ + Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), opt.imgsz, \ not opt.from_scratch # Directories wdir = save_dir / 'weights' From e4323a3150f094bb285cb3e0ac142f74367d413e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 14:56:04 +0200 Subject: [PATCH 082/349] update nw ceiling --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index ce5683de6ea1..29eaa916f2ef 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1152,7 +1152,7 @@ def create_classification_dataloader( cache=cache) batch_size = min(batch_size, len(dataset)) nd = torch.cuda.device_count() - nw = min([2 * os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) + nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) return torch.utils.data.DataLoader(dataset, batch_size=batch_size, From 45f7983c7a9395ad12b75086a96a513f639e1de8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 15:47:01 +0200 Subject: [PATCH 083/349] zero first dataset download --- classifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index d1e4acd83b1c..7b24d8d7255e 100644 --- a/classifier.py +++ b/classifier.py @@ -68,9 +68,10 @@ def train(): # Download Dataset data_dir = FILE.parents[1] / 'datasets' / data - if not data_dir.is_dir(): - url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' - download(url, dir=data_dir.parent) + with torch_distributed_zero_first(LOCAL_RANK): + if not data_dir.is_dir(): + url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' + download(url, dir=data_dir.parent) # Dataloaders trainloader, trainset = create_classification_dataloader(path=data_dir / 'train', From fd6228ff765837cc21defa1b99d3f68eed980d03 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:07:25 +0200 Subject: [PATCH 084/349] Add @torch.no_grad() decorator --- classifier.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/classifier.py b/classifier.py index 7b24d8d7255e..80dd605301ce 100644 --- a/classifier.py +++ b/classifier.py @@ -44,8 +44,8 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, NUM_THREADS, check_file, check_git_status, check_requirements, check_version, - colorstr, download, increment_path, init_seeds) +from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, colorstr, download, + increment_path, init_seeds) from utils.loggers import GenericLogger from utils.torch_utils import de_parallel, model_info, select_device, torch_distributed_zero_first @@ -234,20 +234,20 @@ def train(): imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') +@torch.no_grad() def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches - with torch.no_grad(): - desc = f'{pbar.desc}validating' - bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) - for images, labels in bar: - images, labels = images.to(device), labels.to(device) - y = model(images) - pred.append(torch.max(y, 1)[1]) - targets.append(labels) - if criterion: - loss += criterion(y, labels) + desc = f'{pbar.desc}validating' + bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) + for images, labels in bar: + images, labels = images.to(device), labels.to(device) + y = model(images) + pred.append(torch.max(y, 1)[1]) + targets.append(labels) + if criterion: + loss += criterion(y, labels) loss /= n pred, targets = torch.cat(pred), torch.cat(targets) @@ -267,6 +267,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return accuracy, loss +@torch.no_grad() def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False): # YOLOv5 classification model inference import cv2 From a7d58ec297f720bafb2424cb6fa4034509b74cf7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:19:03 +0200 Subject: [PATCH 085/349] torchvision default for val --- utils/augmentations.py | 4 ++-- utils/dataloaders.py | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index 9ff3b5ec2f07..8c79e820d758 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -341,6 +341,6 @@ def classify_albumentations(augment=True, LOGGER.info(colorstr('albumentations: ') + f'{e}') -def classify_transforms(): +def classify_transforms(size=224): # Transforms to apply if albumentations not installed - return T.Compose([T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) + return T.Compose([T.ToTensor(), T.Resize(size=size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 29eaa916f2ef..c11eadd2f7d3 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1109,10 +1109,10 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): album_transform: Albumentations transforms, used if installed """ - def __init__(self, root, torch_transforms, album_transforms=None, cache=False): + def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) - self.torch_transforms = torch_transforms - self.album_transforms = album_transforms + self.torch_transforms = None if augment else classify_transforms() + self.album_transforms = classify_albumentations(augment, imgsz) if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im @@ -1146,10 +1146,7 @@ def create_classification_dataloader( shuffle=True): # Returns Dataloader object to be used with YOLOv5 Classifier with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP - dataset = ClassificationDataset(root=path, - torch_transforms=classify_transforms(), - album_transforms=classify_albumentations(augment, imgsz), - cache=cache) + dataset = ClassificationDataset(root=path, imgsz=imgsz, augment=augment, cache=cache) batch_size = min(batch_size, len(dataset)) nd = torch.cuda.device_count() nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) From 7e84d07f80c6acafaf6b273ceb5f5da02f4f2544 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:22:10 +0200 Subject: [PATCH 086/349] torchvision default for val --- utils/dataloaders.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index c11eadd2f7d3..ca7eaffc7145 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1112,7 +1112,8 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = None if augment else classify_transforms() - self.album_transforms = classify_albumentations(augment, imgsz) if augment else None + self.album_transforms = classify_albumentations(augment, imgsz) if check_requirements('albumentations', ) and \ + augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im From ce97b673536e04491708f0635886a89c1d19429a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:24:08 +0200 Subject: [PATCH 087/349] torchvision default for val --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index ca7eaffc7145..7ab6049f60d8 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1112,7 +1112,7 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = None if augment else classify_transforms() - self.album_transforms = classify_albumentations(augment, imgsz) if check_requirements('albumentations', ) and \ + self.album_transforms = classify_albumentations(augment, imgsz) if check_requirements(('albumentations',)) and \ augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' From 75450c5523a70b70627a10c0cdab7ea6733d5b89 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:26:36 +0200 Subject: [PATCH 088/349] torchvision default for val --- utils/dataloaders.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 7ab6049f60d8..985e04cca797 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1111,9 +1111,8 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) - self.torch_transforms = None if augment else classify_transforms() - self.album_transforms = classify_albumentations(augment, imgsz) if check_requirements(('albumentations',)) and \ - augment else None + self.torch_transforms = classify_transforms() + self.album_transforms = classify_albumentations(augment, imgsz) if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im From d77d17fd337fefad93eb17baf9c419470d8d72a6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:44:23 +0200 Subject: [PATCH 089/349] print_args() --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 80dd605301ce..7ddb69d698e1 100644 --- a/classifier.py +++ b/classifier.py @@ -45,7 +45,7 @@ from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, colorstr, download, - increment_path, init_seeds) + increment_path, init_seeds, print_args) from utils.loggers import GenericLogger from utils.torch_utils import de_parallel, model_info, select_device, torch_distributed_zero_first @@ -347,6 +347,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa # Checks if RANK in {-1, 0}: + print_args(vars(opt)) check_git_status() check_requirements() From 54b7276fcb179d60744ad8859fa45677ba0d614b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 19:45:18 +0200 Subject: [PATCH 090/349] size, size --- utils/augmentations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index 8c79e820d758..a588eb9bd686 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -343,4 +343,4 @@ def classify_albumentations(augment=True, def classify_transforms(size=224): # Transforms to apply if albumentations not installed - return T.Compose([T.ToTensor(), T.Resize(size=size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) + return T.Compose([T.ToTensor(), T.Resize((size, size)), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) From 51c1822f8af649d8ebd59bc9e69583f1b4326c04 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 20:18:32 +0200 Subject: [PATCH 091/349] fix lr0 --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 7ddb69d698e1..fb51beeb1ff1 100644 --- a/classifier.py +++ b/classifier.py @@ -129,7 +129,7 @@ def train(): model_info(model) # print(model) # Optimizer - lr0 = 0.0001 * bs # intial lr + lr0 = 0.0001 * 128 # intial lr lrf = 0.01 # final lr (fraction of lr0) if opt.optimizer == 'Adam': optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) From 50a76b45938800aaf9a28ef0981dcc2ce57ebc40 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 20:36:44 +0200 Subject: [PATCH 092/349] fix lr0 for Adam --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index fb51beeb1ff1..94b3389cfb2c 100644 --- a/classifier.py +++ b/classifier.py @@ -3,8 +3,8 @@ Train a YOLOv5 classifier model on a classification dataset Usage - train: - $ python classifier.py --model yolov5s --data mnist --epochs 5 --img 128 - $ python -m torch.distributed.run --nproc_per_node 2 --master_port 1 classifier.py --ARGS --device 0,1 + $ python classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 + $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 4,5,6,7 Usage - inference: from classifier import * @@ -129,7 +129,7 @@ def train(): model_info(model) # print(model) # Optimizer - lr0 = 0.0001 * 128 # intial lr + lr0 = 0.0001 * (128 if opt.optimizer.starswith('Adam') else bs) # initial lr lrf = 0.01 # final lr (fraction of lr0) if opt.optimizer == 'Adam': optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) From 2db3b78da4cd18f4d0e072886c8bbd2f638a090a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 20:58:36 +0200 Subject: [PATCH 093/349] updates --- classifier.py | 53 ++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/classifier.py b/classifier.py index 94b3389cfb2c..5edb0c228c12 100644 --- a/classifier.py +++ b/classifier.py @@ -102,35 +102,36 @@ def train(): imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') # Model - if opt.model.startswith('yolov5'): - # YOLOv5 Classifier - try: - model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False) - except Exception: - model = hub.load('ultralytics/yolov5', opt.model, pretrained=pretrained, autoshape=False, force_reload=True) - if isinstance(model, DetectMultiBackend): - model = model.model # unwrap DetectMultiBackend - model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone - m = model.model[-1] # last layer - ch = m.conv.in_channels if hasattr(m, 'conv') else sum(x.in_channels for x in m.m) # ch into module - c = Classify(ch, nc) # Classify() - c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type - model.model[-1] = c # replace - for p in model.parameters(): - p.requires_grad = True # for training - elif opt.model in hub.list('rwightman/gen-efficientnet-pytorch'): # i.e. efficientnet_b0 - model = hub.load('rwightman/gen-efficientnet-pytorch', opt.model, pretrained=pretrained) - model.classifier = nn.Linear(model.classifier.in_features, nc) - else: # try torchvision - model = torchvision.models.__dict__[opt.model](pretrained=pretrained) - model.fc = nn.Linear(model.fc.weight.shape[1], nc) + repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' + with torch_distributed_zero_first(LOCAL_RANK): + if opt.model.startswith('yolov5'): # YOLOv5 Classifier + try: + model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False) + except Exception: + model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False, force_reload=True) + if isinstance(model, DetectMultiBackend): + model = model.model # unwrap DetectMultiBackend + model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone + m = model.model[-1] # last layer + ch = m.conv.in_channels if hasattr(m, 'conv') else sum(x.in_channels for x in m.m) # ch into module + c = Classify(ch, nc) # Classify() + c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type + model.model[-1] = c # replace + for p in model.parameters(): + p.requires_grad = True # for training + elif opt.model in hub.list(repo2): # i.e. efficientnet_b0 + model = hub.load(repo2, opt.model, pretrained=pretrained) + model.classifier = nn.Linear(model.classifier.in_features, nc) + else: # try torchvision + model = torchvision.models.__dict__[opt.model](pretrained=pretrained) + model.fc = nn.Linear(model.fc.weight.shape[1], nc) model = model.to(device) if RANK in {-1, 0}: model_info(model) # print(model) # Optimizer - lr0 = 0.0001 * (128 if opt.optimizer.starswith('Adam') else bs) # initial lr - lrf = 0.01 # final lr (fraction of lr0) + lr0 = 0.01 * (1 if opt.optimizer.starswith('Adam') else 0.01 * bs) # initial lr + lrf = 0.001 # final lr (fraction of lr0) if opt.optimizer == 'Adam': optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) elif opt.optimizer == 'AdamW': @@ -139,7 +140,8 @@ def train(): optimizer = optim.SGD(model.parameters(), lr=lr0, momentum=0.9, nesterov=True) # Scheduler - lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine + # lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine + lf = lambda x: (1 - x / epochs) * (1.0 - lrf) + lrf # linear scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, # final_div_factor=1 / 25 / lrf) @@ -297,7 +299,6 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) - import cv2 import matplotlib.pyplot as plt names = names or [f'class{i}' for i in range(1000)] From 479a9acadd7e1e04cd1a2002a3561592031e0fad Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 13 Jul 2022 21:01:01 +0200 Subject: [PATCH 094/349] updates --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 5edb0c228c12..7c3b9633ffdd 100644 --- a/classifier.py +++ b/classifier.py @@ -130,7 +130,7 @@ def train(): model_info(model) # print(model) # Optimizer - lr0 = 0.01 * (1 if opt.optimizer.starswith('Adam') else 0.01 * bs) # initial lr + lr0 = 0.01 * (1 if opt.optimizer.startswith('Adam') else 0.01 * bs) # initial lr lrf = 0.001 # final lr (fraction of lr0) if opt.optimizer == 'Adam': optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) From 1de0324a9f7cc3bf8e694eb4442c9c73c267bd65 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 14 Jul 2022 22:06:20 +0200 Subject: [PATCH 095/349] AMP --- classifier.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 7c3b9633ffdd..94c9d0d5a97d 100644 --- a/classifier.py +++ b/classifier.py @@ -157,7 +157,7 @@ def train(): t0 = time.time() criterion = nn.CrossEntropyLoss() # loss function best_fitness = 0.0 - # scaler = amp.GradScaler(enabled=cuda) + scaler = amp.GradScaler(enabled=cuda) LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" @@ -175,14 +175,15 @@ def train(): images, labels = images.to(device), labels.to(device) # Forward - with amp.autocast(enabled=False): # stability issues when enabled + with amp.autocast(enabled=cuda): # stability issues when enabled loss = criterion(model(images), labels) # Backward - loss.backward() # scaler.scale(loss).backward() + scaler.scale(loss).backward() # Optimize - optimizer.step() # scaler.step(optimizer); scaler.update() + scaler.step(optimizer) + scaler.update() optimizer.zero_grad() # Print From 9edcb727f8ba158c2a5c6835ed114663ca6b140b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 15 Jul 2022 15:47:07 +0200 Subject: [PATCH 096/349] cleanup --- classifier.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index 94c9d0d5a97d..e019010a18f9 100644 --- a/classifier.py +++ b/classifier.py @@ -3,7 +3,7 @@ Train a YOLOv5 classifier model on a classification dataset Usage - train: - $ python classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 + $ python classifier.py --model yolov5s --data mnist --epochs 5 --img 128 $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 4,5,6,7 Usage - inference: @@ -186,15 +186,15 @@ def train(): scaler.update() optimizer.zero_grad() - # Print if RANK in {-1, 0}: + # Print tloss = (tloss * i + loss.item()) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{tloss:<12.3g}" - # Test - if RANK in {-1, 0} and i == len(pbar) - 1: - fitness, vloss = test(model, testloader, names, criterion, pbar=pbar) # test accuracy, loss + # Test + if i == len(pbar) - 1: # last batch + fitness, vloss = test(model, testloader, names, criterion, pbar=pbar) # test accuracy, loss # Scheduler scheduler.step() From fd80e2ca1a30acc3d656562f1701f2f4fbd928e9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 15 Jul 2022 15:54:48 +0200 Subject: [PATCH 097/349] cleanup --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index e019010a18f9..4303724d2aa3 100644 --- a/classifier.py +++ b/classifier.py @@ -231,7 +231,7 @@ def train(): f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions - images, labels = iter(testloader).next() + images, labels = (x[:30] for x in iter(testloader).next()) # first 30 images and labels images = images.to(device) pred = torch.max(model(images), 1)[1] imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') From ad6898933d0dabc19c7b7d7a6cef285e64500f01 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 17:05:05 +0200 Subject: [PATCH 098/349] Add EMA --- classifier.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 4303724d2aa3..e1251ddff63f 100644 --- a/classifier.py +++ b/classifier.py @@ -47,7 +47,7 @@ from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, colorstr, download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import de_parallel, model_info, select_device, torch_distributed_zero_first +from utils.torch_utils import de_parallel, model_info, ModelEMA, select_device, torch_distributed_zero_first LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -129,6 +129,9 @@ def train(): if RANK in {-1, 0}: model_info(model) # print(model) + # EMA + ema = ModelEMA(model) if RANK in {-1, 0} else None + # Optimizer lr0 = 0.01 * (1 if opt.optimizer.startswith('Adam') else 0.01 * bs) # initial lr lrf = 0.001 # final lr (fraction of lr0) @@ -185,6 +188,8 @@ def train(): scaler.step(optimizer) scaler.update() optimizer.zero_grad() + if ema: + ema.update(model) if RANK in {-1, 0}: # Print @@ -194,7 +199,7 @@ def train(): # Test if i == len(pbar) - 1: # last batch - fitness, vloss = test(model, testloader, names, criterion, pbar=pbar) # test accuracy, loss + fitness, vloss = test(ema.ema, testloader, names, criterion, pbar=pbar) # test accuracy, loss # Scheduler scheduler.step() @@ -215,8 +220,10 @@ def train(): ckpt = { 'epoch': epoch, 'best_fitness': best_fitness, - 'model': deepcopy(de_parallel(model)).half(), - 'optimizer': None, # optimizer.state_dict() + 'model': deepcopy(ema.ema).half(), # deepcopy(de_parallel(model)).half(), + 'ema': None, # deepcopy(ema.ema).half(), + 'updates': ema.updates, + 'optimizer': None, # optimizer.state_dict(), 'date': datetime.now().isoformat()} # Save last, best and delete From 1730f54242d358b2904f1de61881fe39dbf3a53d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 17:17:10 +0200 Subject: [PATCH 099/349] Remove datasets return --- classifier.py | 37 +++++++++++++++++++------------------ utils/dataloaders.py | 2 +- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/classifier.py b/classifier.py index e1251ddff63f..365ced238870 100644 --- a/classifier.py +++ b/classifier.py @@ -74,26 +74,27 @@ def train(): download(url, dir=data_dir.parent) # Dataloaders - trainloader, trainset = create_classification_dataloader(path=data_dir / 'train', - imgsz=imgsz, - batch_size=bs // WORLD_SIZE, - augment=True, - cache=opt.cache, - rank=LOCAL_RANK, - workers=nw) + trainloader = create_classification_dataloader(path=data_dir / 'train', + imgsz=imgsz, + batch_size=bs // WORLD_SIZE, + augment=True, + cache=opt.cache, + rank=LOCAL_RANK, + workers=nw) if RANK in {-1, 0}: test_dir = data_dir / 'test' if (data_dir / 'test').exists() else data_dir / 'val' # data/test or data/val - testloader, testset = create_classification_dataloader(path=test_dir, - imgsz=imgsz, - batch_size=bs // WORLD_SIZE * 2, - augment=False, - cache=opt.cache, - rank=-1, - workers=nw) - - names = trainset.classes - nc = len(names) + testloader = create_classification_dataloader(path=test_dir, + imgsz=imgsz, + batch_size=bs // WORLD_SIZE * 2, + augment=False, + cache=opt.cache, + rank=-1, + workers=nw) + + # Initialize + names = trainloader.dataset.classes # class names + nc = len(names) # number of classes LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') init_seeds(1 + RANK) @@ -238,7 +239,7 @@ def train(): f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions - images, labels = (x[:30] for x in iter(testloader).next()) # first 30 images and labels + images, labels = (x[:64] for x in iter(testloader).next()) # first 30 images and labels images = images.to(device) pred = torch.max(model(images), 1)[1] imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 985e04cca797..2d5c58fad193 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1156,4 +1156,4 @@ def create_classification_dataloader( shuffle=shuffle and sampler is None, num_workers=nw, pin_memory=True, - sampler=sampler), dataset + sampler=sampler) From d00a5f630e00fe286794504b13d175b1e57fa553 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 17:19:30 +0200 Subject: [PATCH 100/349] max 32 printouts --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 365ced238870..61e3ee5e190a 100644 --- a/classifier.py +++ b/classifier.py @@ -329,9 +329,9 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") if verbose and labels is not None: - LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels)) + LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:32])) if verbose and pred is not None: - LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred)) + LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:32])) if __name__ == '__main__': From 1b930b79f42576a187f4d431ac7f98fcc457343d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 17:21:40 +0200 Subject: [PATCH 101/349] cleanup imshow --- classifier.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 61e3ee5e190a..601e6d75ec26 100644 --- a/classifier.py +++ b/classifier.py @@ -323,15 +323,14 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa if labels is not None: s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') ax[i].set_title(s) - plt.savefig(f, dpi=300, bbox_inches='tight') plt.close() LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") - - if verbose and labels is not None: - LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:32])) - if verbose and pred is not None: - LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:32])) + if verbose: + if labels is not None: + LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:32])) + if pred is not None: + LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:32])) if __name__ == '__main__': From 71d818acb6b60e9fd3f7e1dd42cc2489eac8d5c7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 17:52:42 +0200 Subject: [PATCH 102/349] Update EMA --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 601e6d75ec26..4acd3cf44279 100644 --- a/classifier.py +++ b/classifier.py @@ -131,7 +131,7 @@ def train(): model_info(model) # print(model) # EMA - ema = ModelEMA(model) if RANK in {-1, 0} else None + ema = ModelEMA(model, decay=0.999, tau=200) if RANK in {-1, 0} else None # Optimizer lr0 = 0.01 * (1 if opt.optimizer.startswith('Adam') else 0.01 * bs) # initial lr From eb1dd2b141452ad71ec2db4abf9d1276cfcf6fb0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 18:10:18 +0200 Subject: [PATCH 103/349] Update EMA --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 4acd3cf44279..1aba1d9463bf 100644 --- a/classifier.py +++ b/classifier.py @@ -131,7 +131,7 @@ def train(): model_info(model) # print(model) # EMA - ema = ModelEMA(model, decay=0.999, tau=200) if RANK in {-1, 0} else None + ema = ModelEMA(model, tau=len(trainloader) * epochs * 0.01) if RANK in {-1, 0} else None # Optimizer lr0 = 0.01 * (1 if opt.optimizer.startswith('Adam') else 0.01 * bs) # initial lr From f1db125644c8f7ac2ab5aed6b77ab599ae6010b3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 20:07:44 +0200 Subject: [PATCH 104/349] Update EMA --- classifier.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 1aba1d9463bf..5ba35cafc2d9 100644 --- a/classifier.py +++ b/classifier.py @@ -131,19 +131,20 @@ def train(): model_info(model) # print(model) # EMA - ema = ModelEMA(model, tau=len(trainloader) * epochs * 0.01) if RANK in {-1, 0} else None + ema = ModelEMA(model) if RANK in {-1, 0} else None # Optimizer - lr0 = 0.01 * (1 if opt.optimizer.startswith('Adam') else 0.01 * bs) # initial lr - lrf = 0.001 # final lr (fraction of lr0) if opt.optimizer == 'Adam': - optimizer = optim.Adam(model.parameters(), lr=lr0 / 10) + optimizer = optim.Adam(model.parameters(), weight_decay=1e-5, lr=0.08) elif opt.optimizer == 'AdamW': - optimizer = optim.AdamW(model.parameters(), lr=lr0 / 10) + optimizer = optim.AdamW(model.parameters(), weight_decay=1e-5, lr=0.08) + elif opt.optimizer == 'RMSProp': + optimizer = optim.RMSprop(model.parameters(), weight_decay=1e-5, lr=0.08) else: - optimizer = optim.SGD(model.parameters(), lr=lr0, momentum=0.9, nesterov=True) + optimizer = optim.SGD(model.parameters(), weight_decay=1e-5, lr=0.08 * bs, momentum=0.9, nesterov=True) # Scheduler + lrf = 0.001 # final lr (fraction of lr0) # lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine lf = lambda x: (1 - x / epochs) * (1.0 - lrf) + lrf # linear scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) From ab252d3c8ab739c7cfb297441193abee662c64fe Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 20:19:07 +0200 Subject: [PATCH 105/349] Add persistent workers --- utils/dataloaders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 2d5c58fad193..094e9e6a1c98 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1156,4 +1156,5 @@ def create_classification_dataloader( shuffle=shuffle and sampler is None, num_workers=nw, pin_memory=True, + persistent_workers=True, sampler=sampler) From e4869ac0b76cd1dc19c81ff9e3ade6135a091099 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 20:22:09 +0200 Subject: [PATCH 106/349] cleanup --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 5ba35cafc2d9..929d1e18c16a 100644 --- a/classifier.py +++ b/classifier.py @@ -160,7 +160,7 @@ def train(): # Train t0 = time.time() - criterion = nn.CrossEntropyLoss() # loss function + criterion = nn.CrossEntropyLoss(label_smoothing=0.10) # loss function best_fitness = 0.0 scaler = amp.GradScaler(enabled=cuda) LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' @@ -343,7 +343,6 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') - parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='Adam', help='optimizer') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') @@ -353,6 +352,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') + parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') opt = parser.parse_args() # Checks From 633c98b360dd6c52354b4c8b3d44bbd8ec5f872b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 20:27:01 +0200 Subject: [PATCH 107/349] cleanup --- classifier.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 929d1e18c16a..c45e1d8f1b56 100644 --- a/classifier.py +++ b/classifier.py @@ -135,13 +135,13 @@ def train(): # Optimizer if opt.optimizer == 'Adam': - optimizer = optim.Adam(model.parameters(), weight_decay=1e-5, lr=0.08) + optimizer = optim.Adam(model.parameters(), weight_decay=1e-5, lr=opt.lr0) elif opt.optimizer == 'AdamW': - optimizer = optim.AdamW(model.parameters(), weight_decay=1e-5, lr=0.08) + optimizer = optim.AdamW(model.parameters(), weight_decay=1e-5, lr=opt.lr0) elif opt.optimizer == 'RMSProp': - optimizer = optim.RMSprop(model.parameters(), weight_decay=1e-5, lr=0.08) + optimizer = optim.RMSprop(model.parameters(), weight_decay=1e-5, lr=opt.lr0) else: - optimizer = optim.SGD(model.parameters(), weight_decay=1e-5, lr=0.08 * bs, momentum=0.9, nesterov=True) + optimizer = optim.SGD(model.parameters(), weight_decay=1e-5, lr=opt.lr0 * bs, momentum=0.9, nesterov=True) # Scheduler lrf = 0.001 # final lr (fraction of lr0) @@ -353,6 +353,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') + parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') opt = parser.parse_args() # Checks From ff10c311d5bf96acc381b29d5534dcc00c190d6a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 21:11:10 +0200 Subject: [PATCH 108/349] Add label-smoothing --- classifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index c45e1d8f1b56..ac8d38b7491e 100644 --- a/classifier.py +++ b/classifier.py @@ -144,7 +144,7 @@ def train(): optimizer = optim.SGD(model.parameters(), weight_decay=1e-5, lr=opt.lr0 * bs, momentum=0.9, nesterov=True) # Scheduler - lrf = 0.001 # final lr (fraction of lr0) + lrf = 0.01 # final lr (fraction of lr0) # lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine lf = lambda x: (1 - x / epochs) * (1.0 - lrf) + lrf # linear scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) @@ -160,7 +160,7 @@ def train(): # Train t0 = time.time() - criterion = nn.CrossEntropyLoss(label_smoothing=0.10) # loss function + criterion = nn.CrossEntropyLoss(label_smoothing=opt.label_smoothing) # loss function best_fitness = 0.0 scaler = amp.GradScaler(enabled=cuda) LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' @@ -354,6 +354,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') + parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') opt = parser.parse_args() # Checks From b7e5fd2d873ac829b1056aebc11927e0ffea381e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 21:16:18 +0200 Subject: [PATCH 109/349] Add InfiniteDataloader --- utils/dataloaders.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 094e9e6a1c98..4dd87eeeae49 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -22,7 +22,6 @@ import torch import torch.nn.functional as F import torchvision -import torchvision.transforms as T import yaml from PIL import ExifTags, Image, ImageOps from torch.utils.data import DataLoader, Dataset, dataloader, distributed @@ -1151,10 +1150,9 @@ def create_classification_dataloader( nd = torch.cuda.device_count() nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) - return torch.utils.data.DataLoader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - pin_memory=True, - persistent_workers=True, - sampler=sampler) + return InfiniteDataLoader(dataset, + batch_size=batch_size, + shuffle=shuffle and sampler is None, + num_workers=nw, + pin_memory=True, + sampler=sampler) From d0369a2def70e0fe088993037ec7a219381b2f87 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 21:18:15 +0200 Subject: [PATCH 110/349] lr0 to 0.0012 --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index ac8d38b7491e..21a87ea250c8 100644 --- a/classifier.py +++ b/classifier.py @@ -353,7 +353,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') - parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') + parser.add_argument('--lr0', type=float, default=0.0012, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') opt = parser.parse_args() From e2d0e2fc8d7e271f0f561d73c5bb41eccf1cfb0c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 21:24:17 +0200 Subject: [PATCH 111/349] lr0 to 0.0012 --- classifier.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 21a87ea250c8..d73f0458ce2b 100644 --- a/classifier.py +++ b/classifier.py @@ -99,8 +99,8 @@ def train(): init_seeds(1 + RANK) # Show images - images, labels = iter(trainloader).next() - imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') + # images, labels = iter(trainloader).next() + # imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') # Model repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' @@ -240,10 +240,10 @@ def train(): f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions - images, labels = (x[:64] for x in iter(testloader).next()) # first 30 images and labels - images = images.to(device) - pred = torch.max(model(images), 1)[1] - imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + # images, labels = (x[:64] for x in iter(testloader).next()) # first 30 images and labels + # images = images.to(device) + # pred = torch.max(model(images), 1)[1] + # imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') @torch.no_grad() From 7732a916d0429ee4fc1e10c7509f02571695a999 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 21:30:43 +0200 Subject: [PATCH 112/349] next(iter()) --- classifier.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index d73f0458ce2b..6915f89867a2 100644 --- a/classifier.py +++ b/classifier.py @@ -99,8 +99,8 @@ def train(): init_seeds(1 + RANK) # Show images - # images, labels = iter(trainloader).next() - # imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') + images, labels = next(iter(trainloader)) + imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') # Model repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' @@ -240,10 +240,10 @@ def train(): f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions - # images, labels = (x[:64] for x in iter(testloader).next()) # first 30 images and labels - # images = images.to(device) - # pred = torch.max(model(images), 1)[1] - # imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + images, labels = (x[:64] for x in next(iter(testloader))) # first 30 images and labels + images = images.to(device) + pred = torch.max(model(images), 1)[1] + imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') @torch.no_grad() From 92973ad353d9a5112016355c023b73d94fd125ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Jul 2022 19:52:04 +0000 Subject: [PATCH 113/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classifier.py | 2 +- utils/dataloaders.py | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/classifier.py b/classifier.py index 46f40979836e..1fc3f92b4959 100644 --- a/classifier.py +++ b/classifier.py @@ -47,7 +47,7 @@ from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, colorstr, download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import de_parallel, model_info, ModelEMA, select_device, torch_distributed_zero_first +from utils.torch_utils import ModelEMA, de_parallel, model_info, select_device, torch_distributed_zero_first LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 4dd87eeeae49..3368dc435a77 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -85,7 +85,7 @@ def exif_transpose(image): 5: Image.TRANSPOSE, 6: Image.ROTATE_270, 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, }.get(orientation) + 8: Image.ROTATE_90,}.get(orientation) if method is not None: image = image.transpose(method) del exif[0x0112] @@ -1134,15 +1134,14 @@ def __getitem__(self, i): return sample, j -def create_classification_dataloader( - path, - imgsz=224, - batch_size=16, - augment=True, - cache=False, - rank=-1, - workers=8, - shuffle=True): +def create_classification_dataloader(path, + imgsz=224, + batch_size=16, + augment=True, + cache=False, + rank=-1, + workers=8, + shuffle=True): # Returns Dataloader object to be used with YOLOv5 Classifier with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP dataset = ClassificationDataset(root=path, imgsz=imgsz, augment=augment, cache=cache) From 4abc7d85d8fe4b093ff4dc31f92cfa63054fb4eb Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 22:05:46 +0200 Subject: [PATCH 114/349] merge classifier --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 46f40979836e..88fb8b0563fd 100644 --- a/classifier.py +++ b/classifier.py @@ -112,9 +112,9 @@ def train(): model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False, force_reload=True) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - model.model = model.model[:13] if opt.model.endswith('6') else model.model[:11] # backbone + model.model = model.model[:12] if opt.model.endswith('6') else model.model[:10] # backbone m = model.model[-1] # last layer - ch = m.conv.in_channels if hasattr(m, 'conv') else sum(x.in_channels for x in m.m) # ch into module + ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module c = Classify(ch, nc) # Classify() c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type model.model[-1] = c # replace From 5bf36136aa6cd53cbade92d314229b38e8c350a3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 16 Jul 2022 22:10:47 +0200 Subject: [PATCH 115/349] merge classifier --- utils/dataloaders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 3368dc435a77..7a3795804e5d 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1153,5 +1153,5 @@ def create_classification_dataloader(path, batch_size=batch_size, shuffle=shuffle and sampler is None, num_workers=nw, - pin_memory=True, - sampler=sampler) + sampler=sampler, + pin_memory=True) From 0ed1f9985ddd9ff4455c1ca077dd3a9adbe024d9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 17 Jul 2022 00:46:09 +0200 Subject: [PATCH 116/349] Merge classifier-update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 0a684e865baa..cece0f70cf16 100644 --- a/classifier.py +++ b/classifier.py @@ -354,7 +354,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.0012, help='initial learning rate') - parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') + parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') opt = parser.parse_args() # Checks From 98fbc0d1799887fc1525940f12798f476ac28da9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 17 Jul 2022 01:21:15 +0200 Subject: [PATCH 117/349] Merge classifier-update --- classifier.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/classifier.py b/classifier.py index cece0f70cf16..f00d07bf2d85 100644 --- a/classifier.py +++ b/classifier.py @@ -368,8 +368,6 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa cuda = device.type != 'cpu' opt.hyp = check_file(opt.hyp) # check files opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run - # TODO: Remove resize as redundant with augmentations - resize = torch.nn.Upsample(size=(opt.imgsz, opt.imgsz), mode='bilinear', align_corners=False) # image resize # DDP if LOCAL_RANK != -1: From 7b98ac4a989ae89559481dddb8d6a15602e37d84 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 17 Jul 2022 01:56:49 +0200 Subject: [PATCH 118/349] init_seeds --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f00d07bf2d85..04fa7d4abbfc 100644 --- a/classifier.py +++ b/classifier.py @@ -55,6 +55,7 @@ def train(): + init_seeds(1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), opt.imgsz, \ not opt.from_scratch @@ -96,7 +97,6 @@ def train(): names = trainloader.dataset.classes # class names nc = len(names) # number of classes LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') - init_seeds(1 + RANK) # Show images images, labels = next(iter(trainloader)) From 00c265dcc7507703d1f36a23354bec30b0287801 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 17 Jul 2022 11:49:17 +0200 Subject: [PATCH 119/349] update --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f1290ecd472f..c52c33d9da2e 100644 --- a/classifier.py +++ b/classifier.py @@ -128,7 +128,8 @@ def train(): model.fc = nn.Linear(model.fc.weight.shape[1], nc) model = model.to(device) if RANK in {-1, 0}: - model_info(model) # print(model) + model_info(model) + print(model) # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None From 320980672db8d87c634007b127a9a308aafa689e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 17 Jul 2022 12:59:23 +0200 Subject: [PATCH 120/349] smart_optimizer --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index c52c33d9da2e..2810aabc9036 100644 --- a/classifier.py +++ b/classifier.py @@ -129,7 +129,7 @@ def train(): model = model.to(device) if RANK in {-1, 0}: model_info(model) - print(model) + # print(model) # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None From 884a2a0b8ff9e86e30d317634f9b82b9e2f41ffc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 06:55:21 +0200 Subject: [PATCH 121/349] smart_optimizer --- models/common.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/models/common.py b/models/common.py index 5ea1c307f034..33ce7d4b1e4a 100644 --- a/models/common.py +++ b/models/common.py @@ -739,10 +739,14 @@ class Classify(nn.Module): # Classification head, i.e. x(b,c1,20,20) to x(b,c2) def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, stride, padding, groups super().__init__() + c_ = 1280 # efficientnet_b0 size + self.cv1 = Conv(c1, c_, k, s, autopad(k, p), g) + self.cv2 = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1) self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c1,1,1) - self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1) self.flat = nn.Flatten() def forward(self, x): - z = torch.cat([self.aap(y) for y in (x if isinstance(x, list) else [x])], 1) # cat if list - return self.flat(self.conv(z)) # flatten to x(b,c2) + # z = torch.cat([self.aap(y) for y in (x if isinstance(x, list) else [x])], 1) # cat if list + # return self.flat(self.cv2(z)) # flatten to x(b,c2) + x = torch.cat(x, 1) if isinstance(x, list) else x + return self.flat(self.cv2(self.aap(self.cv1(x)))) From ad520e4d4d1b50762d4196f95a621db80300dbdc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 07:01:24 +0200 Subject: [PATCH 122/349] smart_optimizer --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 33ce7d4b1e4a..937bd24e5f3a 100644 --- a/models/common.py +++ b/models/common.py @@ -741,7 +741,7 @@ def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, str super().__init__() c_ = 1280 # efficientnet_b0 size self.cv1 = Conv(c1, c_, k, s, autopad(k, p), g) - self.cv2 = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1) + self.cv2 = nn.Conv2d(c_, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1) self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c1,1,1) self.flat = nn.Flatten() From ee66cc2de3c8a7a7a6467870af7462ceb50b3e91 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 11:58:37 +0200 Subject: [PATCH 123/349] cutoff --- classifier.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 2810aabc9036..88fdf622dc44 100644 --- a/classifier.py +++ b/classifier.py @@ -112,7 +112,8 @@ def train(): model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False, force_reload=True) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - model.model = model.model[:10] if opt.model.endswith('6') else model.model[:8] # backbone + # model.model = model.model[:10] if opt.model.endswith('6') else model.model[:opt.cutoff] # backbone + model.model = model.model[:opt.cutoff] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module c = Classify(ch, nc) # Classify() @@ -350,6 +351,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.0012, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') + parser.add_argument('--cutoff', type=int, default=8, help='Layer to cutoff YOLOv5 model for Classify() head') opt = parser.parse_args() # Checks From 08e5a1dbe0e861468539134bc142afd34c295606 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 13:19:55 +0200 Subject: [PATCH 124/349] update --- classifier.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 88fdf622dc44..9f2f4d0a548c 100644 --- a/classifier.py +++ b/classifier.py @@ -3,7 +3,7 @@ Train a YOLOv5 classifier model on a classification dataset Usage - train: - $ python classifier.py --model yolov5s --data mnist --epochs 5 --img 128 + $ python classifier.py --model yolov5s --data cifar100 --epochs 5 --img 224 --batch 128 $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 4,5,6,7 Usage - inference: @@ -112,8 +112,8 @@ def train(): model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False, force_reload=True) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - # model.model = model.model[:10] if opt.model.endswith('6') else model.model[:opt.cutoff] # backbone - model.model = model.model[:opt.cutoff] # backbone + ic = opt.cutoff or (11 if opt.model.endswith('6') else 9) # cutoff index + model.model = model.model[:ic] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module c = Classify(ch, nc) # Classify() @@ -130,7 +130,8 @@ def train(): model = model.to(device) if RANK in {-1, 0}: model_info(model) - # print(model) + if opt.verbose: + LOGGER.info(model) # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None @@ -349,9 +350,10 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') - parser.add_argument('--lr0', type=float, default=0.0012, help='initial learning rate') + parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') - parser.add_argument('--cutoff', type=int, default=8, help='Layer to cutoff YOLOv5 model for Classify() head') + parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') + parser.add_argument('--verbose', action='store_false', help='Verbose mode') opt = parser.parse_args() # Checks From c8cfd809a3bc76f44f1a46606e2a962c82f8315b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 13:30:56 +0200 Subject: [PATCH 125/349] update --- classifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 9f2f4d0a548c..e818269243ed 100644 --- a/classifier.py +++ b/classifier.py @@ -58,7 +58,8 @@ def train(): init_seeds(1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), opt.imgsz, \ - not opt.from_scratch + str(opt.pretrained).lower() == 'true' + # Directories wdir = save_dir / 'weights' wdir.mkdir(parents=True, exist_ok=True) # make dir @@ -347,7 +348,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--project', default='runs/train', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') - parser.add_argument('--from-scratch', '--scratch', action='store_true', help='train model from scratch') + parser.add_argument('--pretrained', nargs='?', const=True, default=True, help='start from i.e. --pretrained False') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') From efcd7f691a756c7bf2fcc13e03230e5da51ca94b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 13:37:53 +0200 Subject: [PATCH 126/349] update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index e818269243ed..03af88fdfa54 100644 --- a/classifier.py +++ b/classifier.py @@ -354,7 +354,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') - parser.add_argument('--verbose', action='store_false', help='Verbose mode') + parser.add_argument('--verbose', action='store_true', help='Verbose mode') opt = parser.parse_args() # Checks From 895a9c4c7d1d16b4d61e79962039bedbfbd2131c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 15:10:25 +0200 Subject: [PATCH 127/349] assert torch!=1.12.0 for DDP training --- classifier.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/classifier.py b/classifier.py index 03af88fdfa54..ed7cf7d5ddb2 100644 --- a/classifier.py +++ b/classifier.py @@ -31,7 +31,6 @@ import torch.optim.lr_scheduler as lr_scheduler import torchvision from torch.cuda import amp -from torch.nn.parallel import DistributedDataParallel as DDP from tqdm import tqdm FILE = Path(__file__).resolve() @@ -43,10 +42,10 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, colorstr, download, +from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import ModelEMA, model_info, select_device, smart_optimizer, \ +from utils.torch_utils import ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, \ torch_distributed_zero_first LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html @@ -151,10 +150,7 @@ def train(): # DDP mode if cuda and RANK != -1: - if check_version(torch.__version__, '1.11.0'): - model = DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK, static_graph=True) - else: - model = DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) + model = smart_DDP(model) # Train t0 = time.time() From 2d3440879a98fb17dbdb627ac57f554780f4a622 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 18 Jul 2022 15:16:41 +0200 Subject: [PATCH 128/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index ed7cf7d5ddb2..dd3f4268e1f3 100644 --- a/classifier.py +++ b/classifier.py @@ -158,7 +158,7 @@ def train(): best_fitness = 0.0 scaler = amp.GradScaler(enabled=cuda) LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' - f'Using {nw} dataloader workers\n' + f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") From 05709319c1e0d034de04a317f794bbe6e9c17c3e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 13:47:47 +0200 Subject: [PATCH 129/349] Update --- classifier.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/classifier.py b/classifier.py index dd3f4268e1f3..a51a78ddc503 100644 --- a/classifier.py +++ b/classifier.py @@ -247,13 +247,14 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): n = len(dataloader) # number of batches desc = f'{pbar.desc}validating' bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) - for images, labels in bar: - images, labels = images.to(device), labels.to(device) - y = model(images) - pred.append(torch.max(y, 1)[1]) - targets.append(labels) - if criterion: - loss += criterion(y, labels) + with amp.autocast(enabled=cuda): # stability issues when enabled + for images, labels in bar: + images, labels = images.to(device), labels.to(device) + y = model(images) + pred.append(torch.max(y, 1)[1]) + targets.append(labels) + if criterion: + loss += criterion(y, labels) loss /= n pred, targets = torch.cat(pred), torch.cat(targets) From 1b8ef9a0e7ff0cfccaeaeabe954cd913f436b0b2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 14:02:42 +0200 Subject: [PATCH 130/349] nonblocking --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index a51a78ddc503..8a2a319b7c78 100644 --- a/classifier.py +++ b/classifier.py @@ -171,7 +171,7 @@ def train(): if RANK in {-1, 0}: pbar = tqdm(enumerate(trainloader), total=len(trainloader), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') for i, (images, labels) in pbar: # progress bar - images, labels = images.to(device), labels.to(device) + images, labels = images.to(device, non_blocking=True), labels.to(device) # Forward with amp.autocast(enabled=cuda): # stability issues when enabled @@ -249,7 +249,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with amp.autocast(enabled=cuda): # stability issues when enabled for images, labels in bar: - images, labels = images.to(device), labels.to(device) + images, labels = images.to(device, non_blocking=True), labels.to(device) y = model(images) pred.append(torch.max(y, 1)[1]) targets.append(labels) From fa6e32ee24b27180a102e0e7749453bd07f93558 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 14:23:01 +0200 Subject: [PATCH 131/349] plots --- classifier.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/classifier.py b/classifier.py index 8a2a319b7c78..96afe71f4b92 100644 --- a/classifier.py +++ b/classifier.py @@ -100,7 +100,7 @@ def train(): # Show images images, labels = next(iter(trainloader)) - imshow(denormalize(images[:64]), labels[:64], names=names, f=save_dir / 'train_images.jpg') + imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') # Model repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' @@ -234,7 +234,7 @@ def train(): f"\nResults saved to {colorstr('bold', save_dir)}") # Show predictions - images, labels = (x[:64] for x in next(iter(testloader))) # first 30 images and labels + images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels images = images.to(device) pred = torch.max(model(images), 1)[1] imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') @@ -302,7 +302,7 @@ def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False return p -def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Path('images.jpg')): +def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) import matplotlib.pyplot as plt @@ -310,23 +310,23 @@ def imshow(img, labels=None, pred=None, names=None, nmax=64, verbose=False, f=Pa blocks = torch.chunk(img.cpu(), len(img), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default - fig, ax = plt.subplots(math.ceil(n / m), m, tight_layout=True) # 8 rows x n/8 cols + fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols ax = ax.ravel() if m > 1 else [ax] - plt.subplots_adjust(wspace=0.05, hspace=0.05) + # plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) ax[i].axis('off') if labels is not None: s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') - ax[i].set_title(s) + ax[i].set_title(s, fontsize=8, verticalalignment='top') plt.savefig(f, dpi=300, bbox_inches='tight') plt.close() LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") if verbose: if labels is not None: - LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:32])) + LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax])) if pred is not None: - LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:32])) + LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax])) if __name__ == '__main__': From c526341960ff62dc9b559793a8d4e221357d9694 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 14:29:37 +0200 Subject: [PATCH 132/349] Trust repo --- classifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 96afe71f4b92..fa9c9f64a85b 100644 --- a/classifier.py +++ b/classifier.py @@ -106,10 +106,11 @@ def train(): repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' with torch_distributed_zero_first(LOCAL_RANK): if opt.model.startswith('yolov5'): # YOLOv5 Classifier + kwargs = {'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} try: - model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False) + model = hub.load(repo1, opt.model, **kwargs) except Exception: - model = hub.load(repo1, opt.model, pretrained=pretrained, autoshape=False, force_reload=True) + model = hub.load(repo1, opt.model, force_reload=True, **kwargs) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend ic = opt.cutoff or (11 if opt.model.endswith('6') else 9) # cutoff index @@ -122,7 +123,7 @@ def train(): for p in model.parameters(): p.requires_grad = True # for training elif opt.model in hub.list(repo2): # i.e. efficientnet_b0 - model = hub.load(repo2, opt.model, pretrained=pretrained) + model = hub.load(repo2, opt.model, pretrained=pretrained, trust_repo=True) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision model = torchvision.models.__dict__[opt.model](pretrained=pretrained) From 62cceedd32a0a4559825d1628cf1f3e10e9b324f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 15:12:22 +0200 Subject: [PATCH 133/349] Add log_images() --- classifier.py | 6 ++++-- utils/loggers/__init__.py | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index fa9c9f64a85b..5c9479707a20 100644 --- a/classifier.py +++ b/classifier.py @@ -100,7 +100,7 @@ def train(): # Show images images, labels = next(iter(trainloader)) - imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') + logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) # Model repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' @@ -238,7 +238,8 @@ def train(): images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels images = images.to(device) pred = torch.max(model(images), 1)[1] - imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + logger.log_images(file, epoch) @torch.no_grad() @@ -328,6 +329,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax])) if pred is not None: LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax])) + return f if __name__ == '__main__': diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index c220d74c1bef..0c967bf92c7e 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -5,6 +5,7 @@ import os import warnings +from pathlib import Path import pkg_resources as pkg import torch @@ -217,9 +218,21 @@ def __init__(self, opt, console_logger, include=('tb', 'wandb')): def log_metrics(self, metrics_dict, epoch): # Log metrics dictionary to initialized loggers + if self.tb: + for k, v in metrics_dict.items(): + self.tb.add_scalar(k, v, epoch) + if self.wandb: self.wandb.log(metrics_dict) + def log_images(self, files, epoch=0): + # Log images to initialized loggers + files = [Path(f) for f in (files if isinstance(files, (tuple, list)) else [files])] # to Path + files = [f for f in files if f.exists()] # filter by exists + if self.tb: - for k, v in metrics_dict.items(): - self.tb.add_scalar(k, v, epoch) + for f in files: + self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC') + + if self.wandb: + self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}) From 970171ac49bc7865e31cbfd19dbaff40653e915d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 20:53:02 +0200 Subject: [PATCH 134/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 5c9479707a20..9ef5d7185430 100644 --- a/classifier.py +++ b/classifier.py @@ -113,7 +113,7 @@ def train(): model = hub.load(repo1, opt.model, force_reload=True, **kwargs) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - ic = opt.cutoff or (11 if opt.model.endswith('6') else 9) # cutoff index + ic = opt.cutoff or (10 if opt.model.endswith('6') else 8) # cutoff index model.model = model.model[:ic] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module From e16be3bf88060656f269b518c4b573893be06520 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 21:04:37 +0200 Subject: [PATCH 135/349] Update --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 9ef5d7185430..23671cf138e7 100644 --- a/classifier.py +++ b/classifier.py @@ -98,10 +98,6 @@ def train(): nc = len(names) # number of classes LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') - # Show images - images, labels = next(iter(trainloader)) - logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) - # Model repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' with torch_distributed_zero_first(LOCAL_RANK): @@ -129,10 +125,14 @@ def train(): model = torchvision.models.__dict__[opt.model](pretrained=pretrained) model.fc = nn.Linear(model.fc.weight.shape[1], nc) model = model.to(device) + + # Info if RANK in {-1, 0}: model_info(model) if opt.verbose: LOGGER.info(model) + images, labels = next(iter(trainloader)) + logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None From 290d151c243338cae63906c0ca66f0f99834de90 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 21:25:28 +0200 Subject: [PATCH 136/349] Update --- utils/loggers/__init__.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 0c967bf92c7e..08e2664ab702 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -102,11 +102,8 @@ def on_pretrain_routine_end(self): def on_train_batch_end(self, ni, model, imgs, targets, paths, plots): # Callback runs on train batch end if plots: - if ni == 0: - if not self.opt.sync_bn: # --sync known issue https://github.com/ultralytics/yolov5/issues/3754 - with warnings.catch_warnings(): - warnings.simplefilter('ignore') # suppress jit trace warning - self.tb.add_graph(torch.jit.trace(de_parallel(model), imgs[0:1], strict=False), []) + if ni == 0 and not self.opt.sync_bn and self.tb: + log_tensorboard_graph(self.tb, model, imgsz=list(imgs.shape[2:4])) if ni < 3: f = self.save_dir / f'train_batch{ni}.jpg' # filename plot_images(imgs, targets, paths, f) @@ -217,7 +214,7 @@ def __init__(self, opt, console_logger, include=('tb', 'wandb')): self.wandb = None def log_metrics(self, metrics_dict, epoch): - # Log metrics dictionary to initialized loggers + # Log metrics dictionary to all loggers if self.tb: for k, v in metrics_dict.items(): self.tb.add_scalar(k, v, epoch) @@ -226,7 +223,7 @@ def log_metrics(self, metrics_dict, epoch): self.wandb.log(metrics_dict) def log_images(self, files, epoch=0): - # Log images to initialized loggers + # Log images to all loggers files = [Path(f) for f in (files if isinstance(files, (tuple, list)) else [files])] # to Path files = [f for f in files if f.exists()] # filter by exists @@ -236,3 +233,20 @@ def log_images(self, files, epoch=0): if self.wandb: self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}) + + def log_model(self, model, imgsz=(640, 640)): + # Log model graph to all loggers + if self.tb: + log_tensorboard_graph(self.tb, model, imgsz) + + +def log_tensorboard_graph(tb, model, imgsz=(640, 640)): + # Log model graph to TensorBoard + try: + p = next(model.parameters()) # for device, type + im = torch.zeros((1, 3, *imgsz)).to(p.device).type_as(p) # input image + with warnings.catch_warnings(): + warnings.simplefilter('ignore') # suppress jit trace warning + tb.add_graph(torch.jit.trace(de_parallel(model), im, strict=False), []) + except Exception: + print('WARNING: TensorBoard graph visualization failure') From e42e0d5d424809eb08b339b9c9a1187ae7a03a6c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 19 Jul 2022 21:35:28 +0200 Subject: [PATCH 137/349] Update --- classifier.py | 1 + utils/loggers/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/classifier.py b/classifier.py index 23671cf138e7..bb1e7ab90f27 100644 --- a/classifier.py +++ b/classifier.py @@ -133,6 +133,7 @@ def train(): LOGGER.info(model) images, labels = next(iter(trainloader)) logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) + logger.log_model(model, imgsz) # log model # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 08e2664ab702..7ccf93ebff62 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -244,6 +244,7 @@ def log_tensorboard_graph(tb, model, imgsz=(640, 640)): # Log model graph to TensorBoard try: p = next(model.parameters()) # for device, type + imgsz = (imgsz, imgsz) if isinstance(imgsz, int) else imgsz # expand im = torch.zeros((1, 3, *imgsz)).to(p.device).type_as(p) # input image with warnings.catch_warnings(): warnings.simplefilter('ignore') # suppress jit trace warning From 744ac7f3c8b64e7583c42736e03f265e9029314b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 20 Jul 2022 01:24:10 +0200 Subject: [PATCH 138/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index bb1e7ab90f27..fff5150d3b98 100644 --- a/classifier.py +++ b/classifier.py @@ -109,7 +109,7 @@ def train(): model = hub.load(repo1, opt.model, force_reload=True, **kwargs) if isinstance(model, DetectMultiBackend): model = model.model # unwrap DetectMultiBackend - ic = opt.cutoff or (10 if opt.model.endswith('6') else 8) # cutoff index + ic = opt.cutoff or (12 if opt.model.endswith('6') else 10) # cutoff index model.model = model.model[:ic] # backbone m = model.model[-1] # last layer ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module From 4602c01343746ad38f17546900a03884bdbe6795 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 20 Jul 2022 16:59:24 +0200 Subject: [PATCH 139/349] Update --- classifier.py | 2 +- utils/loggers/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index fff5150d3b98..c2ebb4e3afc4 100644 --- a/classifier.py +++ b/classifier.py @@ -133,7 +133,7 @@ def train(): LOGGER.info(model) images, labels = next(iter(trainloader)) logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) - logger.log_model(model, imgsz) # log model + logger.log_graph(model, imgsz) # log model # EMA ema = ModelEMA(model) if RANK in {-1, 0} else None diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 7ccf93ebff62..f3ee19c455f5 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -234,7 +234,7 @@ def log_images(self, files, epoch=0): if self.wandb: self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}) - def log_model(self, model, imgsz=(640, 640)): + def log_graph(self, model, imgsz=(640, 640)): # Log model graph to all loggers if self.tb: log_tensorboard_graph(self.tb, model, imgsz) From 798cab50cf5ec8abf76a87e9966fa7110158fad9 Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Thu, 21 Jul 2022 03:38:35 +0530 Subject: [PATCH 140/349] Logger step fix: Increment step with epochs (#8654) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * replace print with logger * commit steps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 8 ++++---- utils/dataloaders.py | 2 +- utils/loggers/__init__.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/classifier.py b/classifier.py index c2ebb4e3afc4..ff8fd6c12855 100644 --- a/classifier.py +++ b/classifier.py @@ -42,11 +42,11 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, - increment_path, init_seeds, print_args) +from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, increment_path, + init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, \ - torch_distributed_zero_first +from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, + torch_distributed_zero_first) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index b063a3b240be..7a3795804e5d 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -85,7 +85,7 @@ def exif_transpose(image): 5: Image.TRANSPOSE, 6: Image.ROTATE_270, 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, }.get(orientation) + 8: Image.ROTATE_90,}.get(orientation) if method is not None: image = image.transpose(method) del exif[0x0112] diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index f3ee19c455f5..70765a6d2bbb 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -220,7 +220,7 @@ def log_metrics(self, metrics_dict, epoch): self.tb.add_scalar(k, v, epoch) if self.wandb: - self.wandb.log(metrics_dict) + self.wandb.log(metrics_dict, step=epoch) def log_images(self, files, epoch=0): # Log images to all loggers @@ -232,7 +232,7 @@ def log_images(self, files, epoch=0): self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC') if self.wandb: - self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}) + self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}, step=epoch) def log_graph(self, model, imgsz=(640, 640)): # Log model graph to all loggers From 5d69e04bce778e30b423c4626e0aae6684c0a0ba Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 01:32:30 +0200 Subject: [PATCH 141/349] Update --- utils/augmentations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index a588eb9bd686..7cca5fdcb2b8 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -311,6 +311,7 @@ def classify_albumentations(augment=True, std=IMAGENET_STD, auto_aug=False): # YOLOv5 classification Albumentations (optional, only used if package is installed) + prefix = colorstr('albumentations: ') try: import albumentations as A from albumentations.pytorch import ToTensorV2 @@ -319,7 +320,7 @@ def classify_albumentations(augment=True, T = [A.RandomResizedCrop(height=size, width=size, scale=scale)] if auto_aug: # TODO: implement AugMix, AutoAug & RandAug in albumentation - LOGGER.info(colorstr('augmentations: ') + 'auto augmentations are currently not supported') + LOGGER.info(f'{prefix}auto augmentations are currently not supported') else: if hflip > 0: T += [A.HorizontalFlip(p=hflip)] @@ -331,14 +332,13 @@ def classify_albumentations(augment=True, else: # Use fixed crop for eval set (reproducibility) T = [A.SmallestMaxSize(max_size=size), A.CenterCrop(height=size, width=size)] T += [A.Normalize(mean=mean, std=std), ToTensorV2()] # Normalize and convert to Tensor - - LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in T if x.p)) + LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p)) return A.Compose(T) except ImportError: # package not installed, skip pass except Exception as e: - LOGGER.info(colorstr('albumentations: ') + f'{e}') + LOGGER.info(f'{prefix}{e}') def classify_transforms(size=224): From fceeb9a2072d71e8d76811229ec5a7643e4946dc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 01:34:39 +0200 Subject: [PATCH 142/349] Update --- utils/augmentations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index 7cca5fdcb2b8..dbc172c2b1f7 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -22,6 +22,7 @@ class Albumentations: # YOLOv5 Albumentations class (optional, only used if package is installed) def __init__(self): self.transform = None + prefix = colorstr('albumentations: ') try: import albumentations as A check_version(A.__version__, '1.0.3', hard=True) # version requirement @@ -36,11 +37,11 @@ def __init__(self): A.ImageCompression(quality_lower=75, p=0.0)] # transforms self.transform = A.Compose(T, bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) - LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in self.transform.transforms if x.p)) + LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p)) except ImportError: # package not installed, skip pass except Exception as e: - LOGGER.info(colorstr('albumentations: ') + f'{e}') + LOGGER.info(f'{prefix}{e}') def __call__(self, im, labels, p=1.0): if self.transform and random.random() < p: From 5fdeba72bc37b06ee56774dfa5e49b5bc32fa881 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 18:48:27 +0200 Subject: [PATCH 143/349] Update --- models/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/common.py b/models/common.py index 57d446f421fa..f81907aefc3e 100644 --- a/models/common.py +++ b/models/common.py @@ -740,12 +740,12 @@ class Classify(nn.Module): def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, stride, padding, groups super().__init__() c_ = 1280 # efficientnet_b0 size - self.cv1 = Conv(c1, c_, k, s, autopad(k, p), g) + self.conv = Conv(c1, c_, k, s, autopad(k, p), g) self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1) - self.cv2 = nn.Conv2d(c_, c2, k, s, autopad(k, p), groups=g) # to x(b,c2,1,1) self.flat = nn.Flatten() # flatten to x(b,c2) + self.linear = nn.Linear(c_, c2) # to x(b,c2,1,1) def forward(self, x): if isinstance(x, list): x = torch.cat(x, 1) - return self.flat(self.cv2(self.aap(self.cv1(x)))) + return self.linear(self.flat(self.aap(self.conv(x)))) From bc72ec0b0707680fb7fcdc1792a1d2a46d39594d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:07:03 +0200 Subject: [PATCH 144/349] Update --- classifier.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index ff8fd6c12855..da6aade1286d 100644 --- a/classifier.py +++ b/classifier.py @@ -99,10 +99,13 @@ def train(): LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') # Model - repo1, repo2 = 'ultralytics/yolov5', 'rwightman/gen-efficientnet-pytorch' + repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): - if opt.model.startswith('yolov5'): # YOLOv5 Classifier - kwargs = {'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} + if opt.model == 'list': + LOGGER.info('Available --models list:\n' + str([torch.hub.list(x) for x in (repo1, repo2)])) + exit() + elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. 'yolov5s' + kwargs = {'pretrained': pretrained, 'autoshape': False} try: model = hub.load(repo1, opt.model, **kwargs) except Exception: @@ -118,8 +121,8 @@ def train(): model.model[-1] = c # replace for p in model.parameters(): p.requires_grad = True # for training - elif opt.model in hub.list(repo2): # i.e. efficientnet_b0 - model = hub.load(repo2, opt.model, pretrained=pretrained, trust_repo=True) + elif opt.model in hub.list(repo2): # TorchVision models i.e. 'efficientnet_b0' + model = hub.load(repo2, opt.model, pretrained=pretrained) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision model = torchvision.models.__dict__[opt.model](pretrained=pretrained) From 1b0086186b880426463e07d3c4bb55f1d6f58cb4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:09:21 +0200 Subject: [PATCH 145/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index da6aade1286d..3a170d16aa70 100644 --- a/classifier.py +++ b/classifier.py @@ -102,7 +102,7 @@ def train(): repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': - LOGGER.info('Available --models list:\n' + str([torch.hub.list(x) for x in (repo1, repo2)])) + LOGGER.info('Available --models list:\n' + '\n'.join(*torch.hub.list(x) for x in (repo1, repo2))) exit() elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. 'yolov5s' kwargs = {'pretrained': pretrained, 'autoshape': False} From 053ada134a1dd10615e823d3ec636bb40013461c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:17:25 +0200 Subject: [PATCH 146/349] Update --- classifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 3a170d16aa70..0e626a65e73d 100644 --- a/classifier.py +++ b/classifier.py @@ -102,8 +102,9 @@ def train(): repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': - LOGGER.info('Available --models list:\n' + '\n'.join(*torch.hub.list(x) for x in (repo1, repo2))) - exit() + models = torch.hub.list(repo1) + torch.hub.list(repo2) + LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n\n' + '\n'.join(models)) + return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. 'yolov5s' kwargs = {'pretrained': pretrained, 'autoshape': False} try: From 167406dd7af56535ea1261b6b12fc7aedb0d428b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:17:51 +0200 Subject: [PATCH 147/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 0e626a65e73d..53fe426ec883 100644 --- a/classifier.py +++ b/classifier.py @@ -103,7 +103,7 @@ def train(): with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': models = torch.hub.list(repo1) + torch.hub.list(repo2) - LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n\n' + '\n'.join(models)) + LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. 'yolov5s' kwargs = {'pretrained': pretrained, 'autoshape': False} From dbe92bb39b3fe76b11434638829077495cabf950 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:24:29 +0200 Subject: [PATCH 148/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 53fe426ec883..0f24dc0dd700 100644 --- a/classifier.py +++ b/classifier.py @@ -105,7 +105,7 @@ def train(): models = torch.hub.list(repo1) + torch.hub.list(repo2) LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return - elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. 'yolov5s' + elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m kwargs = {'pretrained': pretrained, 'autoshape': False} try: model = hub.load(repo1, opt.model, **kwargs) @@ -122,7 +122,7 @@ def train(): model.model[-1] = c # replace for p in model.parameters(): p.requires_grad = True # for training - elif opt.model in hub.list(repo2): # TorchVision models i.e. 'efficientnet_b0' + elif opt.model in hub.list(repo2): # TorchVision models i.e. resnet50, efficientnet_b0, efficientnet_v2_s model = hub.load(repo2, opt.model, pretrained=pretrained) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision From 761c1d3695785232f85ce7310ef0b729bb2569c8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:40:59 +0200 Subject: [PATCH 149/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 0f24dc0dd700..85b5b6e71efd 100644 --- a/classifier.py +++ b/classifier.py @@ -106,7 +106,7 @@ def train(): LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - kwargs = {'pretrained': pretrained, 'autoshape': False} + kwargs = {'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} try: model = hub.load(repo1, opt.model, **kwargs) except Exception: From 07297c41b72fc07198f13bbb9023d2a855f44621 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:49:32 +0200 Subject: [PATCH 150/349] Update --- classifier.py | 5 +++-- utils/loggers/__init__.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 85b5b6e71efd..9e827aa00e1a 100644 --- a/classifier.py +++ b/classifier.py @@ -136,7 +136,8 @@ def train(): if opt.verbose: LOGGER.info(model) images, labels = next(iter(trainloader)) - logger.log_images(imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg')) + file = imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') + logger.log_images(file, name='Training Images', epoch=-1) logger.log_graph(model, imgsz) # log model # EMA @@ -244,7 +245,7 @@ def train(): images = images.to(device) pred = torch.max(model(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') - logger.log_images(file, epoch) + logger.log_images(file, name='Test Predictions', epoch=epoch + 1) @torch.no_grad() diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 70765a6d2bbb..4820376a8fe5 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -222,7 +222,7 @@ def log_metrics(self, metrics_dict, epoch): if self.wandb: self.wandb.log(metrics_dict, step=epoch) - def log_images(self, files, epoch=0): + def log_images(self, files, name='Images', epoch=0): # Log images to all loggers files = [Path(f) for f in (files if isinstance(files, (tuple, list)) else [files])] # to Path files = [f for f in files if f.exists()] # filter by exists @@ -232,7 +232,7 @@ def log_images(self, files, epoch=0): self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC') if self.wandb: - self.wandb.log({"Images": [wandb.Image(str(f), caption=f.name) for f in files]}, step=epoch) + self.wandb.log({name: [wandb.Image(str(f), caption=f.name) for f in files]}, step=epoch) def log_graph(self, model, imgsz=(640, 640)): # Log model graph to all loggers From d80c2f5e8211400469bdd11dcb669b4958d882bb Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:50:33 +0200 Subject: [PATCH 151/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 9e827aa00e1a..f7b8df8df858 100644 --- a/classifier.py +++ b/classifier.py @@ -137,7 +137,7 @@ def train(): LOGGER.info(model) images, labels = next(iter(trainloader)) file = imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') - logger.log_images(file, name='Training Images', epoch=-1) + logger.log_images(file, name='Training Images') logger.log_graph(model, imgsz) # log model # EMA @@ -245,7 +245,7 @@ def train(): images = images.to(device) pred = torch.max(model(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') - logger.log_images(file, name='Test Predictions', epoch=epoch + 1) + logger.log_images(file, name='Test Predictions', epoch=epoch) @torch.no_grad() From 8610aa82e898b27c3278f07c8e970dba98d1d894 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 19:51:43 +0200 Subject: [PATCH 152/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index f7b8df8df858..33693f1db522 100644 --- a/classifier.py +++ b/classifier.py @@ -137,7 +137,7 @@ def train(): LOGGER.info(model) images, labels = next(iter(trainloader)) file = imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') - logger.log_images(file, name='Training Images') + logger.log_images(file, name='Train Examples') logger.log_graph(model, imgsz) # log model # EMA @@ -245,7 +245,7 @@ def train(): images = images.to(device) pred = torch.max(model(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') - logger.log_images(file, name='Test Predictions', epoch=epoch) + logger.log_images(file, name='Test Examples', epoch=epoch) @torch.no_grad() From 0524acb6a7d12b94f1b97a268af8a92ffaf8277c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 20:10:18 +0200 Subject: [PATCH 153/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 33693f1db522..74ea57bec210 100644 --- a/classifier.py +++ b/classifier.py @@ -245,7 +245,7 @@ def train(): images = images.to(device) pred = torch.max(model(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') - logger.log_images(file, name='Test Examples', epoch=epoch) + logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) @torch.no_grad() From c31cfb4a3882b51d67b3be526b981fc2acec5bf5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 20:33:12 +0200 Subject: [PATCH 154/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 74ea57bec210..9c2646e3d280 100644 --- a/classifier.py +++ b/classifier.py @@ -123,10 +123,10 @@ def train(): for p in model.parameters(): p.requires_grad = True # for training elif opt.model in hub.list(repo2): # TorchVision models i.e. resnet50, efficientnet_b0, efficientnet_v2_s - model = hub.load(repo2, opt.model, pretrained=pretrained) + model = hub.load(repo2, opt.model, weights='IMAGENET1K_V1' if pretrained else None) model.classifier = nn.Linear(model.classifier.in_features, nc) else: # try torchvision - model = torchvision.models.__dict__[opt.model](pretrained=pretrained) + model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) model.fc = nn.Linear(model.fc.weight.shape[1], nc) model = model.to(device) From 0610a5de4b0b6bd8c86d2cfeeee94e67116a4091 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 20:37:38 +0200 Subject: [PATCH 155/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 9c2646e3d280..4a2829ca32e0 100644 --- a/classifier.py +++ b/classifier.py @@ -102,7 +102,7 @@ def train(): repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': - models = torch.hub.list(repo1) + torch.hub.list(repo2) + models = hub.list(repo1) + torch.hub.list(repo2) LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m From 1433d3b0b4d22c1558334004449c46eead5d755b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 20:37:55 +0200 Subject: [PATCH 156/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 4a2829ca32e0..3770a449448c 100644 --- a/classifier.py +++ b/classifier.py @@ -102,7 +102,7 @@ def train(): repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': - models = hub.list(repo1) + torch.hub.list(repo2) + models = hub.list(repo1) + hub.list(repo2) LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m From b66d142e3c2a876fdbbf46c7e0a70937dace4352 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 21:47:03 +0200 Subject: [PATCH 157/349] Update --- classifier.py | 10 +++++----- utils/torch_utils.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index 3770a449448c..3eb3ff1a932a 100644 --- a/classifier.py +++ b/classifier.py @@ -46,7 +46,7 @@ init_seeds, print_args) from utils.loggers import GenericLogger from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, - torch_distributed_zero_first) + torch_distributed_zero_first, update_classifier_model) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -120,14 +120,14 @@ def train(): c = Classify(ch, nc) # Classify() c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type model.model[-1] = c # replace - for p in model.parameters(): - p.requires_grad = True # for training elif opt.model in hub.list(repo2): # TorchVision models i.e. resnet50, efficientnet_b0, efficientnet_v2_s model = hub.load(repo2, opt.model, weights='IMAGENET1K_V1' if pretrained else None) - model.classifier = nn.Linear(model.classifier.in_features, nc) + update_classifier_model(model, nc) # update class count else: # try torchvision model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) - model.fc = nn.Linear(model.fc.weight.shape[1], nc) + update_classifier_model(model, nc) # update class count + for p in model.parameters(): + p.requires_grad = True # for training model = model.to(device) # Info diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 5f2a22c36f1a..c96f49e52f4b 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -45,6 +45,21 @@ def smart_DDP(model): return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) +def update_classifier_model(model, n=1000): + # Update a TorchVision classification model to class count 'n' + name, m = list(model.named_children())[-1] # last module + if isinstance(m, nn.Linear): + setattr(model, name, nn.Linear(m.in_features, n)) + elif isinstance(m, nn.Sequential): + types = [type(x) for x in m] + if nn.Linear in types: + i = types.index(nn.Linear) # nn.Linear index + m[i] = nn.Linear(m[i].in_features, n) + elif nn.Conv2d in types: + i = types.index(nn.Conv2d) # nn.Conv2d index + m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias) + + @contextmanager def torch_distributed_zero_first(local_rank: int): # Decorator to make all processes in distributed training wait for each local_master to do something From 3a7a4044ed70aedbd108c166297fdf37fc4dfcb5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 22:11:35 +0200 Subject: [PATCH 158/349] Update --- models/common.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/common.py b/models/common.py index f81907aefc3e..bdb07d343416 100644 --- a/models/common.py +++ b/models/common.py @@ -741,11 +741,11 @@ def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, str super().__init__() c_ = 1280 # efficientnet_b0 size self.conv = Conv(c1, c_, k, s, autopad(k, p), g) - self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1) - self.flat = nn.Flatten() # flatten to x(b,c2) - self.linear = nn.Linear(c_, c2) # to x(b,c2,1,1) + self.pool = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1) + self.drop = nn.Dropout(p=0.2, inplace=True) + self.linear = nn.Linear(c_, c2) # to x(b,c2) def forward(self, x): if isinstance(x, list): x = torch.cat(x, 1) - return self.linear(self.flat(self.aap(self.conv(x)))) + return self.linear(self.drop(self.pool(self.conv(x)).flatten(1))) From d11fc0638edcf8d62487ec72ea1dead935b67830 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 21 Jul 2022 22:25:36 +0200 Subject: [PATCH 159/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 3eb3ff1a932a..458c83d5c6c0 100644 --- a/classifier.py +++ b/classifier.py @@ -106,7 +106,7 @@ def train(): LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - kwargs = {'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} + kwargs = {'verbose': False, 'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} try: model = hub.load(repo1, opt.model, **kwargs) except Exception: From 334f8bce8b02a38fa4eaf47f977e63e7ad08ca1b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 01:06:22 +0200 Subject: [PATCH 160/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 458c83d5c6c0..904a85f15544 100644 --- a/classifier.py +++ b/classifier.py @@ -243,7 +243,7 @@ def train(): # Show predictions images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels images = images.to(device) - pred = torch.max(model(images), 1)[1] + pred = torch.max(ema.ema(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) From ebebb629f897e51c15d52f1581d0da9102ffecd6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 01:41:14 +0200 Subject: [PATCH 161/349] Update --- classifier.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 904a85f15544..ee96c113ba02 100644 --- a/classifier.py +++ b/classifier.py @@ -126,8 +126,12 @@ def train(): else: # try torchvision model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) update_classifier_model(model, nc) # update class count - for p in model.parameters(): - p.requires_grad = True # for training + for p in model.parameters(): + p.requires_grad = True # for training + if opt.dropout: + for _, m in model.named_modules(): + if isinstance(m, torch.nn.Dropout): + m.p = opt.dropout # set dropout model = model.to(device) # Info @@ -360,6 +364,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') + parser.add_argument('--dropout', type=int, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') opt = parser.parse_args() From 8faadee77a4d81867c08098cb6171bdbd2c3de10 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 01:43:31 +0200 Subject: [PATCH 162/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index ee96c113ba02..f6fcf09ec3f1 100644 --- a/classifier.py +++ b/classifier.py @@ -364,7 +364,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') - parser.add_argument('--dropout', type=int, default=None, help='Dropout (fraction)') + parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') opt = parser.parse_args() From 0a5a0ab2c2a81ed91b477e5479a7da02fd94652b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 02:32:56 +0200 Subject: [PATCH 163/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f6fcf09ec3f1..85ccaa752084 100644 --- a/classifier.py +++ b/classifier.py @@ -128,7 +128,7 @@ def train(): update_classifier_model(model, nc) # update class count for p in model.parameters(): p.requires_grad = True # for training - if opt.dropout: + if opt.dropout is not None: for _, m in model.named_modules(): if isinstance(m, torch.nn.Dropout): m.p = opt.dropout # set dropout From 7cc91ccbad4e92632b6b5e5eadb3a7c36c82555b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 11:44:47 +0200 Subject: [PATCH 164/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 85ccaa752084..3d752724d68b 100644 --- a/classifier.py +++ b/classifier.py @@ -106,7 +106,7 @@ def train(): LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - kwargs = {'verbose': False, 'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} + kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} try: model = hub.load(repo1, opt.model, **kwargs) except Exception: From a54520701ef7213dad3052da9edf817360828cc2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 11:44:54 +0200 Subject: [PATCH 165/349] Update --- hubconf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hubconf.py b/hubconf.py index 6bb9484a856d..8748279e027a 100644 --- a/hubconf.py +++ b/hubconf.py @@ -34,6 +34,7 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo from utils.general import LOGGER, check_requirements, intersect_dicts, logging from utils.torch_utils import select_device + level = LOGGER.level if not verbose: LOGGER.setLevel(logging.WARNING) check_requirements(exclude=('tensorboard', 'thop', 'opencv-python')) @@ -57,6 +58,7 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo model.names = ckpt['model'].names # set class names attribute if autoshape: model = AutoShape(model) # for file/URI/PIL/cv2/np inputs and NMS + LOGGER.setLevel(level) return model.to(device) except Exception as e: From 50aaf980d92ef3fcdd2caaad45be0f7bd3edda1f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 13:51:20 +0200 Subject: [PATCH 166/349] Update --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 3d752724d68b..ca0c6e234c58 100644 --- a/classifier.py +++ b/classifier.py @@ -120,12 +120,12 @@ def train(): c = Classify(ch, nc) # Classify() c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type model.model[-1] = c # replace - elif opt.model in hub.list(repo2): # TorchVision models i.e. resnet50, efficientnet_b0, efficientnet_v2_s - model = hub.load(repo2, opt.model, weights='IMAGENET1K_V1' if pretrained else None) - update_classifier_model(model, nc) # update class count - else: # try torchvision + elif opt.model in torchvision.models.__dict__: # TorchVision models i.e. resnet50, efficientnet_b0 model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) update_classifier_model(model, nc) # update class count + else: + models = hub.list(repo1) + hub.list(repo2) + raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(models)) for p in model.parameters(): p.requires_grad = True # for training if opt.dropout is not None: From 1c2f432dff766a357042f3524836429c103c423e Mon Sep 17 00:00:00 2001 From: Ayush Chaurasia Date: Fri, 22 Jul 2022 17:59:46 +0530 Subject: [PATCH 167/349] Allow logging models from GenericLogger (#8676) * enhance * revert * allow training from scratch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update --img argument from train.py single line * fix image size from 640 to 128 * suport custom dataloader and augmentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * Update dataloaders.py * Single line return, single line comment, remove unused argument * address PR comments * fix spelling * don't augment eval set * use fstring * update augmentations.py * new maning convention for transforms * reverse if statement, inline ops * reverse if statement, inline ops * updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update dataloaders * Remove additional if statement * Remove is_train as redundant * Cleanup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup2 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update augmentations.py * fix: imshow clip warning * update * Revert ToTensorV2 removal * Update classifier.py * Update normalize values, revert uint8 * normalize image using cv2 * remove dedundant comment * Update classifier.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * replace print with logger * commit steps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * support final model logging * update * update * update * update * remove curses * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update classifier.py * Update __init__.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Glenn Jocher --- classifier.py | 2 ++ utils/loggers/__init__.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/classifier.py b/classifier.py index ca0c6e234c58..95f305c62ea1 100644 --- a/classifier.py +++ b/classifier.py @@ -249,7 +249,9 @@ def train(): images = images.to(device) pred = torch.max(ema.ema(images), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + meta = {"epochs": epochs, "accuracy": best_fitness, "date": datetime.now().isoformat()} logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) + logger.log_model(best, epochs, metadata=meta) @torch.no_grad() diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 4820376a8fe5..8a419c20ffaa 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -239,6 +239,13 @@ def log_graph(self, model, imgsz=(640, 640)): if self.tb: log_tensorboard_graph(self.tb, model, imgsz) + def log_model(self, model_path, epoch=0, metadata={}): + # Log model to all loggers + if self.wandb: + art = wandb.Artifact(name=f"run_{wandb.run.id}_model", type="model", metadata=metadata) + art.add_file(str(model_path)) + wandb.log_artifact(art) + def log_tensorboard_graph(tb, model, imgsz=(640, 640)): # Log model graph to TensorBoard From 576056705ca3fa669b45c33d822b5b6cacf5118b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 14:39:49 +0200 Subject: [PATCH 168/349] Update --- classifier.py | 8 ++++++-- data/scripts/get_imagenet.sh | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index ca0c6e234c58..7ef46b863fba 100644 --- a/classifier.py +++ b/classifier.py @@ -20,6 +20,7 @@ import os import sys import time +import subprocess from copy import deepcopy from datetime import datetime from pathlib import Path @@ -71,8 +72,11 @@ def train(): data_dir = FILE.parents[1] / 'datasets' / data with torch_distributed_zero_first(LOCAL_RANK): if not data_dir.is_dir(): - url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' - download(url, dir=data_dir.parent) + if data == 'imagenet': + subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) + else: + url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' + download(url, dir=data_dir.parent) # Dataloaders trainloader = create_classification_dataloader(path=data_dir / 'train', diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index c3fc7852b5b9..ddfb57b035fa 100644 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -8,7 +8,7 @@ # └── imagenet ← downloads here # Download -d='./imagenet' # unzip directory +d='../datasets/imagenet' # unzip directory mkdir $d && cd $d wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # 6.3G, 50000 images wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # 138G, 1281167 images From a551986f76b6762e60223ac7ecc235e3bde3d839 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 14:49:48 +0200 Subject: [PATCH 169/349] Update --- classifier.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classifier.py b/classifier.py index 20e7e3bfc8a6..a8c8ac950d62 100644 --- a/classifier.py +++ b/classifier.py @@ -72,11 +72,13 @@ def train(): data_dir = FILE.parents[1] / 'datasets' / data with torch_distributed_zero_first(LOCAL_RANK): if not data_dir.is_dir(): + t0 = time.time() if data == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) else: url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) + LOGGER.info(f'Dataset download success ✅ ({time.time() - t0:.1f}s), saved to {colorstr(data_dir)}') # Dataloaders trainloader = create_classification_dataloader(path=data_dir / 'train', From 6c9ff8c9f6b46b5516e11ef513cabc6906219163 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 14:59:39 +0200 Subject: [PATCH 170/349] Update --- classifier.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index a8c8ac950d62..7440eed06784 100644 --- a/classifier.py +++ b/classifier.py @@ -43,8 +43,8 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, increment_path, - init_seeds, print_args) +from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, emojis, + increment_path, init_seeds, print_args) from utils.loggers import GenericLogger from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, torch_distributed_zero_first, update_classifier_model) @@ -72,13 +72,14 @@ def train(): data_dir = FILE.parents[1] / 'datasets' / data with torch_distributed_zero_first(LOCAL_RANK): if not data_dir.is_dir(): - t0 = time.time() + t = time.time() if data == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) else: url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) - LOGGER.info(f'Dataset download success ✅ ({time.time() - t0:.1f}s), saved to {colorstr(data_dir)}') + dt = time.time() - t + LOGGER.info(emojis(f"Dataset download success ✅ ({dt:.1f}s), saved to {colorstr('bold', data_dir)}")) # Dataloaders trainloader = create_classification_dataloader(path=data_dir / 'train', From bfb5c1fe1b3262bf380c5537a77e6c2dc2dcc41e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 15:14:08 +0200 Subject: [PATCH 171/349] Update --- data/scripts/get_imagenet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index ddfb57b035fa..edd48c4e4fd4 100644 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -9,7 +9,7 @@ # Download d='../datasets/imagenet' # unzip directory -mkdir $d && cd $d +mkdir -p $d && cd $d wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # 6.3G, 50000 images wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # 138G, 1281167 images From 04d3e64bb514b6c8620c82785db4aac702780184 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 15:37:40 +0200 Subject: [PATCH 172/349] Update dataset download --- classifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 7440eed06784..b808d250dbe5 100644 --- a/classifier.py +++ b/classifier.py @@ -57,8 +57,8 @@ def train(): init_seeds(1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ - Path(opt.save_dir), opt.data, opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), opt.imgsz, \ - str(opt.pretrained).lower() == 'true' + Path(opt.save_dir), Path(opt.data), opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), \ + opt.imgsz, str(opt.pretrained).lower() == 'true' # Directories wdir = save_dir / 'weights' @@ -69,9 +69,10 @@ def train(): logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None # Download Dataset - data_dir = FILE.parents[1] / 'datasets' / data with torch_distributed_zero_first(LOCAL_RANK): + data_dir = data if data.is_dir() else (FILE.parents[1] / 'datasets' / data) if not data_dir.is_dir(): + LOGGER.info(emojis(f'Dataset not found ⚠️, missing path {data_dir}, attempting download...')) t = time.time() if data == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) From dcf5670145dc9fd771890bf83e5ecf967542e3a8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 15:39:43 +0200 Subject: [PATCH 173/349] Update dataset download --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index b808d250dbe5..ee26fe9ea304 100644 --- a/classifier.py +++ b/classifier.py @@ -72,7 +72,7 @@ def train(): with torch_distributed_zero_first(LOCAL_RANK): data_dir = data if data.is_dir() else (FILE.parents[1] / 'datasets' / data) if not data_dir.is_dir(): - LOGGER.info(emojis(f'Dataset not found ⚠️, missing path {data_dir}, attempting download...')) + LOGGER.info(emojis(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...')) t = time.time() if data == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) @@ -80,7 +80,7 @@ def train(): url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) dt = time.time() - t - LOGGER.info(emojis(f"Dataset download success ✅ ({dt:.1f}s), saved to {colorstr('bold', data_dir)}")) + LOGGER.info(emojis(f"Dataset download success ✅ ({dt:.1f}s), saved to {colorstr('bold', data_dir)}\n")) # Dataloaders trainloader = create_classification_dataloader(path=data_dir / 'train', From a86fdd08e7b188c1f66ffb458aad122d7608d50f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 15:52:00 +0200 Subject: [PATCH 174/349] Update --- classifier.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index ee26fe9ea304..8312e74900fc 100644 --- a/classifier.py +++ b/classifier.py @@ -254,8 +254,7 @@ def train(): # Show predictions images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels - images = images.to(device) - pred = torch.max(ema.ema(images), 1)[1] + pred = torch.max(ema.ema(images.to(device)), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') meta = {"epochs": epochs, "accuracy": best_fitness, "date": datetime.now().isoformat()} logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) From 2a59208e13fcfbe836dcf420922a973829d2eb38 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 15:54:01 +0200 Subject: [PATCH 175/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 8312e74900fc..83c3c329ab6c 100644 --- a/classifier.py +++ b/classifier.py @@ -79,8 +79,8 @@ def train(): else: url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) - dt = time.time() - t - LOGGER.info(emojis(f"Dataset download success ✅ ({dt:.1f}s), saved to {colorstr('bold', data_dir)}\n")) + s = f"Dataset download success ✅ ({time.time() - t:.1f}s), saved to {colorstr('bold', data_dir)}\n" + LOGGER.info(emojis(s)) # Dataloaders trainloader = create_classification_dataloader(path=data_dir / 'train', From b80dd7e624a43ae88570cb3b90c9aeaff43a2a3b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 17:38:51 +0200 Subject: [PATCH 176/349] Update --- classifier.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/classifier.py b/classifier.py index 83c3c329ab6c..966d1354cab3 100644 --- a/classifier.py +++ b/classifier.py @@ -180,7 +180,7 @@ def train(): f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'accuracy':12s}") + f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'top1_acc':12s}{'top5_acc':12s}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() @@ -214,7 +214,8 @@ def train(): # Test if i == len(pbar) - 1: # last batch - fitness, vloss = test(ema.ema, testloader, names, criterion, pbar=pbar) # test accuracy, loss + top1, top5, vloss = test(ema.ema, testloader, names, criterion, pbar=pbar) # test accuracy, loss + fitness = top1 # define fitness as top1 accuracy # Scheduler scheduler.step() @@ -226,8 +227,13 @@ def train(): best_fitness = fitness # Log - lr = optimizer.param_groups[0]['lr'] # learning rate - logger.log_metrics({"train/loss": tloss, "val/loss": vloss, "metrics/accuracy": fitness, "lr/0": lr}, epoch) + metrics = { + "train/loss": tloss, + "val/loss": vloss, + "metrics/accuracy_top1": top1, + "metrics/accuracy_top5": top5, + "lr/0": optimizer.param_groups[0]['lr']} # learning rate + logger.log_metrics(metrics, epoch) # Save model final_epoch = epoch + 1 == epochs @@ -256,13 +262,13 @@ def train(): images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels pred = torch.max(ema.ema(images.to(device)), 1)[1] file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') - meta = {"epochs": epochs, "accuracy": best_fitness, "date": datetime.now().isoformat()} + meta = {"epochs": epochs, "top1_acc": best_fitness, "date": datetime.now().isoformat()} logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) logger.log_model(best, epochs, metadata=meta) @torch.no_grad() -def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): +def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches @@ -272,27 +278,28 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): for images, labels in bar: images, labels = images.to(device, non_blocking=True), labels.to(device) y = model(images) - pred.append(torch.max(y, 1)[1]) + pred.append(y.argsort(1, descending=True)[:, :5]) targets.append(labels) if criterion: loss += criterion(y, labels) loss /= n pred, targets = torch.cat(pred), torch.cat(targets) - correct = (targets == pred).float() + correct = (targets[:, None] == pred).float() + acc = torch.stack((correct[:, 0], correct.max(1).values), dim=1) # (top1, top5) accuracy + top1, top5 = acc.mean(0).tolist() if pbar: - pbar.desc += f"{loss:<12.3g}{correct.mean().item():<12.3g}" - - accuracy = correct.mean().item() + pbar.desc += f"{loss:<12.3g}{top1:<12.3g}{top5:<12.3g}" if verbose: # all classes - LOGGER.info(f"{'class':10s}{'number':10s}{'accuracy':10s}") - LOGGER.info(f"{'all':10s}{correct.shape[0]:10s}{accuracy:10.5g}") + LOGGER.info(f"{'class':10s}{'number':10s}{'top1_acc':10s}{'top5_acc':10s}") + LOGGER.info(f"{'all':10s}{targets.shape[0]:10}{top1:10.5g}{top5:10.5g}") for i, c in enumerate(names): - t = correct[targets == i] - LOGGER.info(f"{c:10s}{t.shape[0]:10s}{t.mean().item():10.5g}") + aci = acc[targets == i] + top1i, top5i = aci.mean(0).tolist() + LOGGER.info(f"{c:10s}{aci.shape[0]:10}{top1i:10.5g}{top5i:10.5g}") - return accuracy, loss + return top1, top5, loss @torch.no_grad() From 1097af8fec2e58a13bc7952fc15afad39395723c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 17:51:51 +0200 Subject: [PATCH 177/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 966d1354cab3..f46441ae8637 100644 --- a/classifier.py +++ b/classifier.py @@ -292,12 +292,12 @@ def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): if pbar: pbar.desc += f"{loss:<12.3g}{top1:<12.3g}{top5:<12.3g}" if verbose: # all classes - LOGGER.info(f"{'class':10s}{'number':10s}{'top1_acc':10s}{'top5_acc':10s}") - LOGGER.info(f"{'all':10s}{targets.shape[0]:10}{top1:10.5g}{top5:10.5g}") + LOGGER.info(f"{'class':20s}{'images':10s}{'top1_acc':10s}{'top5_acc':10s}") + LOGGER.info(f"{'all':20s}{targets.shape[0]:10}{top1:10.5g}{top5:10.5g}") for i, c in enumerate(names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:10s}{aci.shape[0]:10}{top1i:10.5g}{top5i:10.5g}") + LOGGER.info(f"{c:20s}{aci.shape[0]:10}{top1i:10.5g}{top5i:10.5g}") return top1, top5, loss From 4edd9b2c9555a7dfe07e13b9112c6f41cd6b64dc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 17:59:34 +0200 Subject: [PATCH 178/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index f46441ae8637..e426757ed0ae 100644 --- a/classifier.py +++ b/classifier.py @@ -292,12 +292,12 @@ def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): if pbar: pbar.desc += f"{loss:<12.3g}{top1:<12.3g}{top5:<12.3g}" if verbose: # all classes - LOGGER.info(f"{'class':20s}{'images':10s}{'top1_acc':10s}{'top5_acc':10s}") - LOGGER.info(f"{'all':20s}{targets.shape[0]:10}{top1:10.5g}{top5:10.5g}") + LOGGER.info(f"{'Class':>20}{'Images':>10}{'Top1_Acc':>10}{'Top5_Acc':>10}") + LOGGER.info(f"{'all':>20}{targets.shape[0]:>10}{top1:>10.5g}{top5:>10.5g}") for i, c in enumerate(names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:20s}{aci.shape[0]:10}{top1i:10.5g}{top5i:10.5g}") + LOGGER.info(f"{c:>20s}{aci.shape[0]:>10}{top1i:>10.5g}{top5i:>10.5g}") return top1, top5, loss From 9256d4560e1cdee5810f879a95bb27eb7b8a4f77 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:03:30 +0200 Subject: [PATCH 179/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index e426757ed0ae..a22d658d36d0 100644 --- a/classifier.py +++ b/classifier.py @@ -180,7 +180,7 @@ def train(): f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'epoch':10s}{'gpu_mem':10s}{'train_loss':12s}{'val_loss':12s}{'top1_acc':12s}{'top5_acc':12s}") + f"{'Epoch':>10}{'GPU_mem':>10}{'Train_loss':>12}{'Val_loss':>12=}{'Top1_Acc':>12}{'Top5_Acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() @@ -210,7 +210,7 @@ def train(): # Print tloss = (tloss * i + loss.item()) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{f'{epoch + 1}/{epochs}':10s}{mem:10s}{tloss:<12.3g}" + pbar.desc = f"{f'{epoch + 1}/{epochs}':>10}{mem:>10}{tloss:>12.3g}" # Test if i == len(pbar) - 1: # last batch @@ -290,7 +290,7 @@ def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): top1, top5 = acc.mean(0).tolist() if pbar: - pbar.desc += f"{loss:<12.3g}{top1:<12.3g}{top5:<12.3g}" + pbar.desc += f"{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" if verbose: # all classes LOGGER.info(f"{'Class':>20}{'Images':>10}{'Top1_Acc':>10}{'Top5_Acc':>10}") LOGGER.info(f"{'all':>20}{targets.shape[0]:>10}{top1:>10.5g}{top5:>10.5g}") From 1d72089a07fea5b93d998f9a15229aa0e5198593 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:05:13 +0200 Subject: [PATCH 180/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index a22d658d36d0..514c3ef9de5b 100644 --- a/classifier.py +++ b/classifier.py @@ -180,7 +180,7 @@ def train(): f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'Epoch':>10}{'GPU_mem':>10}{'Train_loss':>12}{'Val_loss':>12=}{'Top1_Acc':>12}{'Top5_Acc':>12}") + f"{'Epoch':>10}{'GPU_mem':>10}{'Train_loss':>12}{'Val_loss':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() From 73bb5ff8728c64cec1dc23c491c44f3e9ef311ac Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:08:14 +0200 Subject: [PATCH 181/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 514c3ef9de5b..9675774117f4 100644 --- a/classifier.py +++ b/classifier.py @@ -292,12 +292,12 @@ def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): if pbar: pbar.desc += f"{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" if verbose: # all classes - LOGGER.info(f"{'Class':>20}{'Images':>10}{'Top1_Acc':>10}{'Top5_Acc':>10}") - LOGGER.info(f"{'all':>20}{targets.shape[0]:>10}{top1:>10.5g}{top5:>10.5g}") + LOGGER.info(f"{'Class':>20}{'Images':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") + LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") for i, c in enumerate(names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:>20s}{aci.shape[0]:>10}{top1i:>10.5g}{top5i:>10.5g}") + LOGGER.info(f"{c:>20s}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") return top1, top5, loss From 0670b382b6a21101ee06d8e2c2d3869e44405358 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:09:38 +0200 Subject: [PATCH 182/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 9675774117f4..1c9519d5d828 100644 --- a/classifier.py +++ b/classifier.py @@ -180,7 +180,7 @@ def train(): f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'Epoch':>10}{'GPU_mem':>10}{'Train_loss':>12}{'Val_loss':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") + f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{'val_loss':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() From aa9184da2dc1fd36b9f8714e7085d3b2d8f484b1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:12:05 +0200 Subject: [PATCH 183/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 1c9519d5d828..37e76599d4d3 100644 --- a/classifier.py +++ b/classifier.py @@ -268,7 +268,7 @@ def train(): @torch.no_grad() -def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): +def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches @@ -297,7 +297,7 @@ def test(model, dataloader, names, criterion=None, verbose=True, pbar=None): for i, c in enumerate(names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:>20s}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") + LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") return top1, top5, loss From 3626508bcfb6238fc10daf1462bbab99af206a2c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:14:54 +0200 Subject: [PATCH 184/349] Update --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 37e76599d4d3..ebd00e334864 100644 --- a/classifier.py +++ b/classifier.py @@ -180,7 +180,7 @@ def train(): f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{'val_loss':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") + f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{'val_loss':>12}{'top1_acc':>12}{'top5_acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() @@ -292,7 +292,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): if pbar: pbar.desc += f"{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" if verbose: # all classes - LOGGER.info(f"{'Class':>20}{'Images':>12}{'Top1_Acc':>12}{'Top5_Acc':>12}") + LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") for i, c in enumerate(names): aci = acc[targets == i] From aa32e13e59aa80fe19993f7ba2fe949cfd10e7a0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:15:43 +0200 Subject: [PATCH 185/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index ebd00e334864..edcf3df19b8f 100644 --- a/classifier.py +++ b/classifier.py @@ -272,7 +272,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches - desc = f'{pbar.desc}validating' + desc = f'{pbar.desc} validating' bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with amp.autocast(enabled=cuda): # stability issues when enabled for images, labels in bar: From 0e010aeb21ea357e8b69689920c77b6eef798d19 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:21:43 +0200 Subject: [PATCH 186/349] Update --- classifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index edcf3df19b8f..50cb1f9c42d0 100644 --- a/classifier.py +++ b/classifier.py @@ -91,8 +91,8 @@ def train(): rank=LOCAL_RANK, workers=nw) + test_dir = data_dir / 'test' if (data_dir / 'test').exists() else data_dir / 'val' # data/test or data/val if RANK in {-1, 0}: - test_dir = data_dir / 'test' if (data_dir / 'test').exists() else data_dir / 'val' # data/test or data/val testloader = create_classification_dataloader(path=test_dir, imgsz=imgsz, batch_size=bs // WORLD_SIZE * 2, @@ -176,11 +176,12 @@ def train(): criterion = nn.CrossEntropyLoss(label_smoothing=opt.label_smoothing) # loss function best_fitness = 0.0 scaler = amp.GradScaler(enabled=cuda) + val = test_dir.stem # 'val' or 'test' LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" f'Starting training for {epochs} epochs...\n\n' - f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{'val_loss':>12}{'top1_acc':>12}{'top5_acc':>12}") + f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{f'{val}_loss':>12}{'top1_acc':>12}{'top5_acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness model.train() @@ -229,7 +230,7 @@ def train(): # Log metrics = { "train/loss": tloss, - "val/loss": vloss, + f"{val}/loss": vloss, "metrics/accuracy_top1": top1, "metrics/accuracy_top5": top5, "lr/0": optimizer.param_groups[0]['lr']} # learning rate From 1ecaf0b2b16d335a3cc85ed299a537dfa88180cc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:24:31 +0200 Subject: [PATCH 187/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 50cb1f9c42d0..e3ad3b0aa172 100644 --- a/classifier.py +++ b/classifier.py @@ -273,7 +273,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches - desc = f'{pbar.desc} validating' + desc = f"{pbar.desc} accuracy" bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with amp.autocast(enabled=cuda): # stability issues when enabled for images, labels in bar: From 685503b1b7d87daaaa00cfe96691240ccd2f6882 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:32:27 +0200 Subject: [PATCH 188/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index e3ad3b0aa172..d1895bc12c89 100644 --- a/classifier.py +++ b/classifier.py @@ -273,7 +273,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches - desc = f"{pbar.desc} accuracy" + desc = f"{pbar.desc} {'validating' if dataloader.dataset.root.stem == 'val' else 'testing'}" bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with amp.autocast(enabled=cuda): # stability issues when enabled for images, labels in bar: @@ -362,11 +362,11 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') + parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=32, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') From 101b21f150749c9d1a45a14ba0f0eb271cb243ec Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:38:23 +0200 Subject: [PATCH 189/349] Update --- classifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index d1895bc12c89..cb4d8358bcfe 100644 --- a/classifier.py +++ b/classifier.py @@ -211,7 +211,7 @@ def train(): # Print tloss = (tloss * i + loss.item()) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB) - pbar.desc = f"{f'{epoch + 1}/{epochs}':>10}{mem:>10}{tloss:>12.3g}" + pbar.desc = f"{f'{epoch + 1}/{epochs}':>10}{mem:>10}{tloss:>12.3g}" + ' ' * 36 # Test if i == len(pbar) - 1: # last batch @@ -273,7 +273,8 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches - desc = f"{pbar.desc} {'validating' if dataloader.dataset.root.stem == 'val' else 'testing'}" + action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' + desc = f"{pbar.desc[:-36]}{action:>36}" bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with amp.autocast(enabled=cuda): # stability issues when enabled for images, labels in bar: @@ -291,7 +292,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): top1, top5 = acc.mean(0).tolist() if pbar: - pbar.desc += f"{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" + pbar.desc = f"{pbar.desc[:-36]}{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" if verbose: # all classes LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") From 97d1fa6d09f2fbbc64d1bb7ef706c9620762c36d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 22 Jul 2022 18:44:21 +0200 Subject: [PATCH 190/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index cb4d8358bcfe..a4318005030e 100644 --- a/classifier.py +++ b/classifier.py @@ -379,7 +379,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--pretrained', nargs='?', const=True, default=True, help='start from i.e. --pretrained False') parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') - parser.add_argument('--lr0', type=float, default=0.0015, help='initial learning rate') + parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') From fa44db727f26936b7f5ba85d7bca153dfdca24e7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 14:07:56 +0200 Subject: [PATCH 191/349] Update --- utils/general.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/general.py b/utils/general.py index 925f7fbf0ecb..f38954f31e42 100755 --- a/utils/general.py +++ b/utils/general.py @@ -209,8 +209,8 @@ def init_seeds(seed=0, deterministic=False): np.random.seed(seed) torch.manual_seed(seed) cudnn.benchmark, cudnn.deterministic = (False, True) if seed == 0 else (True, False) - # torch.cuda.manual_seed(seed) - # torch.cuda.manual_seed_all(seed) # for multi GPU, exception safe + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) # for Multi-GPU, exception safe def intersect_dicts(da, db, exclude=()): From b751ab24344565bcc06ab79365bd4d3f8dc0bfa7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 14:16:03 +0200 Subject: [PATCH 192/349] Update --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index f38954f31e42..b049ce469a71 100755 --- a/utils/general.py +++ b/utils/general.py @@ -203,7 +203,7 @@ def init_seeds(seed=0, deterministic=False): if deterministic and check_version(torch.__version__, '1.12.0'): # https://github.com/ultralytics/yolov5/pull/8213 torch.use_deterministic_algorithms(True) os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8' - # os.environ['PYTHONHASHSEED'] = str(seed) + os.environ['PYTHONHASHSEED'] = str(seed) random.seed(seed) np.random.seed(seed) From ff8558465e1fd09ef8c9281484f8f9ef76615738 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 14:20:56 +0200 Subject: [PATCH 193/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index a4318005030e..cd35edfd7cd8 100644 --- a/classifier.py +++ b/classifier.py @@ -380,7 +380,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') - parser.add_argument('--label-smoothing', type=float, default=0.15, help='Label smoothing epsilon') + parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') From e1fb3e6f411cd1de5a20119b37d81e2d2f8d7baa Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 18:55:32 +0200 Subject: [PATCH 194/349] Pass imgsz to classify_transforms() --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 29dee61637a1..60616ef28db1 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1121,7 +1121,7 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) - self.torch_transforms = classify_transforms() + self.torch_transforms = classify_transforms(imgsz) self.album_transforms = classify_albumentations(augment, imgsz) if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' From e53521354e73c31aca842787957f0e5e0f959fc1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 21:02:34 +0200 Subject: [PATCH 195/349] Update --- classifier.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index cd35edfd7cd8..b05b01ae7c9e 100644 --- a/classifier.py +++ b/classifier.py @@ -18,9 +18,9 @@ import argparse import math import os +import subprocess import sys import time -import subprocess from copy import deepcopy from datetime import datetime from pathlib import Path @@ -43,8 +43,8 @@ from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_file, check_git_status, check_requirements, colorstr, download, emojis, - increment_path, init_seeds, print_args) +from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, + colorstr, download, emojis, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, torch_distributed_zero_first, update_classifier_model) @@ -114,7 +114,9 @@ def train(): LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False, 'trust_repo': True} + kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False} + if check_version(torch, '0.12.0'): + kwargs['trust_repo'] = True # argument required starting in torch 0.12 try: model = hub.load(repo1, opt.model, **kwargs) except Exception: From f839244ce03e4926f3052c942c7ffa4418946001 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 21:10:30 +0200 Subject: [PATCH 196/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index b05b01ae7c9e..61a1e3b6e2b7 100644 --- a/classifier.py +++ b/classifier.py @@ -115,7 +115,7 @@ def train(): return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False} - if check_version(torch, '0.12.0'): + if check_version(torch.__version__, '0.12.0'): kwargs['trust_repo'] = True # argument required starting in torch 0.12 try: model = hub.load(repo1, opt.model, **kwargs) From 2c908963b44f1307e99391d3208e348b05f660f0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 23:05:49 +0200 Subject: [PATCH 197/349] Update --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index c96f49e52f4b..770831112aaf 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -304,7 +304,7 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e- g[0].append(v.weight) if name == 'Adam': - optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999)) # adjust beta1 to momentum + optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999), amsgrad=True) # adjust beta1 to momentum elif name == 'AdamW': optimizer = torch.optim.AdamW(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0) elif name == 'RMSProp': From a6d552135b2e2904389219e65d373a7e2b3324c0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Jul 2022 23:47:17 +0200 Subject: [PATCH 198/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 61a1e3b6e2b7..a24bcc6e51bd 100644 --- a/classifier.py +++ b/classifier.py @@ -115,7 +115,7 @@ def train(): return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False} - if check_version(torch.__version__, '0.12.0'): + if check_version(torch.__version__, '1.12.0'): kwargs['trust_repo'] = True # argument required starting in torch 0.12 try: model = hub.load(repo1, opt.model, **kwargs) From 9dc5e6ace79f108c749751e4327863384dd3fd07 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 24 Jul 2022 23:29:52 +0200 Subject: [PATCH 199/349] Update --- models/common.py | 2 +- utils/torch_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index bdb07d343416..947729d387a9 100644 --- a/models/common.py +++ b/models/common.py @@ -742,7 +742,7 @@ def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, str c_ = 1280 # efficientnet_b0 size self.conv = Conv(c1, c_, k, s, autopad(k, p), g) self.pool = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1) - self.drop = nn.Dropout(p=0.2, inplace=True) + self.drop = nn.Dropout(p=0.0, inplace=True) self.linear = nn.Linear(c_, c2) # to x(b,c2) def forward(self, x): diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 770831112aaf..c96f49e52f4b 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -304,7 +304,7 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e- g[0].append(v.weight) if name == 'Adam': - optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999), amsgrad=True) # adjust beta1 to momentum + optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999)) # adjust beta1 to momentum elif name == 'AdamW': optimizer = torch.optim.AdamW(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0) elif name == 'RMSProp': From 1782d0c8cff3fdba1d89d38b95396fe002051581 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 24 Jul 2022 23:33:23 +0200 Subject: [PATCH 200/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index a24bcc6e51bd..26d63d9a5224 100644 --- a/classifier.py +++ b/classifier.py @@ -367,9 +367,9 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') - parser.add_argument('--epochs', type=int, default=10) - parser.add_argument('--batch-size', type=int, default=128, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=32, help='train, val image size (pixels)') + parser.add_argument('--epochs', type=int, default=30) + parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') From 84d6bd7bed4fad62d35dbf0c3a54b0ca20985492 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 14:37:27 +0200 Subject: [PATCH 201/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 26d63d9a5224..8fae6c82e5da 100644 --- a/classifier.py +++ b/classifier.py @@ -382,7 +382,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') - parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') + parser.add_argument('--label-smoothing', type=float, default=0.2, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') From a06efbda4124bc98ed4734b2229a6a304733c716 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 15:09:52 +0200 Subject: [PATCH 202/349] Update --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 60616ef28db1..cadfc4e4d4cb 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1122,7 +1122,7 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = classify_transforms(imgsz) - self.album_transforms = classify_albumentations(augment, imgsz) if augment else None + self.album_transforms = classify_albumentations(augment, imgsz) # if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im From 6432df0db977dad9c16530d4a5cae2cd3b228419 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 15:26:43 +0200 Subject: [PATCH 203/349] Update --- utils/dataloaders.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index cadfc4e4d4cb..803b0ab28bcb 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1122,18 +1122,17 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = classify_transforms(imgsz) - self.album_transforms = classify_albumentations(augment, imgsz) # if augment else None + self.album_transforms = classify_albumentations(augment, imgsz) if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im def __getitem__(self, i): - f, j, fn, im = self.samples[i] # filename, index, filename_npy, image + f, j, fn, im = self.samples[i] # filename, index, filename.with_suffix('.npy'), image if self.album_transforms: if self.cache_ram and im is None: im = self.samples[i][3] = cv2.imread(f) elif self.cache_disk: - # fn = Path(f).with_suffix('.npy') # filename numpy if not fn.exists(): # load npy np.save(fn.as_posix(), cv2.imread(f)) im = np.load(fn) From d86bb55c8578ab838a480b24fe1fcd2e2744162d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 15:27:55 +0200 Subject: [PATCH 204/349] Update --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 803b0ab28bcb..f8e815c398cf 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1122,7 +1122,7 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = classify_transforms(imgsz) - self.album_transforms = classify_albumentations(augment, imgsz) if augment else None + self.album_transforms = classify_albumentations(augment, imgsz) # if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im From 0d6490cf2a13b4c6d253005afbdcb4f3b2cfc9da Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 15:52:09 +0200 Subject: [PATCH 205/349] Update --- utils/augmentations.py | 2 +- utils/dataloaders.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index dbc172c2b1f7..a5791a278dfc 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -344,4 +344,4 @@ def classify_albumentations(augment=True, def classify_transforms(size=224): # Transforms to apply if albumentations not installed - return T.Compose([T.ToTensor(), T.Resize((size, size)), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) + return T.Compose([T.Resize(size), T.CenterCrop(size), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index f8e815c398cf..803b0ab28bcb 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1122,7 +1122,7 @@ class ClassificationDataset(torchvision.datasets.ImageFolder): def __init__(self, root, augment, imgsz, cache=False): super().__init__(root=root) self.torch_transforms = classify_transforms(imgsz) - self.album_transforms = classify_albumentations(augment, imgsz) # if augment else None + self.album_transforms = classify_albumentations(augment, imgsz) if augment else None self.cache_ram = cache is True or cache == 'ram' self.cache_disk = cache == 'disk' self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im From 12421e1449ce05bcedf08ab6928a590146592144 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 16:11:29 +0200 Subject: [PATCH 206/349] Update --- utils/augmentations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index a5791a278dfc..7a715cb787d3 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -344,4 +344,4 @@ def classify_albumentations(augment=True, def classify_transforms(size=224): # Transforms to apply if albumentations not installed - return T.Compose([T.Resize(size), T.CenterCrop(size), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) + return T.Compose([T.ToTensor(), T.Resize(size), T.CenterCrop(size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) From 5f3773b7520866a7867b2b1737bdc3aee15eaa9f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 25 Jul 2022 16:27:44 +0200 Subject: [PATCH 207/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 8fae6c82e5da..bbecbb2f81ef 100644 --- a/classifier.py +++ b/classifier.py @@ -365,7 +365,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist or mnist-fashion') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=30) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') From f12bc8ab0654c9533f153d8214e6a76858be5261 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 26 Jul 2022 11:52:05 +0200 Subject: [PATCH 208/349] Update --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index bbecbb2f81ef..72b841feae35 100644 --- a/classifier.py +++ b/classifier.py @@ -159,7 +159,7 @@ def train(): # Optimizer opt.lr0 *= bs if opt.optimizer == 'SGD' else 1 # sale lr with batch size for SGD - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-5) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) # Scheduler lrf = 0.01 # final lr (fraction of lr0) @@ -367,7 +367,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') - parser.add_argument('--epochs', type=int, default=30) + parser.add_argument('--epochs', type=int, default=90) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') @@ -382,7 +382,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') - parser.add_argument('--label-smoothing', type=float, default=0.2, help='Label smoothing epsilon') + parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') From 9d9e16fd59e768798010c00f6f107596ffff4470 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 26 Jul 2022 11:56:34 +0200 Subject: [PATCH 209/349] Update --- classifier.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 72b841feae35..aa4cdfeadd31 100644 --- a/classifier.py +++ b/classifier.py @@ -158,7 +158,6 @@ def train(): ema = ModelEMA(model) if RANK in {-1, 0} else None # Optimizer - opt.lr0 *= bs if opt.optimizer == 'SGD' else 1 # sale lr with batch size for SGD optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) # Scheduler @@ -200,7 +199,7 @@ def train(): loss = criterion(model(images), labels) # Backward - scaler.scale(loss).backward() + scaler.scale(loss * bs if opt.optimizer == 'SGD' else 1).backward() # sale lr with batch size for SGD # Optimize scaler.step(optimizer) From 55f0fa83a75edb8af547c932ec585c0d2af090a1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 26 Jul 2022 11:58:15 +0200 Subject: [PATCH 210/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index aa4cdfeadd31..a9d41594adc2 100644 --- a/classifier.py +++ b/classifier.py @@ -199,7 +199,7 @@ def train(): loss = criterion(model(images), labels) # Backward - scaler.scale(loss * bs if opt.optimizer == 'SGD' else 1).backward() # sale lr with batch size for SGD + scaler.scale(loss).backward() # Optimize scaler.step(optimizer) From fabac7c713565fa45295a63ffe755b7bf0dc084f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 26 Jul 2022 12:33:07 +0200 Subject: [PATCH 211/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index a9d41594adc2..f232d4f1d3fc 100644 --- a/classifier.py +++ b/classifier.py @@ -83,6 +83,7 @@ def train(): LOGGER.info(emojis(s)) # Dataloaders + nc = len([x for x in (data_dir / 'train').glob('*') if x.is_dir()]) # number of classes trainloader = create_classification_dataloader(path=data_dir / 'train', imgsz=imgsz, batch_size=bs // WORLD_SIZE, @@ -103,7 +104,6 @@ def train(): # Initialize names = trainloader.dataset.classes # class names - nc = len(names) # number of classes LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') # Model From e64ba0e3e4e303762ea8dbfe87069506c578c95c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 27 Jul 2022 02:23:45 +0200 Subject: [PATCH 212/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index f232d4f1d3fc..934ddce4289e 100644 --- a/classifier.py +++ b/classifier.py @@ -155,7 +155,7 @@ def train(): logger.log_graph(model, imgsz) # log model # EMA - ema = ModelEMA(model) if RANK in {-1, 0} else None + ema = ModelEMA(model, decay=0.0) if RANK in {-1, 0} else None # Optimizer optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) From 10387349b7dddd2fe6f7d4aa4b794aedc29a4a62 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 27 Jul 2022 15:36:01 +0200 Subject: [PATCH 213/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 934ddce4289e..681d3379e748 100644 --- a/classifier.py +++ b/classifier.py @@ -158,7 +158,7 @@ def train(): ema = ModelEMA(model, decay=0.0) if RANK in {-1, 0} else None # Optimizer - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-5) # Scheduler lrf = 0.01 # final lr (fraction of lr0) From 86dd4d9420b8b65727e6c19e431b139b4902a409 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 02:49:31 +0200 Subject: [PATCH 214/349] Cos scheduler --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 681d3379e748..2e406bff2c72 100644 --- a/classifier.py +++ b/classifier.py @@ -162,8 +162,8 @@ def train(): # Scheduler lrf = 0.01 # final lr (fraction of lr0) - # lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine - lf = lambda x: (1 - x / epochs) * (1.0 - lrf) + lrf # linear + lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine + # lf = lambda x: (1 - x / epochs) * (1 - lrf) + lrf # linear scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, # final_div_factor=1 / 25 / lrf) From 2594685418285fd01af125a0211c43b8e4a2e464 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 02:55:10 +0200 Subject: [PATCH 215/349] Cos scheduler --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 37a37f9f3ca4..76b31d5d3319 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1123,7 +1123,7 @@ def __getitem__(self, i): im = np.load(fn) else: # read image im = cv2.imread(f) # BGR - sample = self.album_transforms(image=im[..., ::-1])["image"] + sample = self.album_transforms(image=cv2.cvtColor(im, cv2.COLOR_BGR2RGB))["image"] else: sample = self.torch_transforms(self.loader(f)) return sample, j From 0251d35dc9dee92dae412477c1f8ead3b1eb9b98 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:44:55 +0200 Subject: [PATCH 216/349] Remove unused args --- classifier.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index 2e406bff2c72..b7a5013ecc88 100644 --- a/classifier.py +++ b/classifier.py @@ -365,12 +365,10 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') - parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch-low.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=90) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') - parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') @@ -378,13 +376,13 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--pretrained', nargs='?', const=True, default=True, help='start from i.e. --pretrained False') - parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') parser.add_argument('--optimizer', choices=['SGD', 'Adam', 'AdamW', 'RMSProp'], default='Adam', help='optimizer') parser.add_argument('--lr0', type=float, default=0.001, help='initial learning rate') parser.add_argument('--label-smoothing', type=float, default=0.1, help='Label smoothing epsilon') parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') + parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') opt = parser.parse_args() # Checks @@ -396,8 +394,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa # Parameters device = select_device(opt.device, batch_size=opt.batch_size) cuda = device.type != 'cpu' - opt.hyp = check_file(opt.hyp) # check files - opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve) # increment run + opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok) # increment run # DDP if LOCAL_RANK != -1: From 1f7b95cfb36e4ddce160715c55cdf27f48240a9a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:45:18 +0200 Subject: [PATCH 217/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index b7a5013ecc88..1d96ce8d64f1 100644 --- a/classifier.py +++ b/classifier.py @@ -382,7 +382,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') - parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify') + parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify') opt = parser.parse_args() # Checks From e88ba9db89f42e0d2b59e9fe0d0a9d7a5e1dc44b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:47:44 +0200 Subject: [PATCH 218/349] Add seed --- classifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 1d96ce8d64f1..5f72e55ac77a 100644 --- a/classifier.py +++ b/classifier.py @@ -55,7 +55,7 @@ def train(): - init_seeds(1 + RANK, deterministic=True) + init_seeds(opt.seed + 1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ Path(opt.save_dir), Path(opt.data), opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), \ opt.imgsz, str(opt.pretrained).lower() == 'true' @@ -382,6 +382,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--cutoff', type=int, default=None, help='Model layer cutoff index for Classify() head') parser.add_argument('--dropout', type=float, default=None, help='Dropout (fraction)') parser.add_argument('--verbose', action='store_true', help='Verbose mode') + parser.add_argument('--seed', type=int, default=0, help='Global training seed') parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify') opt = parser.parse_args() From f6100797ab4f929d37561fd8b6595eed812ca008 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:51:11 +0200 Subject: [PATCH 219/349] Add seed --- classifier.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 5f72e55ac77a..50431ee16bac 100644 --- a/classifier.py +++ b/classifier.py @@ -392,21 +392,21 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa check_git_status() check_requirements() - # Parameters + # DDP mode device = select_device(opt.device, batch_size=opt.batch_size) - cuda = device.type != 'cpu' - opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok) # increment run - - # DDP if LOCAL_RANK != -1: msg = 'is not compatible with YOLOv5 Multi-GPU DDP training' - # TODO assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size' + assert opt.batch_size != -1, 'AutoBatch is coming soon for classification, please pass a valid --batch-size' assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE' assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command' torch.cuda.set_device(LOCAL_RANK) device = torch.device('cuda', LOCAL_RANK) dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo") + # Parameters + cuda = device.type != 'cpu' + opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok) # increment run + # Train train() if WORLD_SIZE > 1 and RANK == 0: From 635ab3f4593e8f70e0753e9abbdd4199dd101209 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:51:54 +0200 Subject: [PATCH 220/349] Update --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index c298692b7335..3733c97afd14 100644 --- a/train.py +++ b/train.py @@ -497,7 +497,7 @@ def main(opt, callbacks=Callbacks()): if RANK in {-1, 0}: print_args(vars(opt)) check_git_status() - check_requirements(exclude=['thop']) + check_requirements() # Resume if opt.resume and not check_wandb_resume(opt) and not opt.evolve: # resume an interrupted run From eea5731b44f36974dd06563361a85ebcc9520085 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 13:53:46 +0200 Subject: [PATCH 221/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 50431ee16bac..29f58a747b2a 100644 --- a/classifier.py +++ b/classifier.py @@ -155,7 +155,7 @@ def train(): logger.log_graph(model, imgsz) # log model # EMA - ema = ModelEMA(model, decay=0.0) if RANK in {-1, 0} else None + ema = ModelEMA(model) if RANK in {-1, 0} else None # Optimizer optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-5) From ef749a1007a651a12373bd7cad0722e6fcbec88f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 14:02:56 +0200 Subject: [PATCH 222/349] Add run(), main() --- classifier.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/classifier.py b/classifier.py index 29f58a747b2a..640f6dcac350 100644 --- a/classifier.py +++ b/classifier.py @@ -154,9 +154,6 @@ def train(): logger.log_images(file, name='Train Examples') logger.log_graph(model, imgsz) # log model - # EMA - ema = ModelEMA(model) if RANK in {-1, 0} else None - # Optimizer optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-5) @@ -168,6 +165,9 @@ def train(): # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, # final_div_factor=1 / 25 / lrf) + # EMA + ema = ModelEMA(model) if RANK in {-1, 0} else None + # DDP mode if cuda and RANK != -1: model = smart_DDP(model) @@ -361,7 +361,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa return f -if __name__ == '__main__': +def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') @@ -384,8 +384,10 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa parser.add_argument('--verbose', action='store_true', help='Verbose mode') parser.add_argument('--seed', type=int, default=0, help='Global training seed') parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify') - opt = parser.parse_args() + return parser.parse_known_args()[0] if known else parser.parse_args() + +def main(opt): # Checks if RANK in {-1, 0}: print_args(vars(opt)) @@ -412,3 +414,17 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa if WORLD_SIZE > 1 and RANK == 0: LOGGER.info('Destroying process group... ') dist.destroy_process_group() + + +def run(**kwargs): + # Usage: import classifier; classifier.run(data=mnist, imgsz=320, model='yolov5m') + opt = parse_opt(True) + for k, v in kwargs.items(): + setattr(opt, k, v) + main(opt) + return opt + + +if __name__ == "__main__": + opt = parse_opt() + main(opt) From 8a0da6d1e0d3a3ff1c9aaed7ab9a79b76f5b1788 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 14:14:55 +0200 Subject: [PATCH 223/349] Merge master --- classifier.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 640f6dcac350..5ea0cb5e72d6 100644 --- a/classifier.py +++ b/classifier.py @@ -54,11 +54,12 @@ WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) -def train(): +def train(opt, device): init_seeds(opt.seed + 1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ Path(opt.save_dir), Path(opt.data), opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), \ opt.imgsz, str(opt.pretrained).lower() == 'true' + cuda = device.type != 'cpu' # Directories wdir = save_dir / 'weights' @@ -272,12 +273,13 @@ def train(): @torch.no_grad() def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() + device = next(model.parameters()).device pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' desc = f"{pbar.desc[:-36]}{action:>36}" bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) - with amp.autocast(enabled=cuda): # stability issues when enabled + with amp.autocast(enabled=device.type != 'cpu'): for images, labels in bar: images, labels = images.to(device, non_blocking=True), labels.to(device) y = model(images) @@ -406,11 +408,10 @@ def main(opt): dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo") # Parameters - cuda = device.type != 'cpu' opt.save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok) # increment run # Train - train() + train(opt, device) if WORLD_SIZE > 1 and RANK == 0: LOGGER.info('Destroying process group... ') dist.destroy_process_group() From a92840a047c5beb2bd849b3a1a5c623d5f8fa157 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 30 Jul 2022 14:15:39 +0200 Subject: [PATCH 224/349] Merge master --- classifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 5ea0cb5e72d6..cc4a68aa59b8 100644 --- a/classifier.py +++ b/classifier.py @@ -160,8 +160,8 @@ def train(opt, device): # Scheduler lrf = 0.01 # final lr (fraction of lr0) - lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine - # lf = lambda x: (1 - x / epochs) * (1 - lrf) + lrf # linear + # lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - lrf) + lrf # cosine + lf = lambda x: (1 - x / epochs) * (1 - lrf) + lrf # linear scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # scheduler = lr_scheduler.OneCycleLR(optimizer, max_lr=lr0, total_steps=epochs, pct_start=0.1, # final_div_factor=1 / 25 / lrf) From f43572e74844bd30667745c087c739875e185dce Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 03:46:41 +0200 Subject: [PATCH 225/349] Update --- utils/dataloaders.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 76b31d5d3319..9e65c032bb48 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1146,11 +1146,24 @@ def create_classification_dataloader(path, sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) generator = torch.Generator() generator.manual_seed(0) - return InfiniteDataLoader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - sampler=sampler, - pin_memory=True, - worker_init_fn=seed_worker, - generator=generator) + + infinite = True + if infinite: + return InfiniteDataLoader(dataset, + batch_size=batch_size, + shuffle=shuffle and sampler is None, + num_workers=nw, + sampler=sampler, + pin_memory=True, + worker_init_fn=seed_worker, + generator=generator) + else: + return DataLoader(dataset, + batch_size=batch_size, + shuffle=shuffle and sampler is None, + num_workers=nw, + sampler=sampler, + pin_memory=True, + persistent_workers=True, + worker_init_fn=seed_worker, + generator=generator) # TODO: might be DDP reproducible From cabbb30ca0ee9782fa72fd2fcf1611563588c377 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 12:12:31 +0200 Subject: [PATCH 226/349] Update --- classifier.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classifier.py b/classifier.py index cc4a68aa59b8..52879311ad10 100644 --- a/classifier.py +++ b/classifier.py @@ -203,6 +203,8 @@ def train(opt, device): scaler.scale(loss).backward() # Optimize + scaler.unscale_(optimizer) # unscale gradients + torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) # clip gradients scaler.step(optimizer) scaler.update() optimizer.zero_grad() From 59b23531d64e3bfa0653e1fbde71ac345aab1dd9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 12:17:34 +0200 Subject: [PATCH 227/349] Update --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 52879311ad10..bf3c5ca98bf1 100644 --- a/classifier.py +++ b/classifier.py @@ -139,10 +139,10 @@ def train(opt, device): raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(models)) for p in model.parameters(): p.requires_grad = True # for training - if opt.dropout is not None: - for _, m in model.named_modules(): - if isinstance(m, torch.nn.Dropout): - m.p = opt.dropout # set dropout + p.register_hook(lambda x: torch.nan_to_num(x)) # NaN to 0.0 + for m in model.modules(): + if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: + m.p = opt.dropout # set dropout model = model.to(device) # Info From c5b92f5707e1036668281d09c4f1127e86f2b661 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 18:47:47 +0200 Subject: [PATCH 228/349] Update --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index bf3c5ca98bf1..d7db08e0e364 100644 --- a/classifier.py +++ b/classifier.py @@ -156,7 +156,7 @@ def train(opt, device): logger.log_graph(model, imgsz) # log model # Optimizer - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-5) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) # Scheduler lrf = 0.01 # final lr (fraction of lr0) From ebb9c99e15d3becb3bb871584a08ec1208e69040 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 18:53:46 +0200 Subject: [PATCH 229/349] Update --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 9e65c032bb48..f34080e3f25c 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1147,7 +1147,7 @@ def create_classification_dataloader(path, generator = torch.Generator() generator.manual_seed(0) - infinite = True + infinite = False if infinite: return InfiniteDataLoader(dataset, batch_size=batch_size, From 7c1225f398bc83f892032ad3b1dac10779eadd53 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 18:58:41 +0200 Subject: [PATCH 230/349] Update --- utils/dataloaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index f34080e3f25c..9e65c032bb48 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -1147,7 +1147,7 @@ def create_classification_dataloader(path, generator = torch.Generator() generator.manual_seed(0) - infinite = False + infinite = True if infinite: return InfiniteDataLoader(dataset, batch_size=batch_size, From e24b47d7e4b0b2bca180581aaeeda4d86cda5e19 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 1 Aug 2022 21:01:13 +0200 Subject: [PATCH 231/349] Update --- classifier.py | 1 - 1 file changed, 1 deletion(-) diff --git a/classifier.py b/classifier.py index d7db08e0e364..b4659eaa67db 100644 --- a/classifier.py +++ b/classifier.py @@ -139,7 +139,6 @@ def train(opt, device): raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(models)) for p in model.parameters(): p.requires_grad = True # for training - p.register_hook(lambda x: torch.nan_to_num(x)) # NaN to 0.0 for m in model.modules(): if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout From e567764910481d6421ec5994713cb19da8358355 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 2 Aug 2022 13:37:21 +0200 Subject: [PATCH 232/349] Create YOLOv5 BaseModel class (#8829) * Create BaseModel * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix * Hub load device fix * Update Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- classifier.py | 32 ++++----- models/experimental.py | 7 +- models/yolo.py | 145 ++++++++++++++++++++++++----------------- utils/dataloaders.py | 31 +++------ utils/torch_utils.py | 10 +++ 5 files changed, 119 insertions(+), 106 deletions(-) diff --git a/classifier.py b/classifier.py index b4659eaa67db..e91f1b3a0773 100644 --- a/classifier.py +++ b/classifier.py @@ -40,13 +40,12 @@ sys.path.append(str(ROOT)) # add ROOT to PATH ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative -from models.common import Classify, DetectMultiBackend from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_file, check_git_status, check_requirements, check_version, - colorstr, download, emojis, increment_path, init_seeds, print_args) +from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, emojis, increment_path, + init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, +from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, torch_distributed_zero_first, update_classifier_model) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html @@ -115,22 +114,14 @@ def train(opt, device): LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - kwargs = {'_verbose': False, 'pretrained': pretrained, 'autoshape': False} - if check_version(torch.__version__, '1.12.0'): - kwargs['trust_repo'] = True # argument required starting in torch 0.12 - try: - model = hub.load(repo1, opt.model, **kwargs) - except Exception: - model = hub.load(repo1, opt.model, force_reload=True, **kwargs) - if isinstance(model, DetectMultiBackend): - model = model.model # unwrap DetectMultiBackend - ic = opt.cutoff or (12 if opt.model.endswith('6') else 10) # cutoff index - model.model = model.model[:ic] # backbone - m = model.model[-1] # last layer - ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module - c = Classify(ch, nc) # Classify() - c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type - model.model[-1] = c # replace + from models.yolo import ClassificationModel + model = smart_hub_load(repo1, + opt.model, + pretrained=pretrained, + _verbose=False, + autoshape=False, + device='cpu') # detection model + model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # classification model elif opt.model in torchvision.models.__dict__: # TorchVision models i.e. resnet50, efficientnet_b0 model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) update_classifier_model(model, nc) # update class count @@ -400,7 +391,6 @@ def main(opt): # DDP mode device = select_device(opt.device, batch_size=opt.batch_size) if LOCAL_RANK != -1: - msg = 'is not compatible with YOLOv5 Multi-GPU DDP training' assert opt.batch_size != -1, 'AutoBatch is coming soon for classification, please pass a valid --batch-size' assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE' assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command' diff --git a/models/experimental.py b/models/experimental.py index 0317c7526c99..8a73c268d4e5 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -92,11 +92,14 @@ def attempt_load(weights, device=None, inplace=True, fuse=True): elif t is nn.Upsample and not hasattr(m, 'recompute_scale_factor'): m.recompute_scale_factor = None # torch 1.11.0 compatibility + # Return model if len(model) == 1: - return model[-1] # return model + return model[-1] + + # Return detection ensemble print(f'Ensemble created with {weights}\n') for k in 'names', 'nc', 'yaml': setattr(model, k, getattr(model[0], k)) model.stride = model[torch.argmax(torch.tensor([m.stride.max() for m in model])).int()].stride # max stride assert all(model[0].nc == m.nc for m in model), f'Models have different class counts: {[m.nc for m in model]}' - return model # return ensemble + return model diff --git a/models/yolo.py b/models/yolo.py index bc1893ccbc48..38e84f4f72e1 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -90,8 +90,64 @@ def _make_grid(self, nx=20, ny=20, i=0): return grid, anchor_grid -class Model(nn.Module): - # YOLOv5 model +class BaseModel(nn.Module): + # YOLOv5 base model + def forward(self, x, profile=False, visualize=False): + return self._forward_once(x, profile, visualize) # single-scale inference, train + + def _forward_once(self, x, profile=False, visualize=False): + y, dt = [], [] # outputs + for m in self.model: + if m.f != -1: # if not from previous layer + x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers + if profile: + self._profile_one_layer(m, x, dt) + x = m(x) # run + y.append(x if m.i in self.save else None) # save output + if visualize: + feature_visualization(x, m.type, m.i, save_dir=visualize) + return x + + def _profile_one_layer(self, m, x, dt): + c = m == self.model[-1] # is final layer, copy input as inplace fix + o = thop.profile(m, inputs=(x.copy() if c else x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPs + t = time_sync() + for _ in range(10): + m(x.copy() if c else x) + dt.append((time_sync() - t) * 100) + if m == self.model[0]: + LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} module") + LOGGER.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}') + if c: + LOGGER.info(f"{sum(dt):10.2f} {'-':>10s} {'-':>10s} Total") + + def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers + LOGGER.info('Fusing layers... ') + for m in self.model.modules(): + if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'): + m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv + delattr(m, 'bn') # remove batchnorm + m.forward = m.forward_fuse # update forward + self.info() + return self + + def info(self, verbose=False, img_size=640): # print model information + model_info(self, verbose, img_size) + + def _apply(self, fn): + # Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers + self = super()._apply(fn) + m = self.model[-1] # Detect() + if isinstance(m, Detect): + m.stride = fn(m.stride) + m.grid = list(map(fn, m.grid)) + if isinstance(m.anchor_grid, list): + m.anchor_grid = list(map(fn, m.anchor_grid)) + return self + + +class DetectionModel(BaseModel): + # YOLOv5 detection model def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes super().__init__() if isinstance(cfg, dict): @@ -149,19 +205,6 @@ def _forward_augment(self, x): y = self._clip_augmented(y) # clip augmented tails return torch.cat(y, 1), None # augmented inference, train - def _forward_once(self, x, profile=False, visualize=False): - y, dt = [], [] # outputs - for m in self.model: - if m.f != -1: # if not from previous layer - x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers - if profile: - self._profile_one_layer(m, x, dt) - x = m(x) # run - y.append(x if m.i in self.save else None) # save output - if visualize: - feature_visualization(x, m.type, m.i, save_dir=visualize) - return x - def _descale_pred(self, p, flips, scale, img_size): # de-scale predictions following augmented inference (inverse operation) if self.inplace: @@ -190,19 +233,6 @@ def _clip_augmented(self, y): y[-1] = y[-1][:, i:] # small return y - def _profile_one_layer(self, m, x, dt): - c = isinstance(m, Detect) # is final layer, copy input as inplace fix - o = thop.profile(m, inputs=(x.copy() if c else x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPs - t = time_sync() - for _ in range(10): - m(x.copy() if c else x) - dt.append((time_sync() - t) * 100) - if m == self.model[0]: - LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} module") - LOGGER.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}') - if c: - LOGGER.info(f"{sum(dt):10.2f} {'-':>10s} {'-':>10s} Total") - def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency # https://arxiv.org/abs/1708.02002 section 3.3 # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1. @@ -213,41 +243,34 @@ def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is b[:, 5:] += math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # cls mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True) - def _print_biases(self): - m = self.model[-1] # Detect() module - for mi in m.m: # from - b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85) - LOGGER.info( - ('%6g Conv2d.bias:' + '%10.3g' * 6) % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean())) - # def _print_weights(self): - # for m in self.model.modules(): - # if type(m) is Bottleneck: - # LOGGER.info('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights +Model = DetectionModel # retain YOLOv5 'Model' class for backwards compatibility - def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers - LOGGER.info('Fusing layers... ') - for m in self.model.modules(): - if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'): - m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv - delattr(m, 'bn') # remove batchnorm - m.forward = m.forward_fuse # update forward - self.info() - return self - def info(self, verbose=False, img_size=640): # print model information - model_info(self, verbose, img_size) - - def _apply(self, fn): - # Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers - self = super()._apply(fn) - m = self.model[-1] # Detect() - if isinstance(m, Detect): - m.stride = fn(m.stride) - m.grid = list(map(fn, m.grid)) - if isinstance(m.anchor_grid, list): - m.anchor_grid = list(map(fn, m.anchor_grid)) - return self +class ClassificationModel(BaseModel): + # YOLOv5 classification model + def __init__(self, cfg=None, model=None, nc=1000, cutoff=10): # yaml, model, number of classes, cutoff index + super().__init__() + self._from_detection_model(model, nc, cutoff) if model is not None else self._from_yaml(cfg) + + def _from_detection_model(self, model, nc=1000, cutoff=10): + # Create a YOLOv5 classification model from a YOLOv5 detection model + if isinstance(model, DetectMultiBackend): + model = model.model # unwrap DetectMultiBackend + model.model = model.model[:cutoff] # backbone + m = model.model[-1] # last layer + ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module + c = Classify(ch, nc) # Classify() + c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type + model.model[-1] = c # replace + self.model = model.model + self.stride = model.stride + self.save = [] + self.nc = nc + + def _from_yaml(self, cfg): + # Create a YOLOv5 classification model from a *.yaml file + self.model = None def parse_model(d, ch): # model_dict, input_channels(3) @@ -321,7 +344,7 @@ def parse_model(d, ch): # model_dict, input_channels(3) # Options if opt.line_profile: # profile layer by layer - _ = model(im, profile=True) + model(im, profile=True) elif opt.profile: # profile forward-backward results = profile(input=im, ops=[model], n=3) diff --git a/utils/dataloaders.py b/utils/dataloaders.py index 9e65c032bb48..c6d67fc7b49d 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -83,7 +83,7 @@ def exif_transpose(image): 5: Image.TRANSPOSE, 6: Image.ROTATE_270, 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, }.get(orientation) + 8: Image.ROTATE_90,}.get(orientation) if method is not None: image = image.transpose(method) del exif[0x0112] @@ -1146,24 +1146,11 @@ def create_classification_dataloader(path, sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) generator = torch.Generator() generator.manual_seed(0) - - infinite = True - if infinite: - return InfiniteDataLoader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - sampler=sampler, - pin_memory=True, - worker_init_fn=seed_worker, - generator=generator) - else: - return DataLoader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - sampler=sampler, - pin_memory=True, - persistent_workers=True, - worker_init_fn=seed_worker, - generator=generator) # TODO: might be DDP reproducible + return InfiniteDataLoader(dataset, + batch_size=batch_size, + shuffle=shuffle and sampler is None, + num_workers=nw, + sampler=sampler, + pin_memory=True, + worker_init_fn=seed_worker, + generator=generator) # or DataLoader(persistent_workers=True) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index c96f49e52f4b..7d56f43c89d0 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -321,6 +321,16 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e- return optimizer +def smart_hub_load(repo='ultralytics/yolov5', model='yolov5s', **kwargs): + # YOLOv5 torch.hub.load() wrapper with smart error/issue handling + if check_version(torch.__version__, '1.12.0'): + kwargs['trust_repo'] = True # argument required starting in torch 0.12 + try: + return torch.hub.load(repo, model, **kwargs) + except Exception: + return torch.hub.load(repo, model, force_reload=True, **kwargs) + + class EarlyStopping: # YOLOv5 simple early stopper def __init__(self, patience=30): From 13c46bd4919f08daaaed9460b6aad3109ac3d697 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 3 Aug 2022 03:31:52 +0200 Subject: [PATCH 233/349] Add experiment --- classifier.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index e91f1b3a0773..c224a2e495ce 100644 --- a/classifier.py +++ b/classifier.py @@ -110,8 +110,8 @@ def train(opt, device): repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK): if opt.model == 'list': - models = hub.list(repo1) + hub.list(repo2) - LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(models)) + m = hub.list(repo1) + hub.list(repo2) # models + LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(m)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m from models.yolo import ClassificationModel @@ -126,13 +126,16 @@ def train(opt, device): model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) update_classifier_model(model, nc) # update class count else: - models = hub.list(repo1) + hub.list(repo2) - raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(models)) + m = hub.list(repo1) + hub.list(repo2) # models + raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) for p in model.parameters(): p.requires_grad = True # for training + from models.common import Bottleneck for m in model.modules(): if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout + if isinstance(m, Bottleneck) and hasattr(m, 'add'): + m.add = False model = model.to(device) # Info From 3284fe76fe63efd95995b9aba2613df4ef500a62 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 3 Aug 2022 15:43:24 +0200 Subject: [PATCH 234/349] Merge master --- classifier.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/classifier.py b/classifier.py index c224a2e495ce..37e58a4138b2 100644 --- a/classifier.py +++ b/classifier.py @@ -134,8 +134,6 @@ def train(opt, device): for m in model.modules(): if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout - if isinstance(m, Bottleneck) and hasattr(m, 'add'): - m.add = False model = model.to(device) # Info From 903169abb4fc9dd64744f3c4ac84182b36cf6bc4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 4 Aug 2022 22:43:17 +0200 Subject: [PATCH 235/349] Attach names --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 37e58a4138b2..24cc25888eaa 100644 --- a/classifier.py +++ b/classifier.py @@ -130,11 +130,11 @@ def train(opt, device): raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) for p in model.parameters(): p.requires_grad = True # for training - from models.common import Bottleneck for m in model.modules(): if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout model = model.to(device) + model.names = names # attach class names # Info if RANK in {-1, 0}: From 2291181b29cb726c921c12a7111bba3342f587f9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Aug 2022 00:14:23 +0200 Subject: [PATCH 236/349] weight decay = 1e-4 --- classifier.py | 2 +- utils/torch_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classifier.py b/classifier.py index 24cc25888eaa..ddced7853fdc 100644 --- a/classifier.py +++ b/classifier.py @@ -147,7 +147,7 @@ def train(opt, device): logger.log_graph(model, imgsz) # log model # Optimizer - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=2e-5) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-4) # Scheduler lrf = 0.01 # final lr (fraction of lr0) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index ded099f722a0..450410d194d4 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -316,7 +316,7 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e- optimizer.add_param_group({'params': g[0], 'weight_decay': weight_decay}) # add g0 with weight_decay optimizer.add_param_group({'params': g[1], 'weight_decay': 0.0}) # add g1 (BatchNorm2d weights) - LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__} with parameter groups " + LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__}(lr={lr}) with parameter groups " f"{len(g[1])} weight (no decay), {len(g[0])} weight, {len(g[2])} bias") return optimizer From 524e98991334d56355afb7f387b84e1941263402 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Aug 2022 00:19:01 +0200 Subject: [PATCH 237/349] weight decay = 5e-5 --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index ddced7853fdc..028d426108a2 100644 --- a/classifier.py +++ b/classifier.py @@ -147,7 +147,7 @@ def train(opt, device): logger.log_graph(model, imgsz) # log model # Optimizer - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=1e-4) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=5e-5) # Scheduler lrf = 0.01 # final lr (fraction of lr0) From 4d3fede990a85d99521a92b11fd49dc798b94c05 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Aug 2022 00:33:13 +0200 Subject: [PATCH 238/349] update smart_optimizer console printout --- classifier.py | 2 +- utils/torch_utils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index 028d426108a2..7147fac6e21e 100644 --- a/classifier.py +++ b/classifier.py @@ -147,7 +147,7 @@ def train(opt, device): logger.log_graph(model, imgsz) # log model # Optimizer - optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, weight_decay=5e-5) + optimizer = smart_optimizer(model, opt.optimizer, opt.lr0, momentum=0.9, decay=5e-5) # Scheduler lrf = 0.01 # final lr (fraction of lr0) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 450410d194d4..be47c6595ddb 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -291,7 +291,7 @@ def copy_attr(a, b, include=(), exclude=()): setattr(a, k, v) -def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e-5): +def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): # YOLOv5 3-param group optimizer: 0) weights with decay, 1) weights no decay, 2) biases no decay g = [], [], [] # optimizer parameter groups bn = tuple(v for k, v in nn.__dict__.items() if 'Norm' in k) # normalization layers, i.e. BatchNorm2d() @@ -314,10 +314,10 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, weight_decay=1e- else: raise NotImplementedError(f'Optimizer {name} not implemented.') - optimizer.add_param_group({'params': g[0], 'weight_decay': weight_decay}) # add g0 with weight_decay + optimizer.add_param_group({'params': g[0], 'weight_decay': decay}) # add g0 with weight_decay optimizer.add_param_group({'params': g[1], 'weight_decay': 0.0}) # add g1 (BatchNorm2d weights) LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__}(lr={lr}) with parameter groups " - f"{len(g[1])} weight (no decay), {len(g[0])} weight, {len(g[2])} bias") + f"{len(g[1])} weight(decay=0.0), {len(g[0])} weight(decay={decay}), {len(g[2])} bias") return optimizer From 2fe1b97b59094467c3cc76b0dba0dfc1824265b6 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 5 Aug 2022 00:41:35 +0200 Subject: [PATCH 239/349] fashion-mnist fix --- classifier.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classifier.py b/classifier.py index 7147fac6e21e..76cca485a18c 100644 --- a/classifier.py +++ b/classifier.py @@ -359,7 +359,7 @@ def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Pa def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or mnist-fashion') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or fashion-mnist') parser.add_argument('--epochs', type=int, default=90) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') From ef00f417c2616b74ccc504f7d954af3dc02ce644 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 9 Aug 2022 23:49:21 +0200 Subject: [PATCH 240/349] Merge master --- classifier.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classifier.py b/classifier.py index 76cca485a18c..322ff42c5d21 100644 --- a/classifier.py +++ b/classifier.py @@ -42,7 +42,7 @@ from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, emojis, increment_path, +from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, @@ -72,7 +72,7 @@ def train(opt, device): with torch_distributed_zero_first(LOCAL_RANK): data_dir = data if data.is_dir() else (FILE.parents[1] / 'datasets' / data) if not data_dir.is_dir(): - LOGGER.info(emojis(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...')) + LOGGER.info(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...') t = time.time() if data == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) @@ -80,7 +80,7 @@ def train(opt, device): url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' download(url, dir=data_dir.parent) s = f"Dataset download success ✅ ({time.time() - t:.1f}s), saved to {colorstr('bold', data_dir)}\n" - LOGGER.info(emojis(s)) + LOGGER.info(s) # Dataloaders nc = len([x for x in (data_dir / 'train').glob('*') if x.is_dir()]) # number of classes From 9c5a035d2c5fedf1650dc98de18a084163d6b1c3 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 12:29:11 +0200 Subject: [PATCH 241/349] Update Table --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 62c7ed4f53e6..061252490397 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,19 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi +### Classification Checkpoints + +| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
(hours) | Speed
CPU
(ms) | Speed
V100
(ms) | params
(M) | FLOPs
@640 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-----------------------------------------|---------------------------|----------------------------|--------------------|------------------------| +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 62.6 | 10.4 | 25.6 | 8.3 | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 34.5 | 13.9 | 5.3 | 0.8 | +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | 10.6 | 6.3 | 2.5 | 0.4 | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 17.9 | 7.3 | 5.5 | 1.3 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 34.8 | 9.7 | 13.0 | 3.8 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 62.9 | 13.2 | 26.6 | 8.4 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | 78.9 | 94.4 | 15:04 | 104.8 | 16.0 | 48.1 | 15.9 | + + ##
Contribute
We love your input! We want to make contributing to YOLOv5 as easy and transparent as possible. Please see our [Contributing Guide](CONTRIBUTING.md) to get started, and fill out the [YOLOv5 Survey](https://ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) to send us feedback on your experiences. Thank you to all our contributors! From bd135ba31163d0955ba57161cee6841f9aabde29 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 12:31:07 +0200 Subject: [PATCH 242/349] Update Table --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 061252490397..c46e10a7d6fc 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,14 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi | [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 62.9 | 13.2 | 26.6 | 8.4 | | [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | 78.9 | 94.4 | 15:04 | 104.8 | 16.0 | 48.1 | 15.9 | +| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
(hours) | Speed
CPU
(ms) | Speed
V100
(ms) | params
(M) | FLOPs
@640 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-----------------------------------------|---------------------------|----------------------------|--------------------|------------------------| +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 34.5 | 13.9 | 5.3 | 0.8 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 34.8 | 9.7 | 13.0 | 3.8 | +| | | | | | | | | | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 62.6 | 10.4 | 25.6 | 8.3 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 62.9 | 13.2 | 26.6 | 8.4 | + ##
Contribute
From 012476bb720c9f5ef25a7dbdb68e836cd9f21242 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 13:19:10 +0200 Subject: [PATCH 243/349] Remove destroy process group --- classifier.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/classifier.py b/classifier.py index 322ff42c5d21..65d0ab642a88 100644 --- a/classifier.py +++ b/classifier.py @@ -404,9 +404,6 @@ def main(opt): # Train train(opt, device) - if WORLD_SIZE > 1 and RANK == 0: - LOGGER.info('Destroying process group... ') - dist.destroy_process_group() def run(**kwargs): From a1e81df3031965ba565a859f422cd87d313d4833 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 17:05:17 +0200 Subject: [PATCH 244/349] add kwargs to forward() --- models/yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/yolo.py b/models/yolo.py index 38e84f4f72e1..12f8d4f95314 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -92,7 +92,7 @@ def _make_grid(self, nx=20, ny=20, i=0): class BaseModel(nn.Module): # YOLOv5 base model - def forward(self, x, profile=False, visualize=False): + def forward(self, x, profile=False, visualize=False, *args, **kwargs): return self._forward_once(x, profile, visualize) # single-scale inference, train def _forward_once(self, x, profile=False, visualize=False): From 9c17a70739341a73e7b8737e48624f9ec77837b5 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 17:50:23 +0200 Subject: [PATCH 245/349] fuse fix for resnet50 --- models/experimental.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/experimental.py b/models/experimental.py index 8a73c268d4e5..0003b3da7356 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -79,7 +79,7 @@ def attempt_load(weights, device=None, inplace=True, fuse=True): for w in weights if isinstance(weights, list) else [weights]: ckpt = torch.load(attempt_download(w), map_location='cpu') # load ckpt = (ckpt.get('ema') or ckpt['model']).to(device).float() # FP32 model - model.append(ckpt.fuse().eval() if fuse else ckpt.eval()) # fused or un-fused model in eval mode + model.append(ckpt.fuse().eval() if fuse and hasattr(ckpt, 'fuse') else ckpt.eval()) # model in eval mode # Compatibility updates for m in model.modules(): From 713288108dd4d878d2f25f3451345e9d6a2e9e6b Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 17:53:50 +0200 Subject: [PATCH 246/349] nc, names fix for resnet50 --- export.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/export.py b/export.py index 6d70724daa2a..10521e008d75 100644 --- a/export.py +++ b/export.py @@ -85,7 +85,7 @@ def export_formats(): ['TensorFlow GraphDef', 'pb', '.pb', True, True], ['TensorFlow Lite', 'tflite', '.tflite', True, False], ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False], - ['TensorFlow.js', 'tfjs', '_web_model', False, False],] + ['TensorFlow.js', 'tfjs', '_web_model', False, False], ] return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU']) @@ -444,9 +444,9 @@ def export_tfjs(file, prefix=colorstr('TensorFlow.js:')): r'"Identity.?.?": {"name": "Identity.?.?"}, ' r'"Identity.?.?": {"name": "Identity.?.?"}, ' r'"Identity.?.?": {"name": "Identity.?.?"}}}', r'{"outputs": {"Identity": {"name": "Identity"}, ' - r'"Identity_1": {"name": "Identity_1"}, ' - r'"Identity_2": {"name": "Identity_2"}, ' - r'"Identity_3": {"name": "Identity_3"}}}', json) + r'"Identity_1": {"name": "Identity_1"}, ' + r'"Identity_2": {"name": "Identity_2"}, ' + r'"Identity_3": {"name": "Identity_3"}}}', json) j.write(subst) LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)') @@ -495,11 +495,12 @@ def run( assert device.type != 'cpu' or coreml, '--half only compatible with GPU export, i.e. use --device 0' assert not dynamic, '--half not compatible with --dynamic, i.e. use either --half or --dynamic but not both' model = attempt_load(weights, device=device, inplace=True, fuse=True) # load FP32 model - nc, names = model.nc, model.names # number of classes, class names # Checks imgsz *= 2 if len(imgsz) == 1 else 1 # expand - assert nc == len(names), f'Model class count {nc} != len(names) {len(names)}' + if hasattr(model, 'names'): + nc, names = model.nc, model.names # number of classes, class names + assert nc == len(names), f'Model class count {nc} != len(names) {len(names)}' if optimize: assert device.type == 'cpu', '--optimize not compatible with cuda devices, i.e. use --device cpu' From a512ab0bc938bab261bfe24fbdc59ebfc4e45252 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 18:07:17 +0200 Subject: [PATCH 247/349] nc, names fix for resnet50 --- export.py | 3 --- models/experimental.py | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/export.py b/export.py index 10521e008d75..507ed5970985 100644 --- a/export.py +++ b/export.py @@ -498,9 +498,6 @@ def run( # Checks imgsz *= 2 if len(imgsz) == 1 else 1 # expand - if hasattr(model, 'names'): - nc, names = model.nc, model.names # number of classes, class names - assert nc == len(names), f'Model class count {nc} != len(names) {len(names)}' if optimize: assert device.type == 'cpu', '--optimize not compatible with cuda devices, i.e. use --device cpu' diff --git a/models/experimental.py b/models/experimental.py index 0003b3da7356..cb32d01ba46a 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -79,6 +79,8 @@ def attempt_load(weights, device=None, inplace=True, fuse=True): for w in weights if isinstance(weights, list) else [weights]: ckpt = torch.load(attempt_download(w), map_location='cpu') # load ckpt = (ckpt.get('ema') or ckpt['model']).to(device).float() # FP32 model + if not hasattr(ckpt, 'stride'): + ckpt.stride = torch.tensor([32.]) # compatibility update for ResNet etc. model.append(ckpt.fuse().eval() if fuse and hasattr(ckpt, 'fuse') else ckpt.eval()) # model in eval mode # Compatibility updates From c2cab3a1a4d82b79778640f7dc8c13f6b048219f Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 18:31:04 +0200 Subject: [PATCH 248/349] ONNX CPU inference fix --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 295380b24195..569e922e6f22 100644 --- a/models/common.py +++ b/models/common.py @@ -350,7 +350,7 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, net = cv2.dnn.readNetFromONNX(w) elif onnx: # ONNX Runtime LOGGER.info(f'Loading {w} for ONNX Runtime inference...') - cuda = torch.cuda.is_available() + cuda = torch.cuda.is_available() and device.type != 'cpu' check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime')) import onnxruntime providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider'] From 63027319533a65da92942e28362ed0e0f10e7bc1 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 18:32:24 +0200 Subject: [PATCH 249/349] revert --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 569e922e6f22..295380b24195 100644 --- a/models/common.py +++ b/models/common.py @@ -350,7 +350,7 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, net = cv2.dnn.readNetFromONNX(w) elif onnx: # ONNX Runtime LOGGER.info(f'Loading {w} for ONNX Runtime inference...') - cuda = torch.cuda.is_available() and device.type != 'cpu' + cuda = torch.cuda.is_available() check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime')) import onnxruntime providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider'] From 7a6033898efe1524b0814a7b082c7e50a31d12a0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 18:50:46 +0200 Subject: [PATCH 250/349] cuda --- models/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 295380b24195..05d3097fdeea 100644 --- a/models/common.py +++ b/models/common.py @@ -324,7 +324,8 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, w = str(weights[0] if isinstance(weights, list) else weights) pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs = self.model_type(w) # get backend w = attempt_download(w) # download if not local - fp16 &= (pt or jit or onnx or engine) and device.type != 'cpu' # FP16 + cuda = torch.cuda.is_available() and device.type != 'cpu' + fp16 &= (pt or jit or onnx or engine) and cuda # FP16 stride, names = 32, [f'class{i}' for i in range(1000)] # assign defaults if data: # assign class names (optional) with open(data, errors='ignore') as f: @@ -350,7 +351,6 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, net = cv2.dnn.readNetFromONNX(w) elif onnx: # ONNX Runtime LOGGER.info(f'Loading {w} for ONNX Runtime inference...') - cuda = torch.cuda.is_available() check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime')) import onnxruntime providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider'] From ea8d2371784d26e7a584e333d377a7755372f135 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 19:34:19 +0200 Subject: [PATCH 251/349] if augment or visualize --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 05d3097fdeea..99634512ffd3 100644 --- a/models/common.py +++ b/models/common.py @@ -457,7 +457,7 @@ def forward(self, im, augment=False, visualize=False, val=False): im = im.half() # to FP16 if self.pt: # PyTorch - y = self.model(im, augment=augment, visualize=visualize)[0] + y = self.model(im, augment=augment, visualize=visualize)[0] if augment or visualize else self.model(im)[0] elif self.jit: # TorchScript y = self.model(im)[0] elif self.dnn: # ONNX OpenCV DNN From 49720f78a75dc8b8bb6b231cf756f8f1a5cf15e4 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 19:50:14 +0200 Subject: [PATCH 252/349] if augment or visualize --- models/common.py | 4 +++- models/yolo.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 99634512ffd3..32038128aa31 100644 --- a/models/common.py +++ b/models/common.py @@ -457,7 +457,9 @@ def forward(self, im, augment=False, visualize=False, val=False): im = im.half() # to FP16 if self.pt: # PyTorch - y = self.model(im, augment=augment, visualize=visualize)[0] if augment or visualize else self.model(im)[0] + y = self.model(im, augment=augment, visualize=visualize) if augment or visualize else self.model(im) + if isinstance(y, tuple): + y = y[0] elif self.jit: # TorchScript y = self.model(im)[0] elif self.dnn: # ONNX OpenCV DNN diff --git a/models/yolo.py b/models/yolo.py index 12f8d4f95314..38e84f4f72e1 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -92,7 +92,7 @@ def _make_grid(self, nx=20, ny=20, i=0): class BaseModel(nn.Module): # YOLOv5 base model - def forward(self, x, profile=False, visualize=False, *args, **kwargs): + def forward(self, x, profile=False, visualize=False): return self._forward_once(x, profile, visualize) # single-scale inference, train def _forward_once(self, x, profile=False, visualize=False): From 3f5c69255ebc9da7f33e1506da882190a01072ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Aug 2022 18:27:30 +0000 Subject: [PATCH 253/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classifier.py | 4 ++-- export.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/classifier.py b/classifier.py index 65d0ab642a88..f48af4fe742f 100644 --- a/classifier.py +++ b/classifier.py @@ -42,8 +42,8 @@ from utils.augmentations import denormalize, normalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, increment_path, - init_seeds, print_args) +from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, + print_args) from utils.loggers import GenericLogger from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, torch_distributed_zero_first, update_classifier_model) diff --git a/export.py b/export.py index 507ed5970985..d381b0d004f0 100644 --- a/export.py +++ b/export.py @@ -85,7 +85,7 @@ def export_formats(): ['TensorFlow GraphDef', 'pb', '.pb', True, True], ['TensorFlow Lite', 'tflite', '.tflite', True, False], ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False], - ['TensorFlow.js', 'tfjs', '_web_model', False, False], ] + ['TensorFlow.js', 'tfjs', '_web_model', False, False],] return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU']) @@ -444,9 +444,9 @@ def export_tfjs(file, prefix=colorstr('TensorFlow.js:')): r'"Identity.?.?": {"name": "Identity.?.?"}, ' r'"Identity.?.?": {"name": "Identity.?.?"}, ' r'"Identity.?.?": {"name": "Identity.?.?"}}}', r'{"outputs": {"Identity": {"name": "Identity"}, ' - r'"Identity_1": {"name": "Identity_1"}, ' - r'"Identity_2": {"name": "Identity_2"}, ' - r'"Identity_3": {"name": "Identity_3"}}}', json) + r'"Identity_1": {"name": "Identity_1"}, ' + r'"Identity_2": {"name": "Identity_2"}, ' + r'"Identity_3": {"name": "Identity_3"}}}', json) j.write(subst) LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)') From 0a07a83ae57ee18d6b660a773b9c1551da1a27f8 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 20:42:56 +0200 Subject: [PATCH 254/349] New smart_inference_mode() --- classifier.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classifier.py b/classifier.py index f48af4fe742f..a7091fa4abca 100644 --- a/classifier.py +++ b/classifier.py @@ -45,8 +45,8 @@ from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, - torch_distributed_zero_first, update_classifier_model) +from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_inference_mode, + smart_optimizer, torch_distributed_zero_first, update_classifier_model) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -263,7 +263,7 @@ def train(opt, device): logger.log_model(best, epochs, metadata=meta) -@torch.no_grad() +@smart_inference_mode() def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): model.eval() device = next(model.parameters()).device @@ -300,7 +300,7 @@ def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): return top1, top5, loss -@torch.no_grad() +@smart_inference_mode() def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False): # YOLOv5 classification model inference import cv2 From 5fe56fcc9483d1403ce6868a5530ebb1403a9855 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sat, 13 Aug 2022 21:06:42 +0200 Subject: [PATCH 255/349] Update README --- README.md | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c46e10a7d6fc..78247f81aad2 100644 --- a/README.md +++ b/README.md @@ -258,21 +258,13 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi | Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
(hours) | Speed
CPU
(ms) | Speed
V100
(ms) | params
(M) | FLOPs
@640 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-----------------------------------------|---------------------------|----------------------------|--------------------|------------------------| -| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 62.6 | 10.4 | 25.6 | 8.3 | -| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 34.5 | 13.9 | 5.3 | 0.8 | -| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | 10.6 | 6.3 | 2.5 | 0.4 | -| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 17.9 | 7.3 | 5.5 | 1.3 | -| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 34.8 | 9.7 | 13.0 | 3.8 | -| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 62.9 | 13.2 | 26.6 | 8.4 | -| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | 78.9 | 94.4 | 15:04 | 104.8 | 16.0 | 48.1 | 15.9 | - -| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
(hours) | Speed
CPU
(ms) | Speed
V100
(ms) | params
(M) | FLOPs
@640 (B) | -|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-----------------------------------------|---------------------------|----------------------------|--------------------|------------------------| -| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 34.5 | 13.9 | 5.3 | 0.8 | -| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 34.8 | 9.7 | 13.0 | 3.8 | -| | | | | | | | | | -| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 62.6 | 10.4 | 25.6 | 8.3 | -| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 62.9 | 13.2 | 26.6 | 8.4 | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 61.8 | 9.2 | 25.6 | 8.3 | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | 8.0 | 3.9 | 2.5 | 0.4 | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 14.5 | 4.6 | 5.5 | 1.3 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 30.1 | 6.3 | 13.0 | 3.8 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 55.4 | 8.3 | 26.6 | 8.4 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | 78.9 | 94.4 | 15:04 | 96.5 | 10.1 | 48.1 | 15.9 | ##
Contribute
From 7c93f2c3aceceb090811e10de3cf0e562129bd1b Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 13:27:56 +0200 Subject: [PATCH 256/349] Refactor into /classify dir --- classify/predict.py | 112 +++++++++++++++++++++ classifier.py => classify/train.py | 146 +++++---------------------- classify/val.py | 145 +++++++++++++++++++++++++++ data/ImageNet.yaml | 156 +++++++++++++++++++++++++++++ utils/dataloaders.py | 2 +- utils/loggers/__init__.py | 2 +- utils/plots.py | 28 ++++++ 7 files changed, 468 insertions(+), 123 deletions(-) create mode 100644 classify/predict.py rename classifier.py => classify/train.py (70%) create mode 100644 classify/val.py create mode 100644 data/ImageNet.yaml diff --git a/classify/predict.py b/classify/predict.py new file mode 100644 index 000000000000..adf05be235d7 --- /dev/null +++ b/classify/predict.py @@ -0,0 +1,112 @@ +# YOLOv5 🚀 by Ultralytics, GPL-3.0 license +""" +Run classification inference on images + +Usage: + $ python classify/predict.py --weights yolov5s-cls.pt --source im.jpg +""" + +import argparse +import os +import sys +from pathlib import Path + +import torch.nn.functional as F +import yaml +from PIL import Image + +FILE = Path(__file__).resolve() +ROOT = FILE.parents[1] # YOLOv5 root directory +if str(ROOT) not in sys.path: + sys.path.append(str(ROOT)) # add ROOT to PATH +ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative + +from classify.train import imshow_cls +from utils.augmentations import classify_transforms, denormalize +from utils.general import LOGGER, check_requirements, print_args, increment_path, colorstr +from utils.torch_utils import select_device, smart_inference_mode, time_sync +from models.common import DetectMultiBackend + + +@smart_inference_mode() +def run( + weights=ROOT / 'yolov5s-cls.pt', # model.pt path(s) + source=ROOT / 'data/images/bus.jpg', # file/dir/URL/glob, 0 for webcam + imgsz=224, # inference size + device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu + half=False, # use FP16 half-precision inference + dnn=False, # use OpenCV DNN for ONNX inference + show=True, + project=ROOT / 'runs/predict-cls', # save to project/name + name='exp', # save to project/name + exist_ok=False, # existing project/name ok, do not increment +): + seen, dt = 1, [0.0, 0.0, 0.0] + + # Directories + save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run + save_dir.mkdir(parents=True, exist_ok=True) # make dir + + # YOLOv5 classification model inference + file = str(source) + transforms = classify_transforms(imgsz) + + # Load model + device = select_device(device) + model = DetectMultiBackend(weights, device=device, dnn=dnn, data=None, fp16=half) + if len(model.names) == 1000: # ImageNet + with open(ROOT / 'data/ImageNet.yaml', errors='ignore') as f: + model.names = yaml.safe_load(f)['names'] # human-readable names + + # Image + t1 = time_sync() + im = transforms(Image.open(file)).unsqueeze(0) + t2 = time_sync() + dt[0] += t2 - t1 + + # Inference + results = model(im) + t3 = time_sync() + dt[1] += t3 - t2 + + p = F.softmax(results, dim=1) # probabilities + i = p.argsort(1, descending=True)[:, :5].squeeze() # top 5 indices + dt[2] += time_sync() - t3 + LOGGER.info(f"image 1/1 {file}: {imgsz}x{imgsz} {', '.join(f'{model.names[j]} {p[0, j]:.2f}' for j in i)}") + + # Plot + if show: + imshow_cls(denormalize(im), f=save_dir / Path(file).name) + + # Print results + t = tuple(x / seen * 1E3 for x in dt) # speeds per image + shape = (1, 3, imgsz, imgsz) + LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms post-process per image at shape {shape}' % t) + LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}") + return p + + +def parse_opt(): + parser = argparse.ArgumentParser() + parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5n-cls.pt', help='model path(s)') + parser.add_argument('--source', type=str, default=ROOT / 'data/images/bus.jpg', help='file') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') + parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') + parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') + parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') + parser.add_argument('--project', default=ROOT / 'runs/predict-cls', help='save to project/name') + parser.add_argument('--name', default='exp', help='save to project/name') + parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') + opt = parser.parse_args() + print_args(vars(opt)) + return opt + + +def main(opt): + check_requirements(exclude=('tensorboard', 'thop')) + run(**vars(opt)) + + +if __name__ == "__main__": + opt = parse_opt() + main(opt) diff --git a/classifier.py b/classify/train.py similarity index 70% rename from classifier.py rename to classify/train.py index a7091fa4abca..af9cd9b3352e 100644 --- a/classifier.py +++ b/classify/train.py @@ -2,21 +2,12 @@ """ Train a YOLOv5 classifier model on a classification dataset -Usage - train: - $ python classifier.py --model yolov5s --data cifar100 --epochs 5 --img 224 --batch 128 - $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classifier.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 4,5,6,7 - -Usage - inference: - from classifier import * - - model = torch.load('path/to/best.pt', map_location=torch.device('cpu'))['model'].float() - files = Path('../datasets/mnist/test/7').glob('*.png') # images from dir - for f in list(files)[:10]: # first 10 images - classify(model, size=128, file=f) +Usage: + $ python classify/train.py --model yolov5s --data cifar100 --epochs 5 --img 224 --batch 128 + $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 0,1,2,3 """ import argparse -import math import os import subprocess import sys @@ -35,18 +26,20 @@ from tqdm import tqdm FILE = Path(__file__).resolve() -ROOT = FILE.parents[0] # YOLOv5 root directory +ROOT = FILE.parents[1] # YOLOv5 root directory if str(ROOT) not in sys.path: sys.path.append(str(ROOT)) # add ROOT to PATH ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative -from utils.augmentations import denormalize, normalize +from classify import val as validate +from utils.augmentations import denormalize from utils.dataloaders import create_classification_dataloader -from utils.general import (LOGGER, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, - print_args) +from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, + increment_path, init_seeds, print_args) from utils.loggers import GenericLogger -from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_inference_mode, - smart_optimizer, torch_distributed_zero_first, update_classifier_model) +from utils.plots import imshow_cls +from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, + torch_distributed_zero_first, update_classifier_model) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -70,7 +63,7 @@ def train(opt, device): # Download Dataset with torch_distributed_zero_first(LOCAL_RANK): - data_dir = data if data.is_dir() else (FILE.parents[1] / 'datasets' / data) + data_dir = data if data.is_dir() else (DATASETS_DIR / data) if not data_dir.is_dir(): LOGGER.info(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...') t = time.time() @@ -108,10 +101,10 @@ def train(opt, device): # Model repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' - with torch_distributed_zero_first(LOCAL_RANK): + with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): if opt.model == 'list': m = hub.list(repo1) + hub.list(repo2) # models - LOGGER.info('\nAvailable models. Usage: python classifier.py --model MODEL\n' + '\n'.join(m)) + LOGGER.info('\nAvailable models. Usage: python classify/train.py --model MODEL\n' + '\n'.join(m)) return elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m from models.yolo import ClassificationModel @@ -142,7 +135,7 @@ def train(opt, device): if opt.verbose: LOGGER.info(model) images, labels = next(iter(trainloader)) - file = imshow(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') + file = imshow_cls(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') logger.log_images(file, name='Train Examples') logger.log_graph(model, imgsz) # log model @@ -210,7 +203,11 @@ def train(opt, device): # Test if i == len(pbar) - 1: # last batch - top1, top5, vloss = test(ema.ema, testloader, names, criterion, pbar=pbar) # test accuracy, loss + top1, top5, vloss = validate.run(model=ema.ema, + dataloader=testloader, + names=names, + criterion=criterion, + pbar=pbar) # test accuracy, loss fitness = top1 # define fitness as top1 accuracy # Scheduler @@ -257,117 +254,24 @@ def train(opt, device): # Show predictions images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels pred = torch.max(ema.ema(images.to(device)), 1)[1] - file = imshow(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + file = imshow_cls(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') meta = {"epochs": epochs, "top1_acc": best_fitness, "date": datetime.now().isoformat()} logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) logger.log_model(best, epochs, metadata=meta) -@smart_inference_mode() -def test(model, dataloader, names, criterion=None, verbose=False, pbar=None): - model.eval() - device = next(model.parameters()).device - pred, targets, loss = [], [], 0 - n = len(dataloader) # number of batches - action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' - desc = f"{pbar.desc[:-36]}{action:>36}" - bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) - with amp.autocast(enabled=device.type != 'cpu'): - for images, labels in bar: - images, labels = images.to(device, non_blocking=True), labels.to(device) - y = model(images) - pred.append(y.argsort(1, descending=True)[:, :5]) - targets.append(labels) - if criterion: - loss += criterion(y, labels) - - loss /= n - pred, targets = torch.cat(pred), torch.cat(targets) - correct = (targets[:, None] == pred).float() - acc = torch.stack((correct[:, 0], correct.max(1).values), dim=1) # (top1, top5) accuracy - top1, top5 = acc.mean(0).tolist() - - if pbar: - pbar.desc = f"{pbar.desc[:-36]}{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" - if verbose: # all classes - LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") - LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") - for i, c in enumerate(names): - aci = acc[targets == i] - top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") - - return top1, top5, loss - - -@smart_inference_mode() -def classify(model, size=128, file='../datasets/mnist/test/3/30.png', plot=False): - # YOLOv5 classification model inference - import cv2 - import numpy as np - import torch.nn.functional as F - - resize = torch.nn.Upsample(size=(size, size), mode='bilinear', align_corners=False) # image resize - - # Image - im = cv2.imread(str(file))[..., ::-1] # HWC, BGR to RGB - im = np.ascontiguousarray(np.asarray(im).transpose((2, 0, 1))) # HWC to CHW - im = torch.tensor(im).float().unsqueeze(0) / 255.0 # to Tensor, to BCWH, rescale - im = resize(im) - - # Inference - results = model(normalize(im)) - p = F.softmax(results, dim=1) # probabilities - i = p.argmax() # max index - LOGGER.info(f'{file} prediction: {i} ({p[0, i]:.2f})') - - # Plot - if plot: - imshow(im, f=Path(file).name) - - return p - - -def imshow(img, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): - # Show classification image grid with labels (optional) and predictions (optional) - import matplotlib.pyplot as plt - - names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(img.cpu(), len(img), dim=0) # select batch index 0, block by channels - n = min(len(blocks), nmax) # number of plots - m = min(8, round(n ** 0.5)) # 8 x 8 default - fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols - ax = ax.ravel() if m > 1 else [ax] - # plt.subplots_adjust(wspace=0.05, hspace=0.05) - for i in range(n): - ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) - ax[i].axis('off') - if labels is not None: - s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') - ax[i].set_title(s, fontsize=8, verticalalignment='top') - plt.savefig(f, dpi=300, bbox_inches='tight') - plt.close() - LOGGER.info(colorstr('imshow: ') + f"examples saved to {f}") - if verbose: - if labels is not None: - LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax])) - if pred is not None: - LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax])) - return f - - def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist or fashion-mnist') + parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist or fashion-mnist') parser.add_argument('--epochs', type=int, default=90) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') - parser.add_argument('--project', default='runs/train', help='save to project/name') + parser.add_argument('--project', default=ROOT / 'runs/train-cls', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--pretrained', nargs='?', const=True, default=True, help='start from i.e. --pretrained False') @@ -407,7 +311,7 @@ def main(opt): def run(**kwargs): - # Usage: import classifier; classifier.run(data=mnist, imgsz=320, model='yolov5m') + # Usage: from yolov5 import classify; classify.train.run(data=mnist, imgsz=320, model='yolov5m') opt = parse_opt(True) for k, v in kwargs.items(): setattr(opt, k, v) diff --git a/classify/val.py b/classify/val.py new file mode 100644 index 000000000000..248c8b3a88e3 --- /dev/null +++ b/classify/val.py @@ -0,0 +1,145 @@ +# YOLOv5 🚀 by Ultralytics, GPL-3.0 license +""" +Validate a classification model on a dataset + +Usage: + $ python classify/val.py --weights yolov5s-cls.pt --data ../datasets/imagenet +""" + +import argparse +import os +import sys +from pathlib import Path + +import torch +from tqdm import tqdm + +FILE = Path(__file__).resolve() +ROOT = FILE.parents[1] # YOLOv5 root directory +if str(ROOT) not in sys.path: + sys.path.append(str(ROOT)) # add ROOT to PATH +ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative + +from utils.general import LOGGER, check_requirements, print_args, increment_path, check_img_size +from utils.torch_utils import select_device, smart_inference_mode +from models.common import DetectMultiBackend +from utils.dataloaders import create_classification_dataloader + + +@smart_inference_mode() +def run( + data=ROOT / '../datasets/mnist', # dataset dir + weights=None, # model.pt path(s) + batch_size=32, # batch size + imgsz=224, # inference size (pixels) + device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu + workers=8, # max dataloader workers (per RANK in DDP mode) + verbose=False, # verbose output + project=ROOT / 'runs/val-cls', # save to project/name + name='exp', # save to project/name + exist_ok=False, # existing project/name ok, do not increment + half=True, # use FP16 half-precision inference + dnn=False, # use OpenCV DNN for ONNX inference + model=None, + dataloader=None, + names=None, + criterion=None, + pbar=None +): + # Initialize/load model and set device + training = model is not None + if training: # called by train.py + device, pt, jit, engine = next(model.parameters()).device, True, False, False # get model device, PyTorch model + half &= device.type != 'cpu' # half precision only supported on CUDA + model.half() if half else model.float() + else: # called directly + device = select_device(device, batch_size=batch_size) + + # Directories + save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run + save_dir.mkdir(parents=True, exist_ok=True) # make dir + + # Load model + model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) + stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine + imgsz = check_img_size(imgsz, s=stride) # check image size + half = model.fp16 # FP16 supported on limited backends with CUDA + if engine: + batch_size = model.batch_size + else: + device = model.device + if not (pt or jit): + batch_size = 1 # export.py models default to batch-size 1 + LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models') + + # Dataloader + test_dir = data / 'test' if (data / 'test').exists() else data / 'val' # data/test or data/val + dataloader = create_classification_dataloader(path=test_dir, + imgsz=imgsz, + batch_size=batch_size, + augment=False, + cache=opt.cache, + rank=-1, + workers=workers) + + model.eval() + pred, targets, loss = [], [], 0 + n = len(dataloader) # number of batches + action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' + desc = f"{pbar.desc[:-36]}{action:>36}" + bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) + with torch.cuda.amp.autocast(enabled=device.type != 'cpu'): + for images, labels in bar: + images, labels = images.to(device, non_blocking=True), labels.to(device) + y = model(images) + pred.append(y.argsort(1, descending=True)[:, :5]) + targets.append(labels) + if criterion: + loss += criterion(y, labels) + + loss /= n + pred, targets = torch.cat(pred), torch.cat(targets) + correct = (targets[:, None] == pred).float() + acc = torch.stack((correct[:, 0], correct.max(1).values), dim=1) # (top1, top5) accuracy + top1, top5 = acc.mean(0).tolist() + + if pbar: + pbar.desc = f"{pbar.desc[:-36]}{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" + if verbose: # all classes + LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") + LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") + for i, c in enumerate(names): + aci = acc[targets == i] + top1i, top5i = aci.mean(0).tolist() + LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") + + return top1, top5, loss + + +def parse_opt(): + parser = argparse.ArgumentParser() + parser.add_argument('--data', type=str, default=ROOT / '../datasets/mnist', help='dataset path') + parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s-cls.pt', help='model.pt path(s)') + parser.add_argument('--batch-size', type=int, default=32, help='batch size') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='inference size (pixels)') + parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') + parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') + parser.add_argument('--verbose', action='store_true', help='report mAP by class') + parser.add_argument('--project', default=ROOT / 'runs/val-cls', help='save to project/name') + parser.add_argument('--name', default='exp', help='save to project/name') + parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') + parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') + parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') + opt = parser.parse_args() + print_args(vars(opt)) + return opt + + +def main(opt): + check_requirements(exclude=('tensorboard', 'thop')) + run(**vars(opt)) + + +if __name__ == "__main__": + opt = parse_opt() + main(opt) diff --git a/data/ImageNet.yaml b/data/ImageNet.yaml new file mode 100644 index 000000000000..9f89b4268aff --- /dev/null +++ b/data/ImageNet.yaml @@ -0,0 +1,156 @@ +# YOLOv5 🚀 by Ultralytics, GPL-3.0 license +# ImageNet-1k dataset https://www.image-net.org/index.php by Stanford University +# Simplified class names from https://github.com/anishathalye/imagenet-simple-labels +# Example usage: python classify/train.py --data imagenet +# parent +# ├── yolov5 +# └── datasets +# └── imagenet ← downloads here (144 GB) + + +# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] +path: ../datasets/imagenet # dataset root dir +train: train # train images (relative to 'path') 1281167 images +val: val # val images (relative to 'path') 50000 images +test: # test images (optional) + +# Classes +nc: 1000 # number of classes +names: ['tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead shark', 'electric ray', 'stingray', 'cock', + 'hen', 'ostrich', 'brambling', 'goldfinch', 'house finch', 'junco', 'indigo bunting', 'American robin', + 'bulbul', 'jay', 'magpie', 'chickadee', 'American dipper', 'kite', 'bald eagle', 'vulture', 'great grey owl', + 'fire salamander', 'smooth newt', 'newt', 'spotted salamander', 'axolotl', 'American bullfrog', 'tree frog', + 'tailed frog', 'loggerhead sea turtle', 'leatherback sea turtle', 'mud turtle', 'terrapin', 'box turtle', + 'banded gecko', 'green iguana', 'Carolina anole', 'desert grassland whiptail lizard', 'agama', + 'frilled-necked lizard', 'alligator lizard', 'Gila monster', 'European green lizard', 'chameleon', + 'Komodo dragon', 'Nile crocodile', 'American alligator', 'triceratops', 'worm snake', 'ring-necked snake', + 'eastern hog-nosed snake', 'smooth green snake', 'kingsnake', 'garter snake', 'water snake', 'vine snake', + 'night snake', 'boa constrictor', 'African rock python', 'Indian cobra', 'green mamba', 'sea snake', + 'Saharan horned viper', 'eastern diamondback rattlesnake', 'sidewinder', 'trilobite', 'harvestman', 'scorpion', + 'yellow garden spider', 'barn spider', 'European garden spider', 'southern black widow', 'tarantula', + 'wolf spider', 'tick', 'centipede', 'black grouse', 'ptarmigan', 'ruffed grouse', 'prairie grouse', 'peacock', + 'quail', 'partridge', 'grey parrot', 'macaw', 'sulphur-crested cockatoo', 'lorikeet', 'coucal', 'bee eater', + 'hornbill', 'hummingbird', 'jacamar', 'toucan', 'duck', 'red-breasted merganser', 'goose', 'black swan', + 'tusker', 'echidna', 'platypus', 'wallaby', 'koala', 'wombat', 'jellyfish', 'sea anemone', 'brain coral', + 'flatworm', 'nematode', 'conch', 'snail', 'slug', 'sea slug', 'chiton', 'chambered nautilus', 'Dungeness crab', + 'rock crab', 'fiddler crab', 'red king crab', 'American lobster', 'spiny lobster', 'crayfish', 'hermit crab', + 'isopod', 'white stork', 'black stork', 'spoonbill', 'flamingo', 'little blue heron', 'great egret', 'bittern', + 'crane (bird)', 'limpkin', 'common gallinule', 'American coot', 'bustard', 'ruddy turnstone', 'dunlin', + 'common redshank', 'dowitcher', 'oystercatcher', 'pelican', 'king penguin', 'albatross', 'grey whale', + 'killer whale', 'dugong', 'sea lion', 'Chihuahua', 'Japanese Chin', 'Maltese', 'Pekingese', 'Shih Tzu', + 'King Charles Spaniel', 'Papillon', 'toy terrier', 'Rhodesian Ridgeback', 'Afghan Hound', 'Basset Hound', + 'Beagle', 'Bloodhound', 'Bluetick Coonhound', 'Black and Tan Coonhound', 'Treeing Walker Coonhound', + 'English foxhound', 'Redbone Coonhound', 'borzoi', 'Irish Wolfhound', 'Italian Greyhound', 'Whippet', + 'Ibizan Hound', 'Norwegian Elkhound', 'Otterhound', 'Saluki', 'Scottish Deerhound', 'Weimaraner', + 'Staffordshire Bull Terrier', 'American Staffordshire Terrier', 'Bedlington Terrier', 'Border Terrier', + 'Kerry Blue Terrier', 'Irish Terrier', 'Norfolk Terrier', 'Norwich Terrier', 'Yorkshire Terrier', + 'Wire Fox Terrier', 'Lakeland Terrier', 'Sealyham Terrier', 'Airedale Terrier', 'Cairn Terrier', + 'Australian Terrier', 'Dandie Dinmont Terrier', 'Boston Terrier', 'Miniature Schnauzer', 'Giant Schnauzer', + 'Standard Schnauzer', 'Scottish Terrier', 'Tibetan Terrier', 'Australian Silky Terrier', + 'Soft-coated Wheaten Terrier', 'West Highland White Terrier', 'Lhasa Apso', 'Flat-Coated Retriever', + 'Curly-coated Retriever', 'Golden Retriever', 'Labrador Retriever', 'Chesapeake Bay Retriever', + 'German Shorthaired Pointer', 'Vizsla', 'English Setter', 'Irish Setter', 'Gordon Setter', 'Brittany', + 'Clumber Spaniel', 'English Springer Spaniel', 'Welsh Springer Spaniel', 'Cocker Spaniels', 'Sussex Spaniel', + 'Irish Water Spaniel', 'Kuvasz', 'Schipperke', 'Groenendael', 'Malinois', 'Briard', 'Australian Kelpie', + 'Komondor', 'Old English Sheepdog', 'Shetland Sheepdog', 'collie', 'Border Collie', 'Bouvier des Flandres', + 'Rottweiler', 'German Shepherd Dog', 'Dobermann', 'Miniature Pinscher', 'Greater Swiss Mountain Dog', + 'Bernese Mountain Dog', 'Appenzeller Sennenhund', 'Entlebucher Sennenhund', 'Boxer', 'Bullmastiff', + 'Tibetan Mastiff', 'French Bulldog', 'Great Dane', 'St. Bernard', 'husky', 'Alaskan Malamute', 'Siberian Husky', + 'Dalmatian', 'Affenpinscher', 'Basenji', 'pug', 'Leonberger', 'Newfoundland', 'Pyrenean Mountain Dog', + 'Samoyed', 'Pomeranian', 'Chow Chow', 'Keeshond', 'Griffon Bruxellois', 'Pembroke Welsh Corgi', + 'Cardigan Welsh Corgi', 'Toy Poodle', 'Miniature Poodle', 'Standard Poodle', 'Mexican hairless dog', + 'grey wolf', 'Alaskan tundra wolf', 'red wolf', 'coyote', 'dingo', 'dhole', 'African wild dog', 'hyena', + 'red fox', 'kit fox', 'Arctic fox', 'grey fox', 'tabby cat', 'tiger cat', 'Persian cat', 'Siamese cat', + 'Egyptian Mau', 'cougar', 'lynx', 'leopard', 'snow leopard', 'jaguar', 'lion', 'tiger', 'cheetah', 'brown bear', + 'American black bear', 'polar bear', 'sloth bear', 'mongoose', 'meerkat', 'tiger beetle', 'ladybug', + 'ground beetle', 'longhorn beetle', 'leaf beetle', 'dung beetle', 'rhinoceros beetle', 'weevil', 'fly', 'bee', + 'ant', 'grasshopper', 'cricket', 'stick insect', 'cockroach', 'mantis', 'cicada', 'leafhopper', 'lacewing', + 'dragonfly', 'damselfly', 'red admiral', 'ringlet', 'monarch butterfly', 'small white', 'sulphur butterfly', + 'gossamer-winged butterfly', 'starfish', 'sea urchin', 'sea cucumber', 'cottontail rabbit', 'hare', + 'Angora rabbit', 'hamster', 'porcupine', 'fox squirrel', 'marmot', 'beaver', 'guinea pig', 'common sorrel', + 'zebra', 'pig', 'wild boar', 'warthog', 'hippopotamus', 'ox', 'water buffalo', 'bison', 'ram', 'bighorn sheep', + 'Alpine ibex', 'hartebeest', 'impala', 'gazelle', 'dromedary', 'llama', 'weasel', 'mink', 'European polecat', + 'black-footed ferret', 'otter', 'skunk', 'badger', 'armadillo', 'three-toed sloth', 'orangutan', 'gorilla', + 'chimpanzee', 'gibbon', 'siamang', 'guenon', 'patas monkey', 'baboon', 'macaque', 'langur', + 'black-and-white colobus', 'proboscis monkey', 'marmoset', 'white-headed capuchin', 'howler monkey', 'titi', + "Geoffroy's spider monkey", 'common squirrel monkey', 'ring-tailed lemur', 'indri', 'Asian elephant', + 'African bush elephant', 'red panda', 'giant panda', 'snoek', 'eel', 'coho salmon', 'rock beauty', 'clownfish', + 'sturgeon', 'garfish', 'lionfish', 'pufferfish', 'abacus', 'abaya', 'academic gown', 'accordion', + 'acoustic guitar', 'aircraft carrier', 'airliner', 'airship', 'altar', 'ambulance', 'amphibious vehicle', + 'analog clock', 'apiary', 'apron', 'waste container', 'assault rifle', 'backpack', 'bakery', 'balance beam', + 'balloon', 'ballpoint pen', 'Band-Aid', 'banjo', 'baluster', 'barbell', 'barber chair', 'barbershop', 'barn', + 'barometer', 'barrel', 'wheelbarrow', 'baseball', 'basketball', 'bassinet', 'bassoon', 'swimming cap', + 'bath towel', 'bathtub', 'station wagon', 'lighthouse', 'beaker', 'military cap', 'beer bottle', 'beer glass', + 'bell-cot', 'bib', 'tandem bicycle', 'bikini', 'ring binder', 'binoculars', 'birdhouse', 'boathouse', + 'bobsleigh', 'bolo tie', 'poke bonnet', 'bookcase', 'bookstore', 'bottle cap', 'bow', 'bow tie', 'brass', 'bra', + 'breakwater', 'breastplate', 'broom', 'bucket', 'buckle', 'bulletproof vest', 'high-speed train', + 'butcher shop', 'taxicab', 'cauldron', 'candle', 'cannon', 'canoe', 'can opener', 'cardigan', 'car mirror', + 'carousel', 'tool kit', 'carton', 'car wheel', 'automated teller machine', 'cassette', 'cassette player', + 'castle', 'catamaran', 'CD player', 'cello', 'mobile phone', 'chain', 'chain-link fence', 'chain mail', + 'chainsaw', 'chest', 'chiffonier', 'chime', 'china cabinet', 'Christmas stocking', 'church', 'movie theater', + 'cleaver', 'cliff dwelling', 'cloak', 'clogs', 'cocktail shaker', 'coffee mug', 'coffeemaker', 'coil', + 'combination lock', 'computer keyboard', 'confectionery store', 'container ship', 'convertible', 'corkscrew', + 'cornet', 'cowboy boot', 'cowboy hat', 'cradle', 'crane (machine)', 'crash helmet', 'crate', 'infant bed', + 'Crock Pot', 'croquet ball', 'crutch', 'cuirass', 'dam', 'desk', 'desktop computer', 'rotary dial telephone', + 'diaper', 'digital clock', 'digital watch', 'dining table', 'dishcloth', 'dishwasher', 'disc brake', 'dock', + 'dog sled', 'dome', 'doormat', 'drilling rig', 'drum', 'drumstick', 'dumbbell', 'Dutch oven', 'electric fan', + 'electric guitar', 'electric locomotive', 'entertainment center', 'envelope', 'espresso machine', 'face powder', + 'feather boa', 'filing cabinet', 'fireboat', 'fire engine', 'fire screen sheet', 'flagpole', 'flute', + 'folding chair', 'football helmet', 'forklift', 'fountain', 'fountain pen', 'four-poster bed', 'freight car', + 'French horn', 'frying pan', 'fur coat', 'garbage truck', 'gas mask', 'gas pump', 'goblet', 'go-kart', + 'golf ball', 'golf cart', 'gondola', 'gong', 'gown', 'grand piano', 'greenhouse', 'grille', 'grocery store', + 'guillotine', 'barrette', 'hair spray', 'half-track', 'hammer', 'hamper', 'hair dryer', 'hand-held computer', + 'handkerchief', 'hard disk drive', 'harmonica', 'harp', 'harvester', 'hatchet', 'holster', 'home theater', + 'honeycomb', 'hook', 'hoop skirt', 'horizontal bar', 'horse-drawn vehicle', 'hourglass', 'iPod', 'clothes iron', + "jack-o'-lantern", 'jeans', 'jeep', 'T-shirt', 'jigsaw puzzle', 'pulled rickshaw', 'joystick', 'kimono', + 'knee pad', 'knot', 'lab coat', 'ladle', 'lampshade', 'laptop computer', 'lawn mower', 'lens cap', + 'paper knife', 'library', 'lifeboat', 'lighter', 'limousine', 'ocean liner', 'lipstick', 'slip-on shoe', + 'lotion', 'speaker', 'loupe', 'sawmill', 'magnetic compass', 'mail bag', 'mailbox', 'tights', 'tank suit', + 'manhole cover', 'maraca', 'marimba', 'mask', 'match', 'maypole', 'maze', 'measuring cup', 'medicine chest', + 'megalith', 'microphone', 'microwave oven', 'military uniform', 'milk can', 'minibus', 'miniskirt', 'minivan', + 'missile', 'mitten', 'mixing bowl', 'mobile home', 'Model T', 'modem', 'monastery', 'monitor', 'moped', + 'mortar', 'square academic cap', 'mosque', 'mosquito net', 'scooter', 'mountain bike', 'tent', 'computer mouse', + 'mousetrap', 'moving van', 'muzzle', 'nail', 'neck brace', 'necklace', 'nipple', 'notebook computer', 'obelisk', + 'oboe', 'ocarina', 'odometer', 'oil filter', 'organ', 'oscilloscope', 'overskirt', 'bullock cart', + 'oxygen mask', 'packet', 'paddle', 'paddle wheel', 'padlock', 'paintbrush', 'pajamas', 'palace', 'pan flute', + 'paper towel', 'parachute', 'parallel bars', 'park bench', 'parking meter', 'passenger car', 'patio', + 'payphone', 'pedestal', 'pencil case', 'pencil sharpener', 'perfume', 'Petri dish', 'photocopier', 'plectrum', + 'Pickelhaube', 'picket fence', 'pickup truck', 'pier', 'piggy bank', 'pill bottle', 'pillow', 'ping-pong ball', + 'pinwheel', 'pirate ship', 'pitcher', 'hand plane', 'planetarium', 'plastic bag', 'plate rack', 'plow', + 'plunger', 'Polaroid camera', 'pole', 'police van', 'poncho', 'billiard table', 'soda bottle', 'pot', + "potter's wheel", 'power drill', 'prayer rug', 'printer', 'prison', 'projectile', 'projector', 'hockey puck', + 'punching bag', 'purse', 'quill', 'quilt', 'race car', 'racket', 'radiator', 'radio', 'radio telescope', + 'rain barrel', 'recreational vehicle', 'reel', 'reflex camera', 'refrigerator', 'remote control', 'restaurant', + 'revolver', 'rifle', 'rocking chair', 'rotisserie', 'eraser', 'rugby ball', 'ruler', 'running shoe', 'safe', + 'safety pin', 'salt shaker', 'sandal', 'sarong', 'saxophone', 'scabbard', 'weighing scale', 'school bus', + 'schooner', 'scoreboard', 'CRT screen', 'screw', 'screwdriver', 'seat belt', 'sewing machine', 'shield', + 'shoe store', 'shoji', 'shopping basket', 'shopping cart', 'shovel', 'shower cap', 'shower curtain', 'ski', + 'ski mask', 'sleeping bag', 'slide rule', 'sliding door', 'slot machine', 'snorkel', 'snowmobile', 'snowplow', + 'soap dispenser', 'soccer ball', 'sock', 'solar thermal collector', 'sombrero', 'soup bowl', 'space bar', + 'space heater', 'space shuttle', 'spatula', 'motorboat', 'spider web', 'spindle', 'sports car', 'spotlight', + 'stage', 'steam locomotive', 'through arch bridge', 'steel drum', 'stethoscope', 'scarf', 'stone wall', + 'stopwatch', 'stove', 'strainer', 'tram', 'stretcher', 'couch', 'stupa', 'submarine', 'suit', 'sundial', + 'sunglass', 'sunglasses', 'sunscreen', 'suspension bridge', 'mop', 'sweatshirt', 'swimsuit', 'swing', 'switch', + 'syringe', 'table lamp', 'tank', 'tape player', 'teapot', 'teddy bear', 'television', 'tennis ball', + 'thatched roof', 'front curtain', 'thimble', 'threshing machine', 'throne', 'tile roof', 'toaster', + 'tobacco shop', 'toilet seat', 'torch', 'totem pole', 'tow truck', 'toy store', 'tractor', 'semi-trailer truck', + 'tray', 'trench coat', 'tricycle', 'trimaran', 'tripod', 'triumphal arch', 'trolleybus', 'trombone', 'tub', + 'turnstile', 'typewriter keyboard', 'umbrella', 'unicycle', 'upright piano', 'vacuum cleaner', 'vase', 'vault', + 'velvet', 'vending machine', 'vestment', 'viaduct', 'violin', 'volleyball', 'waffle iron', 'wall clock', + 'wallet', 'wardrobe', 'military aircraft', 'sink', 'washing machine', 'water bottle', 'water jug', + 'water tower', 'whiskey jug', 'whistle', 'wig', 'window screen', 'window shade', 'Windsor tie', 'wine bottle', + 'wing', 'wok', 'wooden spoon', 'wool', 'split-rail fence', 'shipwreck', 'yawl', 'yurt', 'website', 'comic book', + 'crossword', 'traffic sign', 'traffic light', 'dust jacket', 'menu', 'plate', 'guacamole', 'consomme', + 'hot pot', 'trifle', 'ice cream', 'ice pop', 'baguette', 'bagel', 'pretzel', 'cheeseburger', 'hot dog', + 'mashed potato', 'cabbage', 'broccoli', 'cauliflower', 'zucchini', 'spaghetti squash', 'acorn squash', + 'butternut squash', 'cucumber', 'artichoke', 'bell pepper', 'cardoon', 'mushroom', 'Granny Smith', 'strawberry', + 'orange', 'lemon', 'fig', 'pineapple', 'banana', 'jackfruit', 'custard apple', 'pomegranate', 'hay', + 'carbonara', 'chocolate syrup', 'dough', 'meatloaf', 'pizza', 'pot pie', 'burrito', 'red wine', 'espresso', + 'cup', 'eggnog', 'alp', 'bubble', 'cliff', 'coral reef', 'geyser', 'lakeshore', 'promontory', 'shoal', + 'seashore', 'valley', 'volcano', 'baseball player', 'bridegroom', 'scuba diver', 'rapeseed', 'daisy', + "yellow lady's slipper", 'corn', 'acorn', 'rose hip', 'horse chestnut seed', 'coral fungus', 'agaric', + 'gyromitra', 'stinkhorn mushroom', 'earth star', 'hen-of-the-woods', 'bolete', 'ear', + 'toilet paper'] # class names + +# Download script/URL (optional) +download: data/scripts/get_imagenet.sh diff --git a/utils/dataloaders.py b/utils/dataloaders.py index af3cf230afbb..2c04040bf25d 100755 --- a/utils/dataloaders.py +++ b/utils/dataloaders.py @@ -872,7 +872,7 @@ def flatten_recursive(path=DATASETS_DIR / 'coco128'): def extract_boxes(path=DATASETS_DIR / 'coco128'): # from utils.dataloaders import *; extract_boxes() # Convert detection dataset into classification dataset, with one directory per class path = Path(path) # images dir - shutil.rmtree(path / 'classifier') if (path / 'classifier').is_dir() else None # remove existing + shutil.rmtree(path / 'classification') if (path / 'classification').is_dir() else None # remove existing files = list(path.rglob('*.*')) n = len(files) # number of files for im_file in tqdm(files, total=n): diff --git a/utils/loggers/__init__.py b/utils/loggers/__init__.py index 47a5f36bc779..8ec846f8cfac 100644 --- a/utils/loggers/__init__.py +++ b/utils/loggers/__init__.py @@ -77,7 +77,7 @@ def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None, self.logger.info(s) if not clearml: prefix = colorstr('ClearML: ') - s = f"{prefix}run 'pip install clearml' to automatically track, visualize and remotely train YOLOv5 🚀 runs in ClearML" + s = f"{prefix}run 'pip install clearml' to automatically track, visualize and remotely train YOLOv5 🚀 in ClearML" self.logger.info(s) # TensorBoard diff --git a/utils/plots.py b/utils/plots.py index d050f5d36aba..f1bd15b10b4f 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -388,6 +388,34 @@ def plot_labels(labels, names=(), save_dir=Path('')): plt.close() +def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): + # Show classification image grid with labels (optional) and predictions (optional) + import matplotlib.pyplot as plt + + names = names or [f'class{i}' for i in range(1000)] + blocks = torch.chunk(im.cpu(), len(im), dim=0) # select batch index 0, block by channels + n = min(len(blocks), nmax) # number of plots + m = min(8, round(n ** 0.5)) # 8 x 8 default + fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols + ax = ax.ravel() if m > 1 else [ax] + # plt.subplots_adjust(wspace=0.05, hspace=0.05) + for i in range(n): + ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) + ax[i].axis('off') + if labels is not None: + s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') + ax[i].set_title(s, fontsize=8, verticalalignment='top') + plt.savefig(f, dpi=300, bbox_inches='tight') + plt.close() + if verbose: + LOGGER.info(f"Saving {f}") + if labels is not None: + LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax])) + if pred is not None: + LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax])) + return f + + def plot_evolve(evolve_csv='path/to/evolve.csv'): # from utils.plots import *; plot_evolve() # Plot evolve.csv hyp evolution results evolve_csv = Path(evolve_csv) From ea8729b0b4dce556213ea0080c2603f2ed7d6c07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 11:28:23 +0000 Subject: [PATCH 257/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classify/predict.py | 4 ++-- classify/train.py | 4 ++-- classify/val.py | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index adf05be235d7..918f5baa1e0b 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -22,10 +22,10 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from classify.train import imshow_cls +from models.common import DetectMultiBackend from utils.augmentations import classify_transforms, denormalize -from utils.general import LOGGER, check_requirements, print_args, increment_path, colorstr +from utils.general import LOGGER, check_requirements, colorstr, increment_path, print_args from utils.torch_utils import select_device, smart_inference_mode, time_sync -from models.common import DetectMultiBackend @smart_inference_mode() diff --git a/classify/train.py b/classify/train.py index af9cd9b3352e..deeb14b16743 100644 --- a/classify/train.py +++ b/classify/train.py @@ -34,8 +34,8 @@ from classify import val as validate from utils.augmentations import denormalize from utils.dataloaders import create_classification_dataloader -from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, - increment_path, init_seeds, print_args) +from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, + download, increment_path, init_seeds, print_args) from utils.loggers import GenericLogger from utils.plots import imshow_cls from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, diff --git a/classify/val.py b/classify/val.py index 248c8b3a88e3..dfd199417102 100644 --- a/classify/val.py +++ b/classify/val.py @@ -20,10 +20,10 @@ sys.path.append(str(ROOT)) # add ROOT to PATH ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative -from utils.general import LOGGER, check_requirements, print_args, increment_path, check_img_size -from utils.torch_utils import select_device, smart_inference_mode from models.common import DetectMultiBackend from utils.dataloaders import create_classification_dataloader +from utils.general import LOGGER, check_img_size, check_requirements, increment_path, print_args +from utils.torch_utils import select_device, smart_inference_mode @smart_inference_mode() @@ -44,8 +44,7 @@ def run( dataloader=None, names=None, criterion=None, - pbar=None -): + pbar=None): # Initialize/load model and set device training = model is not None if training: # called by train.py From 30e46eda3d0378e88f9efe6262f58c438a31c77d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 11:35:30 +0000 Subject: [PATCH 258/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classify/val.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/classify/val.py b/classify/val.py index 5cb5e4eab4a8..63405e907d8f 100644 --- a/classify/val.py +++ b/classify/val.py @@ -28,23 +28,23 @@ @smart_inference_mode() def run( - data=ROOT / '../datasets/mnist', # dataset dir - weights=None, # model.pt path(s) - batch_size=32, # batch size - imgsz=224, # inference size (pixels) - device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu - workers=8, # max dataloader workers (per RANK in DDP mode) - verbose=False, # verbose output - project=ROOT / 'runs/val-cls', # save to project/name - name='exp', # save to project/name - exist_ok=False, # existing project/name ok, do not increment - half=True, # use FP16 half-precision inference - dnn=False, # use OpenCV DNN for ONNX inference - model=None, - dataloader=None, - names=None, - criterion=None, - pbar=None, + data=ROOT / '../datasets/mnist', # dataset dir + weights=None, # model.pt path(s) + batch_size=32, # batch size + imgsz=224, # inference size (pixels) + device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu + workers=8, # max dataloader workers (per RANK in DDP mode) + verbose=False, # verbose output + project=ROOT / 'runs/val-cls', # save to project/name + name='exp', # save to project/name + exist_ok=False, # existing project/name ok, do not increment + half=True, # use FP16 half-precision inference + dnn=False, # use OpenCV DNN for ONNX inference + model=None, + dataloader=None, + names=None, + criterion=None, + pbar=None, ): # Initialize/load model and set device training = model is not None From 1d961a41279087412627643e45ef36b6e793100d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 13:40:23 +0200 Subject: [PATCH 259/349] reset defaults --- classify/predict.py | 2 +- classify/train.py | 4 ++-- classify/val.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index 918f5baa1e0b..977935a89d83 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -88,7 +88,7 @@ def run( def parse_opt(): parser = argparse.ArgumentParser() - parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5n-cls.pt', help='model path(s)') + parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s-cls.pt', help='model path(s)') parser.add_argument('--source', type=str, default=ROOT / 'data/images/bus.jpg', help='file') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') diff --git a/classify/train.py b/classify/train.py index deeb14b16743..fcd459c4f672 100644 --- a/classify/train.py +++ b/classify/train.py @@ -263,10 +263,10 @@ def train(opt, device): def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist or fashion-mnist') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist, imagenet, etc.') parser.add_argument('--epochs', type=int, default=90) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') diff --git a/classify/val.py b/classify/val.py index 5cb5e4eab4a8..151ec0f00569 100644 --- a/classify/val.py +++ b/classify/val.py @@ -30,7 +30,7 @@ def run( data=ROOT / '../datasets/mnist', # dataset dir weights=None, # model.pt path(s) - batch_size=32, # batch size + batch_size=128, # batch size imgsz=224, # inference size (pixels) device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu workers=8, # max dataloader workers (per RANK in DDP mode) @@ -120,7 +120,7 @@ def parse_opt(): parser = argparse.ArgumentParser() parser.add_argument('--data', type=str, default=ROOT / '../datasets/mnist', help='dataset path') parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s-cls.pt', help='model.pt path(s)') - parser.add_argument('--batch-size', type=int, default=32, help='batch size') + parser.add_argument('--batch-size', type=int, default=128, help='batch size') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='inference size (pixels)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') From 52b73c73687c12e7a5c774865c6f95cd43d33db5 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 13:41:53 +0200 Subject: [PATCH 260/349] reset defaults --- classify/val.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/val.py b/classify/val.py index ef2c60184e17..2e7abd1e1b71 100644 --- a/classify/val.py +++ b/classify/val.py @@ -120,7 +120,7 @@ def parse_opt(): parser = argparse.ArgumentParser() parser.add_argument('--data', type=str, default=ROOT / '../datasets/mnist', help='dataset path') parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s-cls.pt', help='model.pt path(s)') - parser.add_argument('--batch-size', type=int, default=32, help='batch size') + parser.add_argument('--batch-size', type=int, default=128, help='batch size') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='inference size (pixels)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') From d97848eedbeb147e5843b666588eece7ecce92f0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 13:46:58 +0200 Subject: [PATCH 261/349] fix gpu predict --- classify/predict.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classify/predict.py b/classify/predict.py index 977935a89d83..ada868df7aea 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -60,7 +60,8 @@ def run( # Image t1 = time_sync() - im = transforms(Image.open(file)).unsqueeze(0) + im = transforms(Image.open(file)).unsqueeze(0).to(device) + im = im.half() if model.fp16 else im.float() t2 = time_sync() dt[0] += t2 - t1 From 17564e14108b144ca32fe448c98832a021668571 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 13:48:48 +0200 Subject: [PATCH 262/349] warmup --- classify/predict.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classify/predict.py b/classify/predict.py index ada868df7aea..dc0596072c0b 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -54,6 +54,7 @@ def run( # Load model device = select_device(device) model = DetectMultiBackend(weights, device=device, dnn=dnn, data=None, fp16=half) + model.warmup(imgsz=(1, 3, imgsz, imgsz)) # warmup if len(model.names) == 1000: # ImageNet with open(ROOT / 'data/ImageNet.yaml', errors='ignore') as f: model.names = yaml.safe_load(f)['names'] # human-readable names From f1f71c9b8735a256f7f926329cc4659972c11bd2 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 14:16:29 +0200 Subject: [PATCH 263/349] ema half fix --- classify/train.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index fcd459c4f672..da69b1a606cf 100644 --- a/classify/train.py +++ b/classify/train.py @@ -251,10 +251,12 @@ def train(opt, device): LOGGER.info(f'\nTraining complete {(time.time() - t0) / 3600:.3f} hours.' f"\nResults saved to {colorstr('bold', save_dir)}") - # Show predictions + # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels - pred = torch.max(ema.ema(images.to(device)), 1)[1] + pred = torch.max(ema.ema((images.half() if cuda else images.float()).to(device)), 1)[1] file = imshow_cls(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + + # Log results meta = {"epochs": epochs, "top1_acc": best_fitness, "date": datetime.now().isoformat()} logger.log_images(file, name='Test Examples (true-predicted)', epoch=epoch) logger.log_model(best, epochs, metadata=meta) From 26d3a1e165032993060ba7cd07cd86363a875412 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 14:20:14 +0200 Subject: [PATCH 264/349] spacing --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index da69b1a606cf..eaed19209293 100644 --- a/classify/train.py +++ b/classify/train.py @@ -249,7 +249,7 @@ def train(opt, device): # Train complete if RANK in {-1, 0} and final_epoch: LOGGER.info(f'\nTraining complete {(time.time() - t0) / 3600:.3f} hours.' - f"\nResults saved to {colorstr('bold', save_dir)}") + f"\nResults saved to {colorstr('bold', save_dir)}\n") # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels From 0e9ba3c0011f8dd478d916a19ab70c514ab1e77b Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 14:21:37 +0200 Subject: [PATCH 265/349] remove data --- classify/predict.py | 2 +- classify/val.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index dc0596072c0b..fd3063e66594 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -53,7 +53,7 @@ def run( # Load model device = select_device(device) - model = DetectMultiBackend(weights, device=device, dnn=dnn, data=None, fp16=half) + model = DetectMultiBackend(weights, device=device, dnn=dnn, fp16=half) model.warmup(imgsz=(1, 3, imgsz, imgsz)) # warmup if len(model.names) == 1000: # ImageNet with open(ROOT / 'data/ImageNet.yaml', errors='ignore') as f: diff --git a/classify/val.py b/classify/val.py index 2e7abd1e1b71..fddded4926e0 100644 --- a/classify/val.py +++ b/classify/val.py @@ -60,7 +60,7 @@ def run( save_dir.mkdir(parents=True, exist_ok=True) # make dir # Load model - model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) + model = DetectMultiBackend(weights, device=device, dnn=dnn, fp16=half) stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine imgsz = check_img_size(imgsz, s=stride) # check image size half = model.fp16 # FP16 supported on limited backends with CUDA From 4a982ae8d3f6c44566b6d5dc7b25591264d1943d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 14:22:53 +0200 Subject: [PATCH 266/349] remove cache --- classify/val.py | 1 - 1 file changed, 1 deletion(-) diff --git a/classify/val.py b/classify/val.py index fddded4926e0..d1f33cb8d030 100644 --- a/classify/val.py +++ b/classify/val.py @@ -78,7 +78,6 @@ def run( imgsz=imgsz, batch_size=batch_size, augment=False, - cache=opt.cache, rank=-1, workers=workers) From dada2867d96333ffb4a4589b248380631cc3abfb Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 14:39:14 +0200 Subject: [PATCH 267/349] remove denormalize --- classify/train.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/classify/train.py b/classify/train.py index eaed19209293..afa8c1c6ed6e 100644 --- a/classify/train.py +++ b/classify/train.py @@ -32,7 +32,6 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from classify import val as validate -from utils.augmentations import denormalize from utils.dataloaders import create_classification_dataloader from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args) @@ -135,7 +134,7 @@ def train(opt, device): if opt.verbose: LOGGER.info(model) images, labels = next(iter(trainloader)) - file = imshow_cls(denormalize(images[:25]), labels[:25], names=names, f=save_dir / 'train_images.jpg') + file = imshow_cls(images[:25], labels[:25], names=names, f=save_dir / 'train_images.jpg', verbose=True) logger.log_images(file, name='Train Examples') logger.log_graph(model, imgsz) # log model @@ -254,7 +253,7 @@ def train(opt, device): # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels pred = torch.max(ema.ema((images.half() if cuda else images.float()).to(device)), 1)[1] - file = imshow_cls(denormalize(images), labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + file = imshow_cls(images, labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') # Log results meta = {"epochs": epochs, "top1_acc": best_fitness, "date": datetime.now().isoformat()} From 03e7a975c3847e298e03f42e87e4402d4708993d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 15:03:05 +0200 Subject: [PATCH 268/349] save run settings --- classify/train.py | 14 +++++++++----- classify/val.py | 9 ++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/classify/train.py b/classify/train.py index afa8c1c6ed6e..78d67283be9a 100644 --- a/classify/train.py +++ b/classify/train.py @@ -16,6 +16,7 @@ from datetime import datetime from pathlib import Path +import yaml import torch import torch.distributed as dist import torch.hub as hub @@ -57,6 +58,10 @@ def train(opt, device): wdir.mkdir(parents=True, exist_ok=True) # make dir last, best = wdir / 'last.pt', wdir / 'best.pt' + # Save run settings + with open(save_dir / 'opt.yaml', 'w') as f: + yaml.safe_dump(vars(opt), f, sort_keys=False) + # Logger logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None @@ -204,7 +209,6 @@ def train(opt, device): if i == len(pbar) - 1: # last batch top1, top5, vloss = validate.run(model=ema.ema, dataloader=testloader, - names=names, criterion=criterion, pbar=pbar) # test accuracy, loss fitness = top1 # define fitness as top1 accuracy @@ -263,11 +267,11 @@ def train(opt, device): def parse_opt(known=False): parser = argparse.ArgumentParser() - parser.add_argument('--model', type=str, default='yolov5s', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist, imagenet, etc.') - parser.add_argument('--epochs', type=int, default=90) + parser.add_argument('--model', type=str, default='yolov5n', help='initial weights path') + parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist, imagenet, etc.') + parser.add_argument('--epochs', type=int, default=1) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=64, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') diff --git a/classify/val.py b/classify/val.py index d1f33cb8d030..81f5c3d4ede0 100644 --- a/classify/val.py +++ b/classify/val.py @@ -42,7 +42,6 @@ def run( dnn=False, # use OpenCV DNN for ONNX inference model=None, dataloader=None, - names=None, criterion=None, pbar=None, ): @@ -85,8 +84,8 @@ def run( pred, targets, loss = [], [], 0 n = len(dataloader) # number of batches action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' - desc = f"{pbar.desc[:-36]}{action:>36}" - bar = tqdm(dataloader, desc, n, False, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) + desc = f"{pbar.desc[:-36]}{action:>36}" if pbar else f"{action}" + bar = tqdm(dataloader, desc, n, not training, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with torch.cuda.amp.autocast(enabled=device.type != 'cpu'): for images, labels in bar: images, labels = images.to(device, non_blocking=True), labels.to(device) @@ -107,7 +106,7 @@ def run( if verbose: # all classes LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") - for i, c in enumerate(names): + for i, c in enumerate(model.names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") @@ -123,7 +122,7 @@ def parse_opt(): parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='inference size (pixels)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)') - parser.add_argument('--verbose', action='store_true', help='report mAP by class') + parser.add_argument('--verbose', nargs='?', const=True, default=True, help='verbose output') parser.add_argument('--project', default=ROOT / 'runs/val-cls', help='save to project/name') parser.add_argument('--name', default='exp', help='save to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') From ef20d6589436bf3e7a7a5e3f51974e25f89e4529 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 13:03:29 +0000 Subject: [PATCH 269/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index 78d67283be9a..106889b69193 100644 --- a/classify/train.py +++ b/classify/train.py @@ -16,13 +16,13 @@ from datetime import datetime from pathlib import Path -import yaml import torch import torch.distributed as dist import torch.hub as hub import torch.nn as nn import torch.optim.lr_scheduler as lr_scheduler import torchvision +import yaml from torch.cuda import amp from tqdm import tqdm From bc969a7ada013e30b507812b74b597cb6d89eabe Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 15:04:23 +0200 Subject: [PATCH 270/349] verbose false on initial plots --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index 78d67283be9a..7ce531628bc5 100644 --- a/classify/train.py +++ b/classify/train.py @@ -139,7 +139,7 @@ def train(opt, device): if opt.verbose: LOGGER.info(model) images, labels = next(iter(trainloader)) - file = imshow_cls(images[:25], labels[:25], names=names, f=save_dir / 'train_images.jpg', verbose=True) + file = imshow_cls(images[:25], labels[:25], names=names, f=save_dir / 'train_images.jpg') logger.log_images(file, name='Train Examples') logger.log_graph(model, imgsz) # log model From fbff2128a72f4bc44701b3403ec06cb8a231ba74 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 15:45:10 +0200 Subject: [PATCH 271/349] new save_yaml() function --- classify/train.py | 8 +++----- train.py | 8 +++----- utils/general.py | 6 ++++++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/classify/train.py b/classify/train.py index 537b29b26385..d44a5fc86ef5 100644 --- a/classify/train.py +++ b/classify/train.py @@ -22,7 +22,6 @@ import torch.nn as nn import torch.optim.lr_scheduler as lr_scheduler import torchvision -import yaml from torch.cuda import amp from tqdm import tqdm @@ -35,7 +34,7 @@ from classify import val as validate from utils.dataloaders import create_classification_dataloader from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, - download, increment_path, init_seeds, print_args) + download, increment_path, init_seeds, print_args, yaml_save) from utils.loggers import GenericLogger from utils.plots import imshow_cls from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, @@ -49,7 +48,7 @@ def train(opt, device): init_seeds(opt.seed + 1 + RANK, deterministic=True) save_dir, data, bs, epochs, nw, imgsz, pretrained = \ - Path(opt.save_dir), Path(opt.data), opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), \ + opt.save_dir, Path(opt.data), opt.batch_size, opt.epochs, min(os.cpu_count() - 1, opt.workers), \ opt.imgsz, str(opt.pretrained).lower() == 'true' cuda = device.type != 'cpu' @@ -59,8 +58,7 @@ def train(opt, device): last, best = wdir / 'last.pt', wdir / 'best.pt' # Save run settings - with open(save_dir / 'opt.yaml', 'w') as f: - yaml.safe_dump(vars(opt), f, sort_keys=False) + yaml_save(save_dir / 'opt.yaml', vars(opt)) # Logger logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None diff --git a/train.py b/train.py index 4eafcc4c58bf..dedb5052b228 100644 --- a/train.py +++ b/train.py @@ -47,7 +47,7 @@ from utils.general import (LOGGER, check_amp, check_dataset, check_file, check_git_status, check_img_size, check_requirements, check_suffix, check_yaml, colorstr, get_latest_run, increment_path, init_seeds, intersect_dicts, labels_to_class_weights, labels_to_image_weights, methods, - one_cycle, print_args, print_mutation, strip_optimizer) + one_cycle, print_args, print_mutation, strip_optimizer, yaml_save) from utils.loggers import Loggers from utils.loggers.wandb.wandb_utils import check_wandb_resume from utils.loss import ComputeLoss @@ -81,10 +81,8 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio # Save run settings if not evolve: - with open(save_dir / 'hyp.yaml', 'w') as f: - yaml.safe_dump(hyp, f, sort_keys=False) - with open(save_dir / 'opt.yaml', 'w') as f: - yaml.safe_dump(vars(opt), f, sort_keys=False) + yaml_save(save_dir / 'hyp.yaml', hyp) + yaml_save(save_dir / 'opt.yaml', vars(opt)) # Loggers data_dict = None diff --git a/utils/general.py b/utils/general.py index 502459d7b789..4926f7095ff9 100755 --- a/utils/general.py +++ b/utils/general.py @@ -563,6 +563,12 @@ def amp_allclose(model, im): return False +def yaml_save(file='data.yaml', data={}): + # Save a yaml file safely + with open(file, 'w') as f: + yaml.safe_dump({k: str(v) if isinstance(v, Path) else v for k, v in data.items()}, f, sort_keys=False) + + def url2file(url): # Convert URL to filename, i.e. https://url.com/file.txt?auth -> file.txt url = str(Path(url)).replace(':/', '://') # Pathlib turns :// -> :/ From 59e1300242fdcbf725a0c4d8721d5671d138c7b5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 14 Aug 2022 16:11:31 +0200 Subject: [PATCH 272/349] Update ci-testing.yml --- .github/workflows/ci-testing.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 31d38ead530f..c0134071a553 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -106,6 +106,7 @@ jobs: # export PYTHONPATH="$PWD" # to run '$ python *.py' files in subdirectories m=${{ matrix.model }} # official weights b=runs/train/exp/weights/best # best.pt checkpoint + bc=runs/train-cls/exp/weights/best # best.pt classification checkpoint python train.py --imgsz 64 --batch 32 --weights $m.pt --cfg $m.yaml --epochs 1 --device cpu # train for d in cpu; do # devices for w in $m $b; do # weights @@ -117,9 +118,14 @@ jobs: # python models/tf.py --weights $m.pt # build TF model python models/yolo.py --cfg $m.yaml # build PyTorch model python export.py --weights $m.pt --img 64 --include torchscript # export + python classify/train.py --imgsz 32 --model $m --data mnist2560 --epochs 1 # train cls + python classify/val.py --imgsz 32 --weights $bc.pt --data mnist2560 # val cls + python classify/predict.py --imgsz 32 --weights $bc.pt --source ../datasets/mnist2560/test/7/60.png + python classify/predict.py --imgsz 32 --weights $m-cls.pt --source data/images/bus.jpg # predict cls python - < Date: Sun, 14 Aug 2022 16:41:16 +0200 Subject: [PATCH 273/349] Path(data) CI fix --- classify/val.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classify/val.py b/classify/val.py index 81f5c3d4ede0..0d68bdbad4f5 100644 --- a/classify/val.py +++ b/classify/val.py @@ -72,6 +72,7 @@ def run( LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models') # Dataloader + data = Path(data) test_dir = data / 'test' if (data / 'test').exists() else data / 'val' # data/test or data/val dataloader = create_classification_dataloader(path=test_dir, imgsz=imgsz, From e8ed7cd4c7204002ae9207ada0c52c4a584c4d89 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 16:44:20 +0200 Subject: [PATCH 274/349] Separate classification CI --- .github/workflows/ci-testing.yml | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index c0134071a553..e5b628d08db6 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -5,9 +5,9 @@ name: YOLOv5 CI on: push: - branches: [master] + branches: [ master ] pull_request: - branches: [master] + branches: [ master ] schedule: - cron: '0 0 * * *' # runs at 00:00 UTC every day @@ -16,9 +16,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] - python-version: ['3.9'] # requires python<=3.9 - model: [yolov5n] + os: [ ubuntu-latest ] + python-version: [ '3.9' ] # requires python<=3.9 + model: [ yolov5n ] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -47,9 +47,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.10'] - model: [yolov5n] + os: [ ubuntu-latest, macos-latest, windows-latest ] + python-version: [ '3.10' ] + model: [ yolov5n ] include: - os: ubuntu-latest python-version: '3.7' # '3.6.8' min @@ -128,4 +128,13 @@ jobs: model = torch.hub.load('.', 'custom', path=path, source='local') print(model('data/images/bus.jpg')) EOF - + - name: Run classification tests + shell: bash + run: | + m=${{ matrix.model }} # official weights + b=runs/train-cls/exp/weights/best # best.pt classification checkpoint + python classify/train.py --imgsz 32 --model $m --data mnist2560 --epochs 1 # train cls + python classify/val.py --imgsz 32 --weights $b.pt --data mnist2560 # val cls + python classify/predict.py --imgsz 32 --weights $b.pt --source ../datasets/mnist2560/test/7/60.png + python classify/predict.py --imgsz 32 --weights $m-cls.pt --source data/images/bus.jpg # predict cls + python export.py --weights $b.pt --img 64 --imgsz 224 --include torchscript # export From 379a93673a81d88b92f496baffbcc68eefe69052 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 17:09:20 +0200 Subject: [PATCH 275/349] fix val --- .github/workflows/ci-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index e5b628d08db6..7ffbbfd70314 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -134,7 +134,7 @@ jobs: m=${{ matrix.model }} # official weights b=runs/train-cls/exp/weights/best # best.pt classification checkpoint python classify/train.py --imgsz 32 --model $m --data mnist2560 --epochs 1 # train cls - python classify/val.py --imgsz 32 --weights $b.pt --data mnist2560 # val cls + python classify/val.py --imgsz 32 --weights $b.pt --data ../datasets/mnist2560 # val cls python classify/predict.py --imgsz 32 --weights $b.pt --source ../datasets/mnist2560/test/7/60.png python classify/predict.py --imgsz 32 --weights $m-cls.pt --source data/images/bus.jpg # predict cls python export.py --weights $b.pt --img 64 --imgsz 224 --include torchscript # export From f81686d459970d72c60ea1cf92b701baf49d7a69 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 17:11:04 +0200 Subject: [PATCH 276/349] fix val --- .github/workflows/ci-testing.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 7ffbbfd70314..209881b56b28 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -106,7 +106,6 @@ jobs: # export PYTHONPATH="$PWD" # to run '$ python *.py' files in subdirectories m=${{ matrix.model }} # official weights b=runs/train/exp/weights/best # best.pt checkpoint - bc=runs/train-cls/exp/weights/best # best.pt classification checkpoint python train.py --imgsz 64 --batch 32 --weights $m.pt --cfg $m.yaml --epochs 1 --device cpu # train for d in cpu; do # devices for w in $m $b; do # weights @@ -118,10 +117,6 @@ jobs: # python models/tf.py --weights $m.pt # build TF model python models/yolo.py --cfg $m.yaml # build PyTorch model python export.py --weights $m.pt --img 64 --include torchscript # export - python classify/train.py --imgsz 32 --model $m --data mnist2560 --epochs 1 # train cls - python classify/val.py --imgsz 32 --weights $bc.pt --data mnist2560 # val cls - python classify/predict.py --imgsz 32 --weights $bc.pt --source ../datasets/mnist2560/test/7/60.png - python classify/predict.py --imgsz 32 --weights $m-cls.pt --source data/images/bus.jpg # predict cls python - < Date: Sun, 14 Aug 2022 17:21:21 +0200 Subject: [PATCH 277/349] fix val --- classify/predict.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index fd3063e66594..393a7210a8a9 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -11,9 +11,9 @@ import sys from pathlib import Path +import cv2 import torch.nn.functional as F import yaml -from PIL import Image FILE = Path(__file__).resolve() ROOT = FILE.parents[1] # YOLOv5 root directory @@ -41,18 +41,18 @@ def run( name='exp', # save to project/name exist_ok=False, # existing project/name ok, do not increment ): + file = str(source) seen, dt = 1, [0.0, 0.0, 0.0] + device = select_device(device) # Directories save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run save_dir.mkdir(parents=True, exist_ok=True) # make dir - # YOLOv5 classification model inference - file = str(source) + # Transforms transforms = classify_transforms(imgsz) # Load model - device = select_device(device) model = DetectMultiBackend(weights, device=device, dnn=dnn, fp16=half) model.warmup(imgsz=(1, 3, imgsz, imgsz)) # warmup if len(model.names) == 1000: # ImageNet @@ -61,7 +61,8 @@ def run( # Image t1 = time_sync() - im = transforms(Image.open(file)).unsqueeze(0).to(device) + im = cv2.cvtColor(cv2.imread(file), cv2.COLOR_BGR2RGB) + im = transforms(im).unsqueeze(0).to(device) im = im.half() if model.fp16 else im.float() t2 = time_sync() dt[0] += t2 - t1 From e37821053a93953a71875e7ed46c0ca92858663c Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 17:30:22 +0200 Subject: [PATCH 278/349] smartCrossEntropyLoss --- classify/train.py | 4 ++-- utils/torch_utils.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index d44a5fc86ef5..6030b1f3a6d1 100644 --- a/classify/train.py +++ b/classify/train.py @@ -38,7 +38,7 @@ from utils.loggers import GenericLogger from utils.plots import imshow_cls from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, - torch_distributed_zero_first, update_classifier_model) + smartCrossEntropyLoss, torch_distributed_zero_first, update_classifier_model) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -161,7 +161,7 @@ def train(opt, device): # Train t0 = time.time() - criterion = nn.CrossEntropyLoss(label_smoothing=opt.label_smoothing) # loss function + criterion = smartCrossEntropyLoss(label_smoothing=opt.label_smoothing) # loss function best_fitness = 0.0 scaler = amp.GradScaler(enabled=cuda) val = test_dir.stem # 'val' or 'test' diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 1303aed4666f..967d1b54b9eb 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -42,6 +42,16 @@ def decorate(fn): return decorate +def smartCrossEntropyLoss(label_smoothing=0.0): + # Returns nn.CrossEntropyLoss with label smoothing enabled for torch>=1.10.0 + if check_version(torch.__version__, '1.10.0'): + return nn.CrossEntropyLoss(label_smoothing=label_smoothing) # loss function + else: + if label_smoothing > 0: + LOGGER.warning(f'WARNING: label smoothing {label_smoothing} requires torch>=1.10.0') + return nn.CrossEntropyLoss() # loss function + + def smart_DDP(model): # Model DDP creation with checks assert not check_version(torch.__version__, '1.12.0', pinned=True), \ From 56c5b8e51a433ab33815a392730e2fbbc0fb5399 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 17:46:47 +0200 Subject: [PATCH 279/349] skip validation on hub load --- utils/torch_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 967d1b54b9eb..a6e6f034f0c6 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -340,6 +340,8 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): def smart_hub_load(repo='ultralytics/yolov5', model='yolov5s', **kwargs): # YOLOv5 torch.hub.load() wrapper with smart error/issue handling + if check_version(torch.__version__, '1.9.1'): + kwargs['skip_validation'] = True # validation causes GitHub API rate limit errors if check_version(torch.__version__, '1.12.0'): kwargs['trust_repo'] = True # argument required starting in torch 0.12 try: From 0e5720bda8664e75512071e39d150935c59d2960 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 18:35:59 +0200 Subject: [PATCH 280/349] autodownload with working dir root --- classify/predict.py | 2 +- classify/train.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index 393a7210a8a9..cb4e5466f592 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -79,7 +79,7 @@ def run( # Plot if show: - imshow_cls(denormalize(im), f=save_dir / Path(file).name) + imshow_cls(denormalize(im), f=save_dir / Path(file).name, verbose=True) # Print results t = tuple(x / seen * 1E3 for x in dt) # speeds per image diff --git a/classify/train.py b/classify/train.py index 6030b1f3a6d1..8ed5db9fb35e 100644 --- a/classify/train.py +++ b/classify/train.py @@ -64,7 +64,7 @@ def train(opt, device): logger = GenericLogger(opt=opt, console_logger=LOGGER) if RANK in {-1, 0} else None # Download Dataset - with torch_distributed_zero_first(LOCAL_RANK): + with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): data_dir = data if data.is_dir() else (DATASETS_DIR / data) if not data_dir.is_dir(): LOGGER.info(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...') From 22b9d4ba27c872d73dfd191d9b40ad7bbc80f440 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 18:41:52 +0200 Subject: [PATCH 281/349] str(data) --- classify/train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classify/train.py b/classify/train.py index 8ed5db9fb35e..916e5514fe44 100644 --- a/classify/train.py +++ b/classify/train.py @@ -69,7 +69,7 @@ def train(opt, device): if not data_dir.is_dir(): LOGGER.info(f'\nDataset not found ⚠️, missing path {data_dir}, attempting download...') t = time.time() - if data == 'imagenet': + if str(data) == 'imagenet': subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True) else: url = f'https://github.com/ultralytics/yolov5/releases/download/v1.0/{data}.zip' @@ -266,10 +266,10 @@ def train(opt, device): def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5n', help='initial weights path') - parser.add_argument('--data', type=str, default='mnist2560', help='cifar10, cifar100, mnist, imagenet, etc.') + parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist, imagenet, etc.') parser.add_argument('--epochs', type=int, default=1) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=64, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') From 986390d959c5214a6cec31b9387e9096fccba3aa Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 18:46:58 +0200 Subject: [PATCH 282/349] Dataset usage example --- classify/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classify/train.py b/classify/train.py index 916e5514fe44..9b4c9e33acc6 100644 --- a/classify/train.py +++ b/classify/train.py @@ -1,6 +1,7 @@ # YOLOv5 🚀 by Ultralytics, GPL-3.0 license """ Train a YOLOv5 classifier model on a classification dataset +Datasets: --data mnist, fashion-mnist, cifar10, cifar100, imagenette, imagewoof, imagenet, or 'path/to/custom/dataset' Usage: $ python classify/train.py --model yolov5s --data cifar100 --epochs 5 --img 224 --batch 128 From b456f515d68ddbc4b160c56cd7a6aec0c935f49a Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 18:52:02 +0200 Subject: [PATCH 283/349] im_show normalize --- utils/plots.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/plots.py b/utils/plots.py index f1bd15b10b4f..ce6088100419 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -18,9 +18,11 @@ import torch from PIL import Image, ImageDraw, ImageFont +from utils.augmentations import normalize from utils.general import (CONFIG_DIR, FONT, LOGGER, Timeout, check_font, check_requirements, clip_coords, increment_path, is_ascii, threaded, try_except, xywh2xyxy, xyxy2xywh) from utils.metrics import fitness +from utils.torch_utils import smart_inference_mode # Settings RANK = int(os.getenv('RANK', -1)) @@ -388,11 +390,13 @@ def plot_labels(labels, names=(), save_dir=Path('')): plt.close() +@smart_inference_mode() def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) import matplotlib.pyplot as plt names = names or [f'class{i}' for i in range(1000)] + im = normalize(im) blocks = torch.chunk(im.cpu(), len(im), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default From b7f3016ba45b6b76b40e2b27e561d730fd1673d7 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 19:18:51 +0200 Subject: [PATCH 284/349] im_show normalize --- utils/augmentations.py | 4 ++-- utils/plots.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/utils/augmentations.py b/utils/augmentations.py index 7a715cb787d3..a55fefa68a76 100644 --- a/utils/augmentations.py +++ b/utils/augmentations.py @@ -50,9 +50,9 @@ def __call__(self, im, labels, p=1.0): return im, labels -def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): +def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False): # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = (x - mean) / std - return TF.normalize(x, mean, std, inplace=True) + return TF.normalize(x, mean, std, inplace=inplace) def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): diff --git a/utils/plots.py b/utils/plots.py index ce6088100419..b974c6c8693b 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -18,11 +18,9 @@ import torch from PIL import Image, ImageDraw, ImageFont -from utils.augmentations import normalize from utils.general import (CONFIG_DIR, FONT, LOGGER, Timeout, check_font, check_requirements, clip_coords, increment_path, is_ascii, threaded, try_except, xywh2xyxy, xyxy2xywh) from utils.metrics import fitness -from utils.torch_utils import smart_inference_mode # Settings RANK = int(os.getenv('RANK', -1)) @@ -390,14 +388,12 @@ def plot_labels(labels, names=(), save_dir=Path('')): plt.close() -@smart_inference_mode() def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): # Show classification image grid with labels (optional) and predictions (optional) - import matplotlib.pyplot as plt + from utils.augmentations import denormalize names = names or [f'class{i}' for i in range(1000)] - im = normalize(im) - blocks = torch.chunk(im.cpu(), len(im), dim=0) # select batch index 0, block by channels + blocks = torch.chunk(denormalize(im).cpu(), len(im), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols From e663ff428ad3ddc05ef980eb2f67256bba7df996 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 20:01:44 +0200 Subject: [PATCH 285/349] add imagenet simple names to multibackend --- classify/predict.py | 4 ---- models/common.py | 24 +++++++++++++----------- utils/general.py | 8 +++++++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index cb4e5466f592..3b588ad88c6e 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -13,7 +13,6 @@ import cv2 import torch.nn.functional as F -import yaml FILE = Path(__file__).resolve() ROOT = FILE.parents[1] # YOLOv5 root directory @@ -55,9 +54,6 @@ def run( # Load model model = DetectMultiBackend(weights, device=device, dnn=dnn, fp16=half) model.warmup(imgsz=(1, 3, imgsz, imgsz)) # warmup - if len(model.names) == 1000: # ImageNet - with open(ROOT / 'data/ImageNet.yaml', errors='ignore') as f: - model.names = yaml.safe_load(f)['names'] # human-readable names # Image t1 = time_sync() diff --git a/models/common.py b/models/common.py index 57e801c79011..62c77ea2cea4 100644 --- a/models/common.py +++ b/models/common.py @@ -17,13 +17,12 @@ import requests import torch import torch.nn as nn -import yaml from PIL import Image from torch.cuda import amp from utils.dataloaders import exif_transpose, letterbox -from utils.general import (LOGGER, check_requirements, check_suffix, check_version, colorstr, increment_path, - make_divisible, non_max_suppression, scale_coords, xywh2xyxy, xyxy2xywh) +from utils.general import (LOGGER, ROOT, check_requirements, check_suffix, check_version, colorstr, increment_path, + make_divisible, non_max_suppression, scale_coords, xywh2xyxy, xyxy2xywh, yaml_load) from utils.plots import Annotator, colors, save_one_box from utils.torch_utils import copy_attr, smart_inference_mode, time_sync @@ -322,14 +321,11 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, super().__init__() w = str(weights[0] if isinstance(weights, list) else weights) - pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs = self.model_type(w) # get backend + pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs = self._model_type(w) # get backend w = attempt_download(w) # download if not local cuda = torch.cuda.is_available() and device.type != 'cpu' fp16 &= (pt or jit or onnx or engine) and cuda # FP16 - stride, names = 32, [f'class{i}' for i in range(1000)] # assign defaults - if data: # assign class names (optional) - with open(data, errors='ignore') as f: - names = yaml.safe_load(f)['names'] + stride = 32 # default stride if pt: # PyTorch model = attempt_load(weights if isinstance(weights, list) else w, device=device, inplace=True, fuse=fuse) @@ -448,6 +444,13 @@ def wrap_frozen_graph(gd, inputs, outputs): raise Exception('ERROR: YOLOv5 TF.js inference is not supported') else: raise Exception(f'ERROR: {w} is not a supported format') + + # class names + if 'names' not in locals(): + names = yaml_load(data)['names'] if data else (f'class{i}' for i in range(999)) + if len(names) == 1000 and names[0] == 'n01440764': # ImageNet + names = yaml_load(ROOT / 'data/ImageNet.yaml')['names'] # human-readable names + self.__dict__.update(locals()) # assign all variables to self def forward(self, im, augment=False, visualize=False, val=False): @@ -528,7 +531,7 @@ def warmup(self, imgsz=(1, 3, 640, 640)): self.forward(im) # warmup @staticmethod - def model_type(p='path/to/model.pt'): + def _model_type(p='path/to/model.pt'): # Return model type from model path, i.e. path='path/to/model.onnx' -> type=onnx from export import export_formats suffixes = list(export_formats().Suffix) + ['.xml'] # export suffixes @@ -542,8 +545,7 @@ def model_type(p='path/to/model.pt'): @staticmethod def _load_metadata(f='path/to/meta.yaml'): # Load metadata from meta.yaml if it exists - with open(f, errors='ignore') as f: - d = yaml.safe_load(f) + d = yaml_load(f) return d['stride'], d['names'] # assign stride, names diff --git a/utils/general.py b/utils/general.py index 4926f7095ff9..095fe7febbed 100755 --- a/utils/general.py +++ b/utils/general.py @@ -563,8 +563,14 @@ def amp_allclose(model, im): return False +def yaml_load(file='data.yaml'): + # Single-line safe yaml loading + with open(file, errors='ignore') as f: + return yaml.safe_load(f) + + def yaml_save(file='data.yaml', data={}): - # Save a yaml file safely + # Single-line safe yaml saving with open(file, 'w') as f: yaml.safe_dump({k: str(v) if isinstance(v, Path) else v for k, v in data.items()}, f, sort_keys=False) From cb13e7f534f221b6ef27510e53bc196054d9d2f0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 20:08:57 +0200 Subject: [PATCH 286/349] Add validation speeds --- classify/val.py | 52 +++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/classify/val.py b/classify/val.py index 0d68bdbad4f5..bf9857be6f4b 100644 --- a/classify/val.py +++ b/classify/val.py @@ -22,28 +22,28 @@ from models.common import DetectMultiBackend from utils.dataloaders import create_classification_dataloader -from utils.general import LOGGER, check_img_size, check_requirements, increment_path, print_args -from utils.torch_utils import select_device, smart_inference_mode +from utils.general import LOGGER, check_img_size, check_requirements, colorstr, increment_path, print_args +from utils.torch_utils import select_device, smart_inference_mode, time_sync @smart_inference_mode() def run( - data=ROOT / '../datasets/mnist', # dataset dir - weights=None, # model.pt path(s) - batch_size=128, # batch size - imgsz=224, # inference size (pixels) - device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu - workers=8, # max dataloader workers (per RANK in DDP mode) - verbose=False, # verbose output - project=ROOT / 'runs/val-cls', # save to project/name - name='exp', # save to project/name - exist_ok=False, # existing project/name ok, do not increment - half=True, # use FP16 half-precision inference - dnn=False, # use OpenCV DNN for ONNX inference - model=None, - dataloader=None, - criterion=None, - pbar=None, + data=ROOT / '../datasets/mnist', # dataset dir + weights=None, # model.pt path(s) + batch_size=128, # batch size + imgsz=224, # inference size (pixels) + device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu + workers=8, # max dataloader workers (per RANK in DDP mode) + verbose=False, # verbose output + project=ROOT / 'runs/val-cls', # save to project/name + name='exp', # save to project/name + exist_ok=False, # existing project/name ok, do not increment + half=True, # use FP16 half-precision inference + dnn=False, # use OpenCV DNN for ONNX inference + model=None, + dataloader=None, + criterion=None, + pbar=None, ): # Initialize/load model and set device training = model is not None @@ -82,19 +82,27 @@ def run( workers=workers) model.eval() - pred, targets, loss = [], [], 0 + pred, targets, loss, dt = [], [], 0, [0.0, 0.0, 0.0] n = len(dataloader) # number of batches action = 'validating' if dataloader.dataset.root.stem == 'val' else 'testing' desc = f"{pbar.desc[:-36]}{action:>36}" if pbar else f"{action}" bar = tqdm(dataloader, desc, n, not training, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}', position=0) with torch.cuda.amp.autocast(enabled=device.type != 'cpu'): for images, labels in bar: + t1 = time_sync() images, labels = images.to(device, non_blocking=True), labels.to(device) + t2 = time_sync() + dt[0] += t2 - t1 + y = model(images) + t3 = time_sync() + dt[1] += t3 - t2 + pred.append(y.argsort(1, descending=True)[:, :5]) targets.append(labels) if criterion: loss += criterion(y, labels) + dt[2] += time_sync() - t3 loss /= n pred, targets = torch.cat(pred), torch.cat(targets) @@ -112,6 +120,12 @@ def run( top1i, top5i = aci.mean(0).tolist() LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") + # Print results + t = tuple(x / len(dataloader.dataset.samples) * 1E3 for x in dt) # speeds per image + shape = (1, 3, imgsz, imgsz) + LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms post-process per image at shape {shape}' % t) + LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}") + return top1, top5, loss From 92fd0720b69f41350d6682817ffb15b69f7e6e73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Aug 2022 18:09:20 +0000 Subject: [PATCH 287/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classify/val.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/classify/val.py b/classify/val.py index bf9857be6f4b..a32762aabb42 100644 --- a/classify/val.py +++ b/classify/val.py @@ -28,22 +28,22 @@ @smart_inference_mode() def run( - data=ROOT / '../datasets/mnist', # dataset dir - weights=None, # model.pt path(s) - batch_size=128, # batch size - imgsz=224, # inference size (pixels) - device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu - workers=8, # max dataloader workers (per RANK in DDP mode) - verbose=False, # verbose output - project=ROOT / 'runs/val-cls', # save to project/name - name='exp', # save to project/name - exist_ok=False, # existing project/name ok, do not increment - half=True, # use FP16 half-precision inference - dnn=False, # use OpenCV DNN for ONNX inference - model=None, - dataloader=None, - criterion=None, - pbar=None, + data=ROOT / '../datasets/mnist', # dataset dir + weights=None, # model.pt path(s) + batch_size=128, # batch size + imgsz=224, # inference size (pixels) + device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu + workers=8, # max dataloader workers (per RANK in DDP mode) + verbose=False, # verbose output + project=ROOT / 'runs/val-cls', # save to project/name + name='exp', # save to project/name + exist_ok=False, # existing project/name ok, do not increment + half=True, # use FP16 half-precision inference + dnn=False, # use OpenCV DNN for ONNX inference + model=None, + dataloader=None, + criterion=None, + pbar=None, ): # Initialize/load model and set device training = model is not None From d864cdac906da77cc56327415f8c83272845f0e0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 20:17:19 +0200 Subject: [PATCH 288/349] 24-space names --- classify/val.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classify/val.py b/classify/val.py index bf9857be6f4b..81e1f9696313 100644 --- a/classify/val.py +++ b/classify/val.py @@ -113,12 +113,12 @@ def run( if pbar: pbar.desc = f"{pbar.desc[:-36]}{loss:>12.3g}{top1:>12.3g}{top5:>12.3g}" if verbose: # all classes - LOGGER.info(f"{'Class':>20}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") - LOGGER.info(f"{'all':>20}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") + LOGGER.info(f"{'Class':>24}{'Images':>12}{'top1_acc':>12}{'top5_acc':>12}") + LOGGER.info(f"{'all':>24}{targets.shape[0]:>12}{top1:>12.3g}{top5:>12.3g}") for i, c in enumerate(model.names): aci = acc[targets == i] top1i, top5i = aci.mean(0).tolist() - LOGGER.info(f"{c:>20}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") + LOGGER.info(f"{c:>24}{aci.shape[0]:>12}{top1i:>12.3g}{top5i:>12.3g}") # Print results t = tuple(x / len(dataloader.dataset.samples) * 1E3 for x in dt) # speeds per image From 4020446353f92f33bb7941f5a4a43ababd3d8d05 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 22:09:03 +0200 Subject: [PATCH 289/349] Update bash scripts --- data/scripts/get_coco.sh | 41 ++++++++++++++++++++++++++----- data/scripts/get_imagenet.sh | 47 ++++++++++++++++++++++++------------ 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/data/scripts/get_coco.sh b/data/scripts/get_coco.sh index 0210c8ebbda4..290170a0bbc1 100755 --- a/data/scripts/get_coco.sh +++ b/data/scripts/get_coco.sh @@ -7,21 +7,50 @@ # └── datasets # └── coco ← downloads here +# Arguments (optional) Usage: bash data/scripts/get_coco.sh --train --val --test --segments +if [ "$#" -gt 0 ]; then + for opt in "$@"; do + case "${opt}" in + --train) train=true ;; + --val) val=true ;; + --test) test=true ;; + --segments) segments=true ;; + esac + done +else + train=true + val=true + test=false + segments=false +fi + # Download/unzip labels d='../datasets' # unzip directory url=https://github.com/ultralytics/yolov5/releases/download/v1.0/ -f='coco2017labels.zip' # or 'coco2017labels-segments.zip', 68 MB +if [ "$segments" == "true" ]; then + f='coco2017labels-segments.zip' # 168 MB +else + f='coco2017labels.zip' # 168 MB +fi echo 'Downloading' $url$f ' ...' curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # Download/unzip images d='../datasets/coco/images' # unzip directory url=http://images.cocodataset.org/zips/ -f1='train2017.zip' # 19G, 118k images -f2='val2017.zip' # 1G, 5k images -f3='test2017.zip' # 7G, 41k images (optional) -for f in $f1 $f2; do +if [ "$train" == "true" ]; then + f='train2017.zip' # 19G, 118k images + echo 'Downloading' $url$f '...' + curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & +fi +if [ "$val" == "true" ]; then + f='val2017.zip' # 1G, 5k images + echo 'Downloading' $url$f '...' + curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & +fi +if [ "$test" == "true" ]; then + f='test2017.zip' # 7G, 41k images (optional) echo 'Downloading' $url$f '...' curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & -done +fi wait # finish background tasks diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index edd48c4e4fd4..b063e022257a 100644 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -7,25 +7,42 @@ # └── datasets # └── imagenet ← downloads here -# Download +# Arguments (optional) Usage: bash data/scripts/get_imagenet.sh --train --val +if [ "$#" -gt 0 ]; then + for opt in "$@"; do + case "${opt}" in + --train) train=true ;; + --val) val=true ;; + esac + done +else + train=true + val=true +fi + +# Make dir d='../datasets/imagenet' # unzip directory mkdir -p $d && cd $d -wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # 6.3G, 50000 images -wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # 138G, 1281167 images -# Extract train -mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train -tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar -find . -name "*.tar" | while read NAME; do - mkdir -p "${NAME%.tar}" - tar -xvf "${NAME}" -C "${NAME%.tar}" - rm -f "${NAME}" -done -cd .. +# Download/unzip train +if [ "$train" == "true" ]; then + wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # download 138G, 1281167 images + mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train + tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar + find . -name "*.tar" | while read NAME; do + mkdir -p "${NAME%.tar}" + tar -xvf "${NAME}" -C "${NAME%.tar}" + rm -f "${NAME}" + done + cd .. +fi -# Extract val and move images into subdirectories -mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar -wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash +# Download/unzip val +if [ "$val" == "true" ]; then + wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # download 6.3G, 50000 images + mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar + wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash # move into subdirs +fi # Delete corrupted image (optional: PNG under JPEG name that may cause dataloaders to fail) # rm train/n04266014/n04266014_10835.JPEG From 044022bd3d0bbb7e54baf3bcd0443c760dd91db4 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 22:23:23 +0200 Subject: [PATCH 290/349] Update permissions --- data/scripts/get_coco128.sh | 0 data/scripts/get_imagenet.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 data/scripts/get_coco128.sh mode change 100644 => 100755 data/scripts/get_imagenet.sh diff --git a/data/scripts/get_coco128.sh b/data/scripts/get_coco128.sh old mode 100644 new mode 100755 diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh old mode 100644 new mode 100755 From 3d2d1125c761ba5c4c2cefc8bcdb36bb65493fba Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 22:45:36 +0200 Subject: [PATCH 291/349] Add bash script arguments --- data/scripts/get_coco.sh | 8 ++++---- data/scripts/get_coco128.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/scripts/get_coco.sh b/data/scripts/get_coco.sh index 290170a0bbc1..506d46df9fb5 100755 --- a/data/scripts/get_coco.sh +++ b/data/scripts/get_coco.sh @@ -33,7 +33,7 @@ else f='coco2017labels.zip' # 168 MB fi echo 'Downloading' $url$f ' ...' -curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & +curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f & # Download/unzip images d='../datasets/coco/images' # unzip directory @@ -41,16 +41,16 @@ url=http://images.cocodataset.org/zips/ if [ "$train" == "true" ]; then f='train2017.zip' # 19G, 118k images echo 'Downloading' $url$f '...' - curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & + curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f & fi if [ "$val" == "true" ]; then f='val2017.zip' # 1G, 5k images echo 'Downloading' $url$f '...' - curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & + curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f & fi if [ "$test" == "true" ]; then f='test2017.zip' # 7G, 41k images (optional) echo 'Downloading' $url$f '...' - curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & + curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f & fi wait # finish background tasks diff --git a/data/scripts/get_coco128.sh b/data/scripts/get_coco128.sh index ee05a867e564..e7ddce89b115 100755 --- a/data/scripts/get_coco128.sh +++ b/data/scripts/get_coco128.sh @@ -12,6 +12,6 @@ d='../datasets' # unzip directory url=https://github.com/ultralytics/yolov5/releases/download/v1.0/ f='coco128.zip' # or 'coco128-segments.zip', 68 MB echo 'Downloading' $url$f ' ...' -curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & +curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f & wait # finish background tasks From 13ec3364c182c791d22335e4ad86b3893d1a96e2 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Sun, 14 Aug 2022 22:59:57 +0200 Subject: [PATCH 292/349] remove verbose --- data/scripts/get_imagenet.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/scripts/get_imagenet.sh b/data/scripts/get_imagenet.sh index b063e022257a..6026d502e8f3 100755 --- a/data/scripts/get_imagenet.sh +++ b/data/scripts/get_imagenet.sh @@ -28,10 +28,10 @@ mkdir -p $d && cd $d if [ "$train" == "true" ]; then wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar # download 138G, 1281167 images mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train - tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar + tar -xf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar find . -name "*.tar" | while read NAME; do mkdir -p "${NAME%.tar}" - tar -xvf "${NAME}" -C "${NAME%.tar}" + tar -xf "${NAME}" -C "${NAME%.tar}" rm -f "${NAME}" done cd .. @@ -40,7 +40,7 @@ fi # Download/unzip val if [ "$val" == "true" ]; then wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar # download 6.3G, 50000 images - mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar + mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xf ILSVRC2012_img_val.tar wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash # move into subdirs fi From 95090dc3949f559f82a06948cbbf8927a5b46241 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 13:08:18 +0200 Subject: [PATCH 293/349] TRT data fix --- models/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 62c77ea2cea4..76f7478b6d1c 100644 --- a/models/common.py +++ b/models/common.py @@ -394,8 +394,8 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, if dtype == np.float16: fp16 = True shape = tuple(context.get_binding_shape(index)) - data = torch.from_numpy(np.empty(shape, dtype=np.dtype(dtype))).to(device) - bindings[name] = Binding(name, dtype, shape, data, int(data.data_ptr())) + im = torch.from_numpy(np.empty(shape, dtype=dtype)).to(device) + bindings[name] = Binding(name, dtype, shape, im, int(im.data_ptr())) binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items()) batch_size = bindings['images'].shape[0] # if dynamic, this is instead max batch size elif coreml: # CoreML From e7499314efed333198488209176cd34ac417256a Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 13:11:29 +0200 Subject: [PATCH 294/349] names generator fix --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index 76f7478b6d1c..b63c5c03796a 100644 --- a/models/common.py +++ b/models/common.py @@ -447,7 +447,7 @@ def wrap_frozen_graph(gd, inputs, outputs): # class names if 'names' not in locals(): - names = yaml_load(data)['names'] if data else (f'class{i}' for i in range(999)) + names = yaml_load(data)['names'] if data else [f'class{i}' for i in range(999)] if len(names) == 1000 and names[0] == 'n01440764': # ImageNet names = yaml_load(ROOT / 'data/ImageNet.yaml')['names'] # human-readable names From 3e6b07ad0135279c5764e487f26a7b5739056c5f Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 13:13:03 +0200 Subject: [PATCH 295/349] optimize if names --- models/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/common.py b/models/common.py index b63c5c03796a..374d36189421 100644 --- a/models/common.py +++ b/models/common.py @@ -448,7 +448,7 @@ def wrap_frozen_graph(gd, inputs, outputs): # class names if 'names' not in locals(): names = yaml_load(data)['names'] if data else [f'class{i}' for i in range(999)] - if len(names) == 1000 and names[0] == 'n01440764': # ImageNet + if names[0] == 'n01440764' and len(names) == 1000: # ImageNet names = yaml_load(ROOT / 'data/ImageNet.yaml')['names'] # human-readable names self.__dict__.update(locals()) # assign all variables to self From 5963a259179150c8bd3f7bbaf6cb4fae6c0efb8c Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 14:29:50 +0200 Subject: [PATCH 296/349] update usage --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index 9b4c9e33acc6..49b1c4e073e0 100644 --- a/classify/train.py +++ b/classify/train.py @@ -4,7 +4,7 @@ Datasets: --data mnist, fashion-mnist, cifar10, cifar100, imagenette, imagewoof, imagenet, or 'path/to/custom/dataset' Usage: - $ python classify/train.py --model yolov5s --data cifar100 --epochs 5 --img 224 --batch 128 + $ python classify/train.py --model yolov5s --data cifar100 --epochs 5 --img 128 $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 0,1,2,3 """ From d35d9f0d1cf6d6a302e992a9a11fbfc33c48ab4a Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 15:58:38 +0200 Subject: [PATCH 297/349] Add local loading --- classify/train.py | 15 +++++++-------- utils/torch_utils.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/classify/train.py b/classify/train.py index 49b1c4e073e0..6c06e2201989 100644 --- a/classify/train.py +++ b/classify/train.py @@ -20,7 +20,6 @@ import torch import torch.distributed as dist import torch.hub as hub -import torch.nn as nn import torch.optim.lr_scheduler as lr_scheduler import torchvision from torch.cuda import amp @@ -33,6 +32,7 @@ ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from classify import val as validate +from models.experimental import attempt_load from utils.dataloaders import create_classification_dataloader from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args, yaml_save) @@ -105,25 +105,24 @@ def train(opt, device): # Model repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): - if opt.model == 'list': - m = hub.list(repo1) + hub.list(repo2) # models - LOGGER.info('\nAvailable models. Usage: python classify/train.py --model MODEL\n' + '\n'.join(m)) - return + if Path(opt.model).is_file(): + model = attempt_load(opt.model, device='cpu', fuse=False) elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - from models.yolo import ClassificationModel model = smart_hub_load(repo1, opt.model, pretrained=pretrained, _verbose=False, autoshape=False, device='cpu') # detection model - model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # classification model + if '-cls' not in opt.model: # convert detect to classify model + from models.yolo import ClassificationModel + model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # classification model elif opt.model in torchvision.models.__dict__: # TorchVision models i.e. resnet50, efficientnet_b0 model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) - update_classifier_model(model, nc) # update class count else: m = hub.list(repo1) + hub.list(repo2) # models raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) + update_classifier_model(model, nc) # update class count for p in model.parameters(): p.requires_grad = True # for training for m in model.modules(): diff --git a/utils/torch_utils.py b/utils/torch_utils.py index a6e6f034f0c6..03daa10067b6 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -65,7 +65,7 @@ def smart_DDP(model): def update_classifier_model(model, n=1000): # Update a TorchVision classification model to class count 'n' - name, m = list(model.named_children())[-1] # last module + name, m = list(model.named_modules())[-1] # last module, use model.named_children() here? if isinstance(m, nn.Linear): setattr(model, name, nn.Linear(m.in_features, n)) elif isinstance(m, nn.Sequential): From cb8ddc0e81ce09129b71d092a9f3c13da290a655 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 17:46:02 +0200 Subject: [PATCH 298/349] Verbose=False --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index 6c06e2201989..2356dceea18d 100644 --- a/classify/train.py +++ b/classify/train.py @@ -255,7 +255,7 @@ def train(opt, device): # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels pred = torch.max(ema.ema((images.half() if cuda else images.float()).to(device)), 1)[1] - file = imshow_cls(images, labels, pred, names, verbose=True, f=save_dir / 'test_images.jpg') + file = imshow_cls(images, labels, pred, names, verbose=False, f=save_dir / 'test_images.jpg') # Log results meta = {"epochs": epochs, "top1_acc": best_fitness, "date": datetime.now().isoformat()} From 5a5bd4ee9b03d5600e11117a7b2604f9e6f2f712 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 17:52:31 +0200 Subject: [PATCH 299/349] update names printing --- classify/train.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/classify/train.py b/classify/train.py index 2356dceea18d..117889cb56ba 100644 --- a/classify/train.py +++ b/classify/train.py @@ -98,10 +98,6 @@ def train(opt, device): rank=-1, workers=nw) - # Initialize - names = trainloader.dataset.classes # class names - LOGGER.info(f'Training {opt.model} on {data} dataset with {nc} classes...') - # Model repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): @@ -129,6 +125,7 @@ def train(opt, device): if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout model = model.to(device) + names = trainloader.dataset.classes # class names model.names = names # attach class names # Info @@ -168,7 +165,7 @@ def train(opt, device): LOGGER.info(f'Image sizes {imgsz} train, {imgsz} test\n' f'Using {nw * WORLD_SIZE} dataloader workers\n' f"Logging results to {colorstr('bold', save_dir)}\n" - f'Starting training for {epochs} epochs...\n\n' + f'Starting {opt.model} training on {data} dataset with {nc} classes for {epochs} epochs...\n\n' f"{'Epoch':>10}{'GPU_mem':>10}{'train_loss':>12}{f'{val}_loss':>12}{'top1_acc':>12}{'top5_acc':>12}") for epoch in range(epochs): # loop over the dataset multiple times tloss, vloss, fitness = 0.0, 0.0, 0.0 # train loss, val loss, fitness From b3bd5a1d6cb45650a8592fc2955e1d2e25c24a9f Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 18:08:01 +0200 Subject: [PATCH 300/349] Add Usage examples --- classify/train.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index 117889cb56ba..f9bfda91bb0f 100644 --- a/classify/train.py +++ b/classify/train.py @@ -246,8 +246,13 @@ def train(opt, device): # Train complete if RANK in {-1, 0} and final_epoch: - LOGGER.info(f'\nTraining complete {(time.time() - t0) / 3600:.3f} hours.' - f"\nResults saved to {colorstr('bold', save_dir)}\n") + LOGGER.info(f'\nTraining complete ({(time.time() - t0) / 3600:.3f} hours)' + f"\nResults saved to {colorstr('bold', save_dir)}" + f"\nPredict: python classify/predict.py --weights {best} --source im.jpg" + f"\nValidate: python classify/val.py --weights {best} --data {data}" + f"\nExport: python export.py --weights {best} --include onnx" + f"\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{best}')" + f"\nVisualize: https://netron.app\n") # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels From 4086b0f739e5da083fc9f70435753910901add2b Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 18:19:35 +0200 Subject: [PATCH 301/349] Add Usage examples --- classify/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classify/train.py b/classify/train.py index f9bfda91bb0f..bf3b8368311b 100644 --- a/classify/train.py +++ b/classify/train.py @@ -249,7 +249,7 @@ def train(opt, device): LOGGER.info(f'\nTraining complete ({(time.time() - t0) / 3600:.3f} hours)' f"\nResults saved to {colorstr('bold', save_dir)}" f"\nPredict: python classify/predict.py --weights {best} --source im.jpg" - f"\nValidate: python classify/val.py --weights {best} --data {data}" + f"\nValidate: python classify/val.py --weights {best} --data {data_dir}" f"\nExport: python export.py --weights {best} --include onnx" f"\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{best}')" f"\nVisualize: https://netron.app\n") From 6d2ce08137aed0313b2f807d18137ba0a0bea7be Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 18:20:51 +0200 Subject: [PATCH 302/349] Add Usage examples --- classify/train.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/classify/train.py b/classify/train.py index bf3b8368311b..c90853d667a4 100644 --- a/classify/train.py +++ b/classify/train.py @@ -248,11 +248,11 @@ def train(opt, device): if RANK in {-1, 0} and final_epoch: LOGGER.info(f'\nTraining complete ({(time.time() - t0) / 3600:.3f} hours)' f"\nResults saved to {colorstr('bold', save_dir)}" - f"\nPredict: python classify/predict.py --weights {best} --source im.jpg" - f"\nValidate: python classify/val.py --weights {best} --data {data_dir}" - f"\nExport: python export.py --weights {best} --include onnx" - f"\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{best}')" - f"\nVisualize: https://netron.app\n") + f"\nPredict: python classify/predict.py --weights {best} --source im.jpg" + f"\nValidate: python classify/val.py --weights {best} --data {data_dir}" + f"\nExport: python export.py --weights {best} --include onnx" + f"\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{best}')" + f"\nVisualize: https://netron.app\n") # Plot examples images, labels = (x[:25] for x in next(iter(testloader))) # first 25 images and labels From d21251169245aab51ab993e4f441cd6abdf5fb8f Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 18:28:20 +0200 Subject: [PATCH 303/349] Add Usage examples --- utils/plots.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index b974c6c8693b..b4a62504463d 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -397,7 +397,8 @@ def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols - ax = ax.ravel() if m > 1 else [ax] + if m > 1: + ax = ax.ravel() # plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) From 1abaf4226373fb22dc9f0eb91f646267dab18d7d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 18:48:50 +0200 Subject: [PATCH 304/349] named_children --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 03daa10067b6..a6e6f034f0c6 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -65,7 +65,7 @@ def smart_DDP(model): def update_classifier_model(model, n=1000): # Update a TorchVision classification model to class count 'n' - name, m = list(model.named_modules())[-1] # last module, use model.named_children() here? + name, m = list(model.named_children())[-1] # last module if isinstance(m, nn.Linear): setattr(model, name, nn.Linear(m.in_features, n)) elif isinstance(m, nn.Sequential): From db2f7b3788529a7cfe56d552727b33aaf6df8f5a Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 20:56:21 +0200 Subject: [PATCH 305/349] reshape_classifier_outputs --- classify/train.py | 23 +++++++++-------------- utils/torch_utils.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/classify/train.py b/classify/train.py index 0fc87e3d23c8..85e7d802e44f 100644 --- a/classify/train.py +++ b/classify/train.py @@ -33,13 +33,14 @@ from classify import val as validate from models.experimental import attempt_load +from models.yolo import DetectionModel, ClassificationModel from utils.dataloaders import create_classification_dataloader from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args, yaml_save) from utils.loggers import GenericLogger from utils.plots import imshow_cls -from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_hub_load, smart_optimizer, - smartCrossEntropyLoss, torch_distributed_zero_first, update_classifier_model) +from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, + smartCrossEntropyLoss, torch_distributed_zero_first, reshape_classifier_output) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) @@ -103,25 +104,19 @@ def train(opt, device): with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): if Path(opt.model).is_file() or opt.model.endswith('.pt'): model = attempt_load(opt.model, device='cpu', fuse=False) - elif opt.model.startswith('yolov5'): # YOLOv5 models, i.e. yolov5s, yolov5m - model = smart_hub_load(repo1, - opt.model, - pretrained=pretrained, - _verbose=False, - autoshape=False, - device='cpu') # detection model - if '-cls' not in opt.model: # convert detect to classify model - from models.yolo import ClassificationModel - model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # classification model elif opt.model in torchvision.models.__dict__: # TorchVision models i.e. resnet50, efficientnet_b0 model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) else: - m = hub.list(repo1) # + hub.list(repo2) # models + m = hub.list(repo1, skip_validation=True) # hub.list(repo2, skip_validation=True) # models raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) - update_classifier_model(model, nc) # update class count + if isinstance(model, DetectionModel): + model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # convert to classification model + reshape_classifier_output(model, nc) # update class count for p in model.parameters(): p.requires_grad = True # for training for m in model.modules(): + if not pretrained and hasattr(m, 'reset_parameters'): + m.reset_parameters() if isinstance(m, torch.nn.Dropout) and opt.dropout is not None: m.p = opt.dropout # set dropout model = model.to(device) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index a6e6f034f0c6..f00fcb92612c 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -63,19 +63,22 @@ def smart_DDP(model): return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) -def update_classifier_model(model, n=1000): - # Update a TorchVision classification model to class count 'n' +def reshape_classifier_output(model, n=1000): + # Update a TorchVision classification model to class count 'n' if required name, m = list(model.named_children())[-1] # last module if isinstance(m, nn.Linear): - setattr(model, name, nn.Linear(m.in_features, n)) + if m.out_features != n: + setattr(model, name, nn.Linear(m.in_features, n)) elif isinstance(m, nn.Sequential): types = [type(x) for x in m] if nn.Linear in types: i = types.index(nn.Linear) # nn.Linear index - m[i] = nn.Linear(m[i].in_features, n) + if m[i].out_features != n: + m[i] = nn.Linear(m[i].in_features, n) elif nn.Conv2d in types: i = types.index(nn.Conv2d) # nn.Conv2d index - m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias) + if m[i].out_channels != n: + m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias) @contextmanager From 5b2070bf16e32686c666d1a68601f96e06a3ac79 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 18:57:10 +0000 Subject: [PATCH 306/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- classify/train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classify/train.py b/classify/train.py index 85e7d802e44f..72c9b2c7d946 100644 --- a/classify/train.py +++ b/classify/train.py @@ -33,14 +33,14 @@ from classify import val as validate from models.experimental import attempt_load -from models.yolo import DetectionModel, ClassificationModel +from models.yolo import ClassificationModel, DetectionModel from utils.dataloaders import create_classification_dataloader from utils.general import (DATASETS_DIR, LOGGER, WorkingDirectory, check_git_status, check_requirements, colorstr, download, increment_path, init_seeds, print_args, yaml_save) from utils.loggers import GenericLogger from utils.plots import imshow_cls -from utils.torch_utils import (ModelEMA, model_info, select_device, smart_DDP, smart_optimizer, - smartCrossEntropyLoss, torch_distributed_zero_first, reshape_classifier_output) +from utils.torch_utils import (ModelEMA, model_info, reshape_classifier_output, select_device, smart_DDP, + smart_optimizer, smartCrossEntropyLoss, torch_distributed_zero_first) LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html RANK = int(os.getenv('RANK', -1)) From 1338c6dde5bd5b837aa022a2f950ff9c14eb51d5 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 21:05:40 +0200 Subject: [PATCH 307/349] update --- classify/train.py | 2 +- classify/val.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index 85e7d802e44f..2d49ac9b3b51 100644 --- a/classify/train.py +++ b/classify/train.py @@ -262,7 +262,7 @@ def train(opt, device): def parse_opt(known=False): parser = argparse.ArgumentParser() - parser.add_argument('--model', type=str, default='yolov5n', help='initial weights path') + parser.add_argument('--model', type=str, default='yolov5s-cls.pt', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist, imagenet, etc.') parser.add_argument('--epochs', type=int, default=1) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') diff --git a/classify/val.py b/classify/val.py index 6c3daf34d577..0930ba8c9c51 100644 --- a/classify/val.py +++ b/classify/val.py @@ -29,7 +29,7 @@ @smart_inference_mode() def run( data=ROOT / '../datasets/mnist', # dataset dir - weights=None, # model.pt path(s) + weights=ROOT / 'yolov5s-cls.pt', # model.pt path(s) batch_size=128, # batch size imgsz=224, # inference size (pixels) device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu From b088d52088da5a7b7ca61f358a773be87eef39bd Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 21:08:46 +0200 Subject: [PATCH 308/349] update --- classify/train.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index ef658ff44bf8..0287254cf458 100644 --- a/classify/train.py +++ b/classify/train.py @@ -100,14 +100,13 @@ def train(opt, device): workers=nw) # Model - repo1, repo2 = 'ultralytics/yolov5', 'pytorch/vision' with torch_distributed_zero_first(LOCAL_RANK), WorkingDirectory(ROOT): if Path(opt.model).is_file() or opt.model.endswith('.pt'): model = attempt_load(opt.model, device='cpu', fuse=False) elif opt.model in torchvision.models.__dict__: # TorchVision models i.e. resnet50, efficientnet_b0 model = torchvision.models.__dict__[opt.model](weights='IMAGENET1K_V1' if pretrained else None) else: - m = hub.list(repo1, skip_validation=True) # hub.list(repo2, skip_validation=True) # models + m = hub.list('ultralytics/yolov5') # + hub.list('pytorch/vision') # models raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) if isinstance(model, DetectionModel): model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # convert to classification model From 6730731cef1defd5ba53eb018e55fc24de5e3c62 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 21:33:44 +0200 Subject: [PATCH 309/349] fix CI --- .github/workflows/ci-testing.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 209881b56b28..df9ba3d4e531 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -126,10 +126,10 @@ jobs: - name: Run classification tests shell: bash run: | - m=${{ matrix.model }} # official weights - b=runs/train-cls/exp/weights/best # best.pt checkpoint + m=${{ matrix.model }}-cls.pt # official weights + b=runs/train-cls/exp/weights/best.pt # best.pt checkpoint python classify/train.py --imgsz 32 --model $m --data mnist2560 --epochs 1 # train - python classify/val.py --imgsz 32 --weights $b.pt --data ../datasets/mnist2560 # val - python classify/predict.py --imgsz 32 --weights $b.pt --source ../datasets/mnist2560/test/7/60.png # predict - python classify/predict.py --imgsz 32 --weights $m-cls.pt --source data/images/bus.jpg # predict - python export.py --weights $b.pt --img 64 --imgsz 224 --include torchscript # export + python classify/val.py --imgsz 32 --weights $b --data ../datasets/mnist2560 # val + python classify/predict.py --imgsz 32 --weights $b --source ../datasets/mnist2560/test/7/60.png # predict + python classify/predict.py --imgsz 32 --weights $m --source data/images/bus.jpg # predict + python export.py --weights $b --img 64 --imgsz 224 --include torchscript # export From fc3b0bb2ccc766fd06cebb3924858a7f6d35851d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 21:59:15 +0200 Subject: [PATCH 310/349] fix incorrect class substitution --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index f00fcb92612c..40670cfaabdd 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -65,7 +65,7 @@ def smart_DDP(model): def reshape_classifier_output(model, n=1000): # Update a TorchVision classification model to class count 'n' if required - name, m = list(model.named_children())[-1] # last module + name, m = list(model.named_modules())[-1] # last module if isinstance(m, nn.Linear): if m.out_features != n: setattr(model, name, nn.Linear(m.in_features, n)) From 0ff0ec20b91f6db130d49b1e79e0212d336aad27 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 22:22:39 +0200 Subject: [PATCH 311/349] fix incorrect class substitution --- utils/torch_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 40670cfaabdd..e6dd4d7f96a4 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -65,8 +65,12 @@ def smart_DDP(model): def reshape_classifier_output(model, n=1000): # Update a TorchVision classification model to class count 'n' if required - name, m = list(model.named_modules())[-1] # last module - if isinstance(m, nn.Linear): + from models.common import Classify + name, m = list((model.model if hasattr(model, 'model') else model).named_children())[-1] # last module + if isinstance(m, Classify): # YOLOv5 Classify() head + if m.linear.out_features != n: + m.linear = nn.Linear(m.linear.in_features, n) + elif isinstance(m, nn.Linear): # ResNet, EfficientNet if m.out_features != n: setattr(model, name, nn.Linear(m.in_features, n)) elif isinstance(m, nn.Sequential): From 72e5607b4d42d2896dc02615caf0aafe6ee93300 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 22:38:14 +0200 Subject: [PATCH 312/349] remove denormalize --- classify/predict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index 3b588ad88c6e..36cb68a9f77f 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -22,7 +22,7 @@ from classify.train import imshow_cls from models.common import DetectMultiBackend -from utils.augmentations import classify_transforms, denormalize +from utils.augmentations import classify_transforms from utils.general import LOGGER, check_requirements, colorstr, increment_path, print_args from utils.torch_utils import select_device, smart_inference_mode, time_sync @@ -75,7 +75,7 @@ def run( # Plot if show: - imshow_cls(denormalize(im), f=save_dir / Path(file).name, verbose=True) + imshow_cls(im, f=save_dir / Path(file).name, verbose=True) # Print results t = tuple(x / seen * 1E3 for x in dt) # speeds per image From bf1dae5bb6744f2c1c724d46d2de9d6feb04ac05 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 22:57:23 +0200 Subject: [PATCH 313/349] ravel fix --- utils/plots.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/plots.py b/utils/plots.py index b4a62504463d..b974c6c8693b 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -397,8 +397,7 @@ def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols - if m > 1: - ax = ax.ravel() + ax = ax.ravel() if m > 1 else [ax] # plt.subplots_adjust(wspace=0.05, hspace=0.05) for i in range(n): ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) From 5f64ebdd70d72c1e96480cc4e7972dc6347b2da2 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 23:01:14 +0200 Subject: [PATCH 314/349] cleanup --- classify/predict.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/classify/predict.py b/classify/predict.py index 36cb68a9f77f..419830d43952 100644 --- a/classify/predict.py +++ b/classify/predict.py @@ -73,14 +73,12 @@ def run( dt[2] += time_sync() - t3 LOGGER.info(f"image 1/1 {file}: {imgsz}x{imgsz} {', '.join(f'{model.names[j]} {p[0, j]:.2f}' for j in i)}") - # Plot - if show: - imshow_cls(im, f=save_dir / Path(file).name, verbose=True) - # Print results t = tuple(x / seen * 1E3 for x in dt) # speeds per image shape = (1, 3, imgsz, imgsz) LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms post-process per image at shape {shape}' % t) + if show: + imshow_cls(im, f=save_dir / Path(file).name, verbose=True) LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}") return p From bdee2e45b158032d5c083131684fa568f1b56aa6 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 23:08:14 +0200 Subject: [PATCH 315/349] update opt file printing --- utils/general.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index 50452c584f6c..fc81da7814aa 100755 --- a/utils/general.py +++ b/utils/general.py @@ -195,7 +195,8 @@ def print_args(args: Optional[dict] = None, show_file=True, show_fcn=False): if args is None: # get args automatically args, _, _, frm = inspect.getargvalues(x) args = {k: v for k, v in frm.items() if k in args} - s = (f'{Path(file).stem}: ' if show_file else '') + (f'{fcn}: ' if show_fcn else '') + file = Path(file).relative_to(ROOT).with_suffix('') + s = (f'{file}: ' if show_file else '') + (f'{fcn}: ' if show_fcn else '') LOGGER.info(colorstr(s) + ', '.join(f'{k}={v}' for k, v in args.items())) From fd388d69008ff587adf96beff95d084e023e1d44 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 23:13:54 +0200 Subject: [PATCH 316/349] update opt file printing --- utils/general.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index fc81da7814aa..d1d4e1584ccb 100755 --- a/utils/general.py +++ b/utils/general.py @@ -195,7 +195,10 @@ def print_args(args: Optional[dict] = None, show_file=True, show_fcn=False): if args is None: # get args automatically args, _, _, frm = inspect.getargvalues(x) args = {k: v for k, v in frm.items() if k in args} - file = Path(file).relative_to(ROOT).with_suffix('') + try: + file = Path(file).resolve().relative_to(ROOT).with_suffix('') + except ValueError: + file = Path(file).stem s = (f'{file}: ' if show_file else '') + (f'{fcn}: ' if show_fcn else '') LOGGER.info(colorstr(s) + ', '.join(f'{k}={v}' for k, v in args.items())) From 1a307b8ef3c7a923186eba266fee4a53351bb803 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Mon, 15 Aug 2022 23:25:46 +0200 Subject: [PATCH 317/349] update defaults --- classify/train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index 0287254cf458..df64deaaed57 100644 --- a/classify/train.py +++ b/classify/train.py @@ -263,9 +263,9 @@ def parse_opt(known=False): parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='yolov5s-cls.pt', help='initial weights path') parser.add_argument('--data', type=str, default='mnist', help='cifar10, cifar100, mnist, imagenet, etc.') - parser.add_argument('--epochs', type=int, default=1) + parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch-size', type=int, default=64, help='total batch size for all GPUs') - parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=224, help='train, val image size (pixels)') + parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=128, help='train, val image size (pixels)') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') From 6a347f3bda1928539868b365169867bb1e6d830d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 00:29:47 +0200 Subject: [PATCH 318/349] add opt to checkpoint --- classify/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classify/train.py b/classify/train.py index df64deaaed57..293455688367 100644 --- a/classify/train.py +++ b/classify/train.py @@ -230,6 +230,7 @@ def train(opt, device): 'ema': None, # deepcopy(ema.ema).half(), 'updates': ema.updates, 'optimizer': None, # optimizer.state_dict(), + 'opt': vars(opt), 'date': datetime.now().isoformat()} # Save last, best and delete From 26dfbe3dac838722280d00bc5f195fca2a81f257 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 02:16:44 +0200 Subject: [PATCH 319/349] Add warning --- classify/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/classify/train.py b/classify/train.py index 293455688367..f9b683ac3b3c 100644 --- a/classify/train.py +++ b/classify/train.py @@ -109,6 +109,7 @@ def train(opt, device): m = hub.list('ultralytics/yolov5') # + hub.list('pytorch/vision') # models raise ModuleNotFoundError(f'--model {opt.model} not found. Available models are: \n' + '\n'.join(m)) if isinstance(model, DetectionModel): + LOGGER.warning("WARNING: pass YOLOv5 classifier model with '-cls' suffix, i.e. '--model yolov5s-cls.pt'") model = ClassificationModel(model=model, nc=nc, cutoff=opt.cutoff or 10) # convert to classification model reshape_classifier_output(model, nc) # update class count for p in model.parameters(): From b6d3018253e2eae7f98d92ca0c5c19c2bca0a931 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 14:56:07 +0200 Subject: [PATCH 320/349] Add comment --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index d1d4e1584ccb..0455a571a7c5 100755 --- a/utils/general.py +++ b/utils/general.py @@ -363,7 +363,7 @@ def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=Fals @try_except def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True, cmds=()): - # Check installed dependencies meet requirements (pass *.txt file or list of packages) + # Check installed dependencies meet YOLOv5 requirements (pass *.txt file or list of packages) prefix = colorstr('red', 'bold', 'requirements:') check_python() # check python version if isinstance(requirements, (str, Path)): # requirements.txt file From 04e84d6ce4f9a22d9219b25ee3995a2c8ea9d918 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 17:10:24 +0200 Subject: [PATCH 321/349] plot half bug fix --- utils/plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index b974c6c8693b..41416584175d 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -393,7 +393,7 @@ def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f from utils.augmentations import denormalize names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(denormalize(im).cpu(), len(im), dim=0) # select batch index 0, block by channels + blocks = torch.chunk(denormalize(im).cpu().float(), len(im), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols From 9d9a2f1a2df25b22b84e0f2502ccebf1bcdbda91 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 17:14:27 +0200 Subject: [PATCH 322/349] Use NotImplementedError --- models/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 374d36189421..69044528bf55 100644 --- a/models/common.py +++ b/models/common.py @@ -441,9 +441,9 @@ def wrap_frozen_graph(gd, inputs, outputs): input_details = interpreter.get_input_details() # inputs output_details = interpreter.get_output_details() # outputs elif tfjs: - raise Exception('ERROR: YOLOv5 TF.js inference is not supported') + raise NotImplementedError('ERROR: YOLOv5 TF.js inference is not supported') else: - raise Exception(f'ERROR: {w} is not a supported format') + raise NotImplementedError(f'ERROR: {w} is not a supported format') # class names if 'names' not in locals(): From 44584ce01e3d3b6657fe35881bff9ef7ab714253 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 19:34:01 +0200 Subject: [PATCH 323/349] fix export shape report --- export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/export.py b/export.py index 431f98e61bef..595039b24bce 100644 --- a/export.py +++ b/export.py @@ -518,7 +518,7 @@ def run( y = model(im) # dry runs if half and not coreml: im, model = im.half(), model.half() # to FP16 - shape = tuple(y[0].shape) # model output shape + shape = tuple((y[0] if isinstance(y, tuple) else y).shape) # model output shape LOGGER.info(f"\n{colorstr('PyTorch:')} starting from {file} with output shape {shape} ({file_size(file):.1f} MB)") # Exports From b26129bcfc92212af34213c49d2081ccc4b319f6 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 19:57:01 +0200 Subject: [PATCH 324/349] Fix TRT load --- models/common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/common.py b/models/common.py index 69044528bf55..17e40e60d7d7 100644 --- a/models/common.py +++ b/models/common.py @@ -323,8 +323,7 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, w = str(weights[0] if isinstance(weights, list) else weights) pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs = self._model_type(w) # get backend w = attempt_download(w) # download if not local - cuda = torch.cuda.is_available() and device.type != 'cpu' - fp16 &= (pt or jit or onnx or engine) and cuda # FP16 + fp16 &= pt or jit or onnx or engine # FP16 stride = 32 # default stride if pt: # PyTorch @@ -347,6 +346,7 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, net = cv2.dnn.readNetFromONNX(w) elif onnx: # ONNX Runtime LOGGER.info(f'Loading {w} for ONNX Runtime inference...') + cuda = torch.cuda.is_available() and device.type != 'cpu' check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime')) import onnxruntime providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider'] @@ -376,6 +376,8 @@ def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, LOGGER.info(f'Loading {w} for TensorRT inference...') import tensorrt as trt # https://developer.nvidia.com/nvidia-tensorrt-download check_version(trt.__version__, '7.0.0', hard=True) # require tensorrt>=7.0.0 + if device.type == 'cpu': + device = torch.device('cuda:0') Binding = namedtuple('Binding', ('name', 'dtype', 'shape', 'data', 'ptr')) logger = trt.Logger(trt.Logger.INFO) with open(w, 'rb') as f, trt.Runtime(logger) as runtime: From 744906698f07cf0286201eac5e193d566fe4a6a1 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 21:55:15 +0200 Subject: [PATCH 325/349] cleanup CI --- .github/workflows/ci-testing.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index df9ba3d4e531..4134ad89a6e0 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -100,8 +100,7 @@ jobs: python --version pip --version pip list - - name: Run tests - shell: bash + - name: Test detection run: | # export PYTHONPATH="$PWD" # to run '$ python *.py' files in subdirectories m=${{ matrix.model }} # official weights @@ -123,8 +122,7 @@ jobs: model = torch.hub.load('.', 'custom', path=path, source='local') print(model('data/images/bus.jpg')) EOF - - name: Run classification tests - shell: bash + - name: Test classification run: | m=${{ matrix.model }}-cls.pt # official weights b=runs/train-cls/exp/weights/best.pt # best.pt checkpoint From c0f0b7b6887438e32dda887cf61fe0f61d05d5e0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 22:01:52 +0200 Subject: [PATCH 326/349] profile comment --- utils/torch_utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index e6dd4d7f96a4..1cdbe20f8670 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -149,14 +149,13 @@ def time_sync(): def profile(input, ops, n=10, device=None): - # YOLOv5 speed/memory/FLOPs profiler - # - # Usage: - # input = torch.randn(16, 3, 640, 640) - # m1 = lambda x: x * torch.sigmoid(x) - # m2 = nn.SiLU() - # profile(input, [m1, m2], n=100) # profile over 100 iterations - + """ YOLOv5 speed/memory/FLOPs profiler + Usage: + input = torch.randn(16, 3, 640, 640) + m1 = lambda x: x * torch.sigmoid(x) + m2 = nn.SiLU() + profile(input, [m1, m2], n=100) # profile over 100 iterations + """ results = [] if not isinstance(device, torch.device): device = select_device(device) From 01806e33984e8bdda8fdb0a6c3cf98801ee88a98 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 22:03:11 +0200 Subject: [PATCH 327/349] CI fix --- .github/workflows/ci-testing.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 4134ad89a6e0..aa797c44d487 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -87,7 +87,7 @@ jobs: else pip install -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu fi - shell: bash # required for Windows compatibility + shell: bash # for Windows compatibility - name: Check environment run: | python -c "import utils; utils.notebook_init()" @@ -101,6 +101,7 @@ jobs: pip --version pip list - name: Test detection + shell: bash # for Windows compatibility run: | # export PYTHONPATH="$PWD" # to run '$ python *.py' files in subdirectories m=${{ matrix.model }} # official weights @@ -123,6 +124,7 @@ jobs: print(model('data/images/bus.jpg')) EOF - name: Test classification + shell: bash # for Windows compatibility run: | m=${{ matrix.model }}-cls.pt # official weights b=runs/train-cls/exp/weights/best.pt # best.pt checkpoint From 1d867fdd6cb2e47ee98ec935b6a5fd89f31fdfea Mon Sep 17 00:00:00 2001 From: glennjocher Date: Tue, 16 Aug 2022 22:33:52 +0200 Subject: [PATCH 328/349] Add cls models --- data/scripts/download_weights.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/scripts/download_weights.sh b/data/scripts/download_weights.sh index e9fa65394178..a4f3becfdbeb 100755 --- a/data/scripts/download_weights.sh +++ b/data/scripts/download_weights.sh @@ -1,7 +1,7 @@ #!/bin/bash # YOLOv5 🚀 by Ultralytics, GPL-3.0 license # Download latest models from https://github.com/ultralytics/yolov5/releases -# Example usage: bash path/to/download_weights.sh +# Example usage: bash data/scripts/download_weights.sh # parent # └── yolov5 # ├── yolov5s.pt ← downloads here @@ -11,10 +11,11 @@ python - < Date: Tue, 16 Aug 2022 23:35:02 +0200 Subject: [PATCH 329/349] avoid inplace error --- utils/plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index 41416584175d..b79b21609d04 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -393,7 +393,7 @@ def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f from utils.augmentations import denormalize names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(denormalize(im).cpu().float(), len(im), dim=0) # select batch index 0, block by channels + blocks = torch.chunk(denormalize(im.clone()).cpu().float(), len(im), dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols From 3652de4a825d86146478510d5189b324ec4ccdd7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Aug 2022 21:35:30 +0000 Subject: [PATCH 330/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- utils/plots.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index b79b21609d04..7417308c4d82 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -393,7 +393,8 @@ def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f from utils.augmentations import denormalize names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(denormalize(im.clone()).cpu().float(), len(im), dim=0) # select batch index 0, block by channels + blocks = torch.chunk(denormalize(im.clone()).cpu().float(), len(im), + dim=0) # select batch index 0, block by channels n = min(len(blocks), nmax) # number of plots m = min(8, round(n ** 0.5)) # 8 x 8 default fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols From d1c84ae4cf5c4596c9b997b4a34f5574681dec8d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:38:44 +0200 Subject: [PATCH 331/349] Fix usage examples --- classify/train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classify/train.py b/classify/train.py index f9b683ac3b3c..f2b465567446 100644 --- a/classify/train.py +++ b/classify/train.py @@ -4,8 +4,8 @@ Datasets: --data mnist, fashion-mnist, cifar10, cifar100, imagenette, imagewoof, imagenet, or 'path/to/custom/dataset' Usage: - $ python classify/train.py --model yolov5s --data cifar100 --epochs 5 --img 128 - $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s --data imagenet --epochs 5 --img 224 --device 0,1,2,3 + $ python classify/train.py --model yolov5s-cls.pt --data cifar100 --epochs 5 --img 128 + $ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s-cls.pt --data imagenet --epochs 5 --img 224 --device 0,1,2,3 """ import argparse From 706ff9675940156abcee215966f06f9c78f46558 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:45:36 +0200 Subject: [PATCH 332/349] Update README --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 78247f81aad2..46f8b5e1cb82 100644 --- a/README.md +++ b/README.md @@ -254,18 +254,77 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi -### Classification Checkpoints - -| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
(hours) | Speed
CPU
(ms) | Speed
V100
(ms) | params
(M) | FLOPs
@640 (B) | -|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-----------------------------------------|---------------------------|----------------------------|--------------------|------------------------| -| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 61.8 | 9.2 | 25.6 | 8.3 | -| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | -| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | 8.0 | 3.9 | 2.5 | 0.4 | -| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 14.5 | 4.6 | 5.5 | 1.3 | -| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 30.1 | 6.3 | 13.0 | 3.8 | -| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 55.4 | 8.3 | 26.6 | 8.4 | -| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | 78.9 | 94.4 | 15:04 | 96.5 | 10.1 | 48.1 | 15.9 | +##
Classification ⭐ NEW
+YOLOv5 now supports classification training, validation, prediction and export since [release v6.2](https://github.com/ultralytics/yolov5/releases)! We've made training classification as simple as our detection models. Click below to get started. + +
+ Classification Checkpoints (click to expand) + +| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
4x A100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-------------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | +| | +| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | +| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | +| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | +| | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | +| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | +| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | + +
+ Table Notes (click to expand) + +- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. +- **Accuracy** values are for single-model single-scale on [ImageNet-1k](https://www.image-net.org/index.php) dataset.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224` +- **Speed** averaged over 100 inference images using a [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` +- **Export** to ONNX at FP32 and TensorRT at FP16 done with `export.py`.
Reproduce by `python export.py --weights yolov5s-cls.pt --include engine onnx --imgsz 224` +
+
+ +
+ Classification Usage Examples (click to expand) + +### Train +YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets. + +```bash +# Single-GPU +python classify/train.py --model yolov5s-cls.pt --data cifar100 --epochs 5 --img 224 --batch 128 + +# Multi-GPU DDP +python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s-cls.pt --data imagenet --epochs 5 --img 224 --device 0,1,2,3 +``` + +### Val +```bash +python classify/val.py --weights yolov5s-cls.pt --data ../datasets/imagenet --img 224 +``` + +### Predict +```bash +python classify/predict.py --weights yolov5s-cls.pt --data data/images/bus.jpg +``` +```python +model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load from PyTorch Hub +``` + +### Export +```bash +python export.py --weights yolov5s-cls.pt --include onnx +python export.py --weights resnet50.pt --include onnx +python export.py --weights efficientnet_b0.pt --include onnx +``` +
+ + ##
Contribute
From 6f4a1bb3a0c3650e1195e4047376c856870efba5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Aug 2022 23:46:11 +0000 Subject: [PATCH 333/349] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46f8b5e1cb82..48fcef6ec990 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ YOLOv5 now supports classification training, validation, prediction and export s | [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | | [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | | [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | -| | +| | | [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | | [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | | [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | From b5f8748ba0185d4d4eb1f7eeeb2b5646a0936bfd Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:51:03 +0200 Subject: [PATCH 334/349] Update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46f8b5e1cb82..163e8686a0e2 100644 --- a/README.md +++ b/README.md @@ -256,11 +256,13 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi ##
Classification ⭐ NEW
-YOLOv5 now supports classification training, validation, prediction and export since [release v6.2](https://github.com/ultralytics/yolov5/releases)! We've made training classification as simple as our detection models. Click below to get started. +YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings support for classification model training, validation, prediction and export! We've made training classifier models super simple. Click below to get started.
Classification Checkpoints (click to expand) +We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models as well to compare. We exported all models to ONNX in FP32 for CPU speed tests and to TensorRT for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. + | Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
4x A100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-------------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| | [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | @@ -284,7 +286,7 @@ YOLOv5 now supports classification training, validation, prediction and export s - All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. - **Accuracy** values are for single-model single-scale on [ImageNet-1k](https://www.image-net.org/index.php) dataset.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224` -- **Speed** averaged over 100 inference images using a [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` +- **Speed** averaged over 100 inference images using a Google [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` - **Export** to ONNX at FP32 and TensorRT at FP16 done with `export.py`.
Reproduce by `python export.py --weights yolov5s-cls.pt --include engine onnx --imgsz 224`
@@ -324,7 +326,6 @@ python export.py --weights efficientnet_b0.pt --include onnx ``` - ##
Contribute
From 0a8f489741ea9c0158d3d54c2048894482dae4ad Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:53:33 +0200 Subject: [PATCH 335/349] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3ccf0427ea5..1f42a80085ea 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,8 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup
Classification Checkpoints (click to expand) -We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models as well to compare. We exported all models to ONNX in FP32 for CPU speed tests and to TensorRT for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. +
+We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
4x A100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-------------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| From 33ae0179d8140944b4a1d7be36885dabe8908429 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:54:50 +0200 Subject: [PATCH 336/349] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1f42a80085ea..e61cbec271f5 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup Classification Checkpoints (click to expand)
+ We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
4x A100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | From 7c494aa023afbc7f009b7bcfbb0597a864942a32 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:55:48 +0200 Subject: [PATCH 337/349] Update README --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e61cbec271f5..c9d6338d9bb0 100644 --- a/README.md +++ b/README.md @@ -265,23 +265,23 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. -| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Train time
90 epochs
4x A100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | -|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|-------------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| -| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | -| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | -| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | -| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | -| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | +| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | | | -| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | -| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | -| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | -| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | +| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | +| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | +| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | | | -| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | -| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | -| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | -| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | +| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | +| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 |
Table Notes (click to expand) From dcdc38b70df030fc815b865f9f0bdcba278ce790 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:56:30 +0200 Subject: [PATCH 338/349] Update README --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c9d6338d9bb0..41a0d3d02759 100644 --- a/README.md +++ b/README.md @@ -265,23 +265,23 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. -| Model | size
(pixels) | accuracy
top1 | accuracy
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | -|----------------------------------------------------------------------------------------------------|-----------------------|-----------------------|-----------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| -| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | -| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | -| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | -| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | -| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | +| Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | | | -| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | -| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | -| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | -| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | +| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | +| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | +| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | | | -| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | -| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | -| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | -| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | +| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | +| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 |
Table Notes (click to expand) From 85289d48569b1d74c3e2af48680f27bf915de583 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 01:57:47 +0200 Subject: [PATCH 339/349] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41a0d3d02759..5d3adb8d0925 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. -| Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX-CPU
(ms) | Speed
TensorRT-V100
(ms) | params
(M) | FLOPs
@224 (B) | +| Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| | [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | | [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | From ef63af1236d9954945539fc3a27e6c6cfe1465b0 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 02:50:43 +0200 Subject: [PATCH 340/349] Update README --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 5d3adb8d0925..e2b0588bb08a 100644 --- a/README.md +++ b/README.md @@ -201,14 +201,6 @@ Get started in seconds with our verified environments. Click each icon below for |:-:|:-:|:-:|:-:| |Automatically compile and quantize YOLOv5 for better inference performance in one click at [Deci](https://bit.ly/yolov5-deci-platform)|Automatically track, visualize and even remotely train YOLOv5 using [ClearML](https://cutt.ly/yolov5-readme-clearml) (open-source!)|Label and export your custom datasets directly to YOLOv5 for training with [Roboflow](https://roboflow.com/?ref=ultralytics) |Automatically track and visualize all your YOLOv5 training runs in the cloud with [Weights & Biases](https://wandb.ai/site?utm_campaign=repo_yolo_readme) - ##
Why YOLOv5
From 105184cad8c9ec4db50137987243fed303466b33 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 02:52:47 +0200 Subject: [PATCH 341/349] Update README --- .github/README_cn.md | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/.github/README_cn.md b/.github/README_cn.md index 7b56462905d7..2d32327c90fd 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -239,6 +239,82 @@ We are super excited about our first-ever Ultralytics YOLOv5 🚀 EXPORT Competi
+ +##
Classification ⭐ NEW
+ +YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings support for classification model training, validation, prediction and export! We've made training classifier models super simple. Click below to get started. + +
+ Classification Checkpoints (click to expand) + +
+ +We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. + +| Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | +|----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| +| [YOLOv5n-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5n-cls.pt) | 224 | 64.6 | 85.4 | 7:59 | **3.3** | **0.5** | **2.5** | **0.5** | +| [YOLOv5s-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s-cls.pt) | 224 | 71.5 | 90.2 | 8:09 | 6.6 | 0.6 | 5.4 | 1.4 | +| [YOLOv5m-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5m-cls.pt) | 224 | 75.9 | 92.9 | 10:06 | 15.5 | 0.9 | 12.9 | 3.9 | +| [YOLOv5l-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5l-cls.pt) | 224 | 78.0 | 94.0 | 11:56 | 26.9 | 1.4 | 26.5 | 8.5 | +| [YOLOv5x-cls](https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5x-cls.pt) | 224 | **79.0** | **94.4** | 15:04 | 54.3 | 1.8 | 48.1 | 15.9 | +| | +| [ResNet18](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet18.pt) | 224 | 70.3 | 89.5 | **6:47** | 11.2 | 0.5 | 11.7 | 3.7 | +| [ResNet34](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet34.pt) | 224 | 73.9 | 91.8 | 8:33 | 20.6 | 0.9 | 21.8 | 7.4 | +| [ResNet50](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet50.pt) | 224 | 76.8 | 93.4 | 11:10 | 23.4 | 1.0 | 25.6 | 8.5 | +| [ResNet101](https://github.com/ultralytics/yolov5/releases/download/v6.2/resnet101.pt) | 224 | 78.5 | 94.3 | 17:10 | 42.1 | 1.9 | 44.5 | 15.9 | +| | +| [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | +| [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | +| [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | + +
+ Table Notes (click to expand) + +- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. +- **Accuracy** values are for single-model single-scale on [ImageNet-1k](https://www.image-net.org/index.php) dataset.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224` +- **Speed** averaged over 100 inference images using a Google [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` +- **Export** to ONNX at FP32 and TensorRT at FP16 done with `export.py`.
Reproduce by `python export.py --weights yolov5s-cls.pt --include engine onnx --imgsz 224` +
+
+ +
+ Classification Usage Examples (click to expand) + +### Train +YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets. + +```bash +# Single-GPU +python classify/train.py --model yolov5s-cls.pt --data cifar100 --epochs 5 --img 224 --batch 128 + +# Multi-GPU DDP +python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/train.py --model yolov5s-cls.pt --data imagenet --epochs 5 --img 224 --device 0,1,2,3 +``` + +### Val +```bash +python classify/val.py --weights yolov5s-cls.pt --data ../datasets/imagenet --img 224 +``` + +### Predict +```bash +python classify/predict.py --weights yolov5s-cls.pt --data data/images/bus.jpg +``` +```python +model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load from PyTorch Hub +``` + +### Export +```bash +python export.py --weights yolov5s-cls.pt --include onnx +python export.py --weights resnet50.pt --include onnx +python export.py --weights efficientnet_b0.pt --include onnx +``` +
+ + ##
贡献
我们重视您的意见! 我们希望给大家提供尽可能的简单和透明的方式对 YOLOv5 做出贡献。开始之前请先点击并查看我们的 [贡献指南](CONTRIBUTING.md),填写[YOLOv5调查问卷](https://ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) 来向我们发送您的经验反馈。真诚感谢我们所有的贡献者! From 47bbaeef64084e889103d2a6a8a991a09d333891 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 02:57:18 +0200 Subject: [PATCH 342/349] Update README --- .github/README_cn.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 2d32327c90fd..87d1c3468393 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -171,26 +171,23 @@ python train.py --data coco.yaml --cfg yolov5n.yaml --weights '' --batch-size 12 ##
如何与第三方集成
-|Weights and Biases|Roboflow ⭐ 新| -|:-:|:-:| -|通过 [Weights & Biases](https://wandb.ai/site?utm_campaign=repo_yolo_readme) 自动跟踪和可视化你在云端的所有YOLOv5训练运行状态。|标记并将您的自定义数据集直接导出到YOLOv5,以便用 [Roboflow](https://roboflow.com/?ref=ultralytics) 进行训练。 | - - ##
为什么选择 YOLOv5
From 47196852464168a60c13d122f84dcab3a5d4b5d5 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:14:51 +0200 Subject: [PATCH 343/349] Update README --- .github/README_cn.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 87d1c3468393..574a0dab733f 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -264,7 +264,7 @@ We trained classification versions of the 5 base YOLOv5 models on ImageNet for 9 | [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | | [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | | [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | -| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 77.7 | 94.0 | 19:19 | 18.9 | 1.9 | 12.2 | 2.4 |
Table Notes (click to expand) diff --git a/README.md b/README.md index e2b0588bb08a..322d6cab6228 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ We trained classification versions of the 5 base YOLOv5 models on ImageNet for 9 | [EfficientNet_b0](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b0.pt) | 224 | 75.1 | 92.4 | 13:03 | 12.5 | 1.3 | 5.3 | 1.0 | | [EfficientNet_b1](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b1.pt) | 224 | 76.4 | 93.2 | 17:04 | 14.9 | 1.6 | 7.8 | 1.5 | | [EfficientNet_b2](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b2.pt) | 224 | 76.6 | 93.4 | 17:10 | 15.9 | 1.6 | 9.1 | 1.7 | -| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 75.1 | 92.4 | 13:03 | 31.8 | 11.2 | 5.3 | 0.8 | +| [EfficientNet_b3](https://github.com/ultralytics/yolov5/releases/download/v6.2/efficientnet_b3.pt) | 224 | 77.7 | 94.0 | 19:19 | 18.9 | 1.9 | 12.2 | 2.4 |
Table Notes (click to expand) From 55d85a6cfcd99ad643d28cc13a5209978bb351a2 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:33:51 +0200 Subject: [PATCH 344/349] Update README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 322d6cab6228..0fb565582d61 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ We trained classification versions of the 5 base YOLOv5 models on ImageNet for 9 Classification Usage Examples (click to expand) ### Train -YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets. +YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets with the `--data` argument. To start training on MNIST for example use `--data mnist`. ```bash # Single-GPU @@ -300,11 +300,14 @@ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/trai ``` ### Val +Validate accuracy on a pretrained model. To validate YOLOv5s-cls accuracy on ImageNet. ```bash +bash data/scripts/get_imagenet.sh --val # download ImageNet val split (6.3G, 50000 images) python classify/val.py --weights yolov5s-cls.pt --data ../datasets/imagenet --img 224 ``` ### Predict +Run a classification prediction on an image. ```bash python classify/predict.py --weights yolov5s-cls.pt --data data/images/bus.jpg ``` @@ -313,10 +316,9 @@ model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load ``` ### Export +Export a group of trained YOLOv5-cls, ResNet and EfficientNet models to ONNX and TensorRT. ```bash -python export.py --weights yolov5s-cls.pt --include onnx -python export.py --weights resnet50.pt --include onnx -python export.py --weights efficientnet_b0.pt --include onnx +python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine ```
From e8ce124434592a585376e2b3e660e30fa9ccec8d Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:34:25 +0200 Subject: [PATCH 345/349] Update README --- .github/README_cn.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 574a0dab733f..220cdbfd206b 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -280,7 +280,7 @@ We trained classification versions of the 5 base YOLOv5 models on ImageNet for 9 Classification Usage Examples (click to expand) ### Train -YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets. +YOLOv5 classification training supports auto-download of MNIST, Fashion-MNIST, CIFAR10, CIFAR100, Imagenette, Imagewoof, and ImageNet datasets with the `--data` argument. To start training on MNIST for example use `--data mnist`. ```bash # Single-GPU @@ -291,11 +291,14 @@ python -m torch.distributed.run --nproc_per_node 4 --master_port 1 classify/trai ``` ### Val +Validate accuracy on a pretrained model. To validate YOLOv5s-cls accuracy on ImageNet. ```bash +bash data/scripts/get_imagenet.sh --val # download ImageNet val split (6.3G, 50000 images) python classify/val.py --weights yolov5s-cls.pt --data ../datasets/imagenet --img 224 ``` ### Predict +Run a classification prediction on an image. ```bash python classify/predict.py --weights yolov5s-cls.pt --data data/images/bus.jpg ``` @@ -304,10 +307,9 @@ model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load ``` ### Export +Export a group of trained YOLOv5-cls, ResNet and EfficientNet models to ONNX and TensorRT. ```bash -python export.py --weights yolov5s-cls.pt --include onnx -python export.py --weights resnet50.pt --include onnx -python export.py --weights efficientnet_b0.pt --include onnx +python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine ```
From 78e1bf940b95465a33db80e0f40f72a42ccda3d6 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:35:26 +0200 Subject: [PATCH 346/349] Update README --- .github/README_cn.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 220cdbfd206b..72b1ebd6302e 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -309,7 +309,7 @@ model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load ### Export Export a group of trained YOLOv5-cls, ResNet and EfficientNet models to ONNX and TensorRT. ```bash -python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine +python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine --img 224 ```
diff --git a/README.md b/README.md index 0fb565582d61..0c0a342ad960 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s-cls.pt') # load ### Export Export a group of trained YOLOv5-cls, ResNet and EfficientNet models to ONNX and TensorRT. ```bash -python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine +python export.py --weights yolov5s-cls.pt resnet50.pt efficientnet_b0.pt --include onnx engine --img 224 ```
From d0a3e7088e5325734b5ee89980f88b21e5ffa240 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:38:38 +0200 Subject: [PATCH 347/349] Update README --- .github/README_cn.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 72b1ebd6302e..245a72db0783 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -246,7 +246,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup
-We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. +We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instnace, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| diff --git a/README.md b/README.md index 0c0a342ad960..29783a51dccf 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup
-We trained classification versions of the 5 base YOLOv5 models on ImageNet for 90 epochs, and we trained comparison ResNet and EfficientNet models to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. +We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instnace, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| From 4b398dfc7c8b7e3af80a18d8ae9699c55d8c1c83 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:39:39 +0200 Subject: [PATCH 348/349] Update README --- .github/README_cn.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 245a72db0783..2c20a480829b 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -246,7 +246,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup
-We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instnace, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. +We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instance, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| diff --git a/README.md b/README.md index 29783a51dccf..43ff67d55026 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ YOLOv5 [release v6.2](https://github.com/ultralytics/yolov5/releases) brings sup
-We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instnace, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. +We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4xA100 instance, and we trained ResNet and EfficientNet models alongside with the same default training settings to compare. We exported all models to ONNX FP32 for CPU speed tests and to TensorRT FP16 for GPU speed tests. We ran all speed tests on Google [Colab Pro](https://colab.research.google.com/signup) for easy reproducibility. | Model | size
(pixels) | acc
top1 | acc
top5 | Training
90 epochs
4xA100 (hours) | Speed
ONNX CPU
(ms) | Speed
TensorRT V100
(ms) | params
(M) | FLOPs
@224 (B) | |----------------------------------------------------------------------------------------------------|-----------------------|------------------|------------------|----------------------------------------------|--------------------------------|-------------------------------------|--------------------|------------------------| From 9a43979c4582aed032d8c091876e43f5e9f5ce16 Mon Sep 17 00:00:00 2001 From: glennjocher Date: Wed, 17 Aug 2022 11:57:30 +0200 Subject: [PATCH 349/349] Update README --- .github/README_cn.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/README_cn.md b/.github/README_cn.md index 2c20a480829b..86b502df61f7 100644 --- a/.github/README_cn.md +++ b/.github/README_cn.md @@ -269,7 +269,7 @@ We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4x
Table Notes (click to expand) -- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. +- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. Runs logged to https://wandb.ai/glenn-jocher/YOLOv5-Classifier-v6-2. - **Accuracy** values are for single-model single-scale on [ImageNet-1k](https://www.image-net.org/index.php) dataset.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224` - **Speed** averaged over 100 inference images using a Google [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` - **Export** to ONNX at FP32 and TensorRT at FP16 done with `export.py`.
Reproduce by `python export.py --weights yolov5s-cls.pt --include engine onnx --imgsz 224` diff --git a/README.md b/README.md index 43ff67d55026..b368d1d6e264 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ We trained YOLOv5-cls classification models on ImageNet for 90 epochs using a 4x
Table Notes (click to expand) -- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. +- All checkpoints are trained to 90 epochs with SGD optimizer with lr0=0.001 at image size 224 and all default settings. Runs logged to https://wandb.ai/glenn-jocher/YOLOv5-Classifier-v6-2. - **Accuracy** values are for single-model single-scale on [ImageNet-1k](https://www.image-net.org/index.php) dataset.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224` - **Speed** averaged over 100 inference images using a Google [Colab Pro](https://colab.research.google.com/signup) V100 High-RAM instance.
Reproduce by `python classify/val.py --data ../datasets/imagenet --img 224 --batch 1` - **Export** to ONNX at FP32 and TensorRT at FP16 done with `export.py`.
Reproduce by `python export.py --weights yolov5s-cls.pt --include engine onnx --imgsz 224`