AutoAnchor and AutoBatch `LOGGER` (#5635)
Browse files* AutoBatch, AutoAnchor `LOGGER`
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Update autoanchor.py
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
- utils/autoanchor.py +26 -24
- utils/autobatch.py +7 -7
- utils/plots.py +3 -3
utils/autoanchor.py
CHANGED
@@ -10,7 +10,9 @@ import torch
|
|
10 |
import yaml
|
11 |
from tqdm import tqdm
|
12 |
|
13 |
-
from utils.general import colorstr
|
|
|
|
|
14 |
|
15 |
|
16 |
def check_anchor_order(m):
|
@@ -19,14 +21,12 @@ def check_anchor_order(m):
|
|
19 |
da = a[-1] - a[0] # delta a
|
20 |
ds = m.stride[-1] - m.stride[0] # delta s
|
21 |
if da.sign() != ds.sign(): # same order
|
22 |
-
|
23 |
m.anchors[:] = m.anchors.flip(0)
|
24 |
|
25 |
|
26 |
def check_anchors(dataset, model, thr=4.0, imgsz=640):
|
27 |
# Check anchor fit to data, recompute if necessary
|
28 |
-
prefix = colorstr('autoanchor: ')
|
29 |
-
print(f'\n{prefix}Analyzing anchors... ', end='')
|
30 |
m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
|
31 |
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
|
32 |
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
|
@@ -42,23 +42,24 @@ def check_anchors(dataset, model, thr=4.0, imgsz=640):
|
|
42 |
|
43 |
anchors = m.anchors.clone() * m.stride.to(m.anchors.device).view(-1, 1, 1) # current anchors
|
44 |
bpr, aat = metric(anchors.cpu().view(-1, 2))
|
45 |
-
|
46 |
-
if bpr
|
47 |
-
|
|
|
|
|
48 |
na = m.anchors.numel() // 2 # number of anchors
|
49 |
try:
|
50 |
anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
|
51 |
except Exception as e:
|
52 |
-
|
53 |
new_bpr = metric(anchors)[0]
|
54 |
if new_bpr > bpr: # replace anchors
|
55 |
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
|
56 |
m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
|
57 |
check_anchor_order(m)
|
58 |
-
|
59 |
else:
|
60 |
-
|
61 |
-
print('') # newline
|
62 |
|
63 |
|
64 |
def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
|
@@ -81,7 +82,6 @@ def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen
|
|
81 |
from scipy.cluster.vq import kmeans
|
82 |
|
83 |
thr = 1 / thr
|
84 |
-
prefix = colorstr('autoanchor: ')
|
85 |
|
86 |
def metric(k, wh): # compute metrics
|
87 |
r = wh[:, None] / k[None]
|
@@ -93,15 +93,17 @@ def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen
|
|
93 |
_, best = metric(torch.tensor(k, dtype=torch.float32), wh)
|
94 |
return (best * (best > thr).float()).mean() # fitness
|
95 |
|
96 |
-
def print_results(k):
|
97 |
k = k[np.argsort(k.prod(1))] # sort small to large
|
98 |
x, best = metric(k, wh0)
|
99 |
bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
for i, x in enumerate(k):
|
104 |
-
|
|
|
|
|
105 |
return k
|
106 |
|
107 |
if isinstance(dataset, str): # *.yaml file
|
@@ -117,19 +119,19 @@ def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen
|
|
117 |
# Filter
|
118 |
i = (wh0 < 3.0).any(1).sum()
|
119 |
if i:
|
120 |
-
|
121 |
wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
|
122 |
# wh = wh * (np.random.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1
|
123 |
|
124 |
# Kmeans calculation
|
125 |
-
|
126 |
s = wh.std(0) # sigmas for whitening
|
127 |
k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
|
128 |
-
assert len(k) == n, f'{
|
129 |
k *= s
|
130 |
wh = torch.tensor(wh, dtype=torch.float32) # filtered
|
131 |
wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
|
132 |
-
k = print_results(k)
|
133 |
|
134 |
# Plot
|
135 |
# k, d = [None] * 20, [None] * 20
|
@@ -146,7 +148,7 @@ def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen
|
|
146 |
# Evolve
|
147 |
npr = np.random
|
148 |
f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
|
149 |
-
pbar = tqdm(range(gen), desc=f'{
|
150 |
for _ in pbar:
|
151 |
v = np.ones(sh)
|
152 |
while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
|
@@ -155,8 +157,8 @@ def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen
|
|
155 |
fg = anchor_fitness(kg)
|
156 |
if fg > f:
|
157 |
f, k = fg, kg.copy()
|
158 |
-
pbar.desc = f'{
|
159 |
if verbose:
|
160 |
-
print_results(k)
|
161 |
|
162 |
return print_results(k)
|
|
|
10 |
import yaml
|
11 |
from tqdm import tqdm
|
12 |
|
13 |
+
from utils.general import LOGGER, colorstr, emojis
|
14 |
+
|
15 |
+
PREFIX = colorstr('AutoAnchor: ')
|
16 |
|
17 |
|
18 |
def check_anchor_order(m):
|
|
|
21 |
da = a[-1] - a[0] # delta a
|
22 |
ds = m.stride[-1] - m.stride[0] # delta s
|
23 |
if da.sign() != ds.sign(): # same order
|
24 |
+
LOGGER.info(f'{PREFIX}Reversing anchor order')
|
25 |
m.anchors[:] = m.anchors.flip(0)
|
26 |
|
27 |
|
28 |
def check_anchors(dataset, model, thr=4.0, imgsz=640):
|
29 |
# Check anchor fit to data, recompute if necessary
|
|
|
|
|
30 |
m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
|
31 |
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
|
32 |
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
|
|
|
42 |
|
43 |
anchors = m.anchors.clone() * m.stride.to(m.anchors.device).view(-1, 1, 1) # current anchors
|
44 |
bpr, aat = metric(anchors.cpu().view(-1, 2))
|
45 |
+
s = f'\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). '
|
46 |
+
if bpr > 0.98: # threshold to recompute
|
47 |
+
LOGGER.info(emojis(f'{s}Current anchors are a good fit to dataset ✅'))
|
48 |
+
else:
|
49 |
+
LOGGER.info(emojis(f'{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...'))
|
50 |
na = m.anchors.numel() // 2 # number of anchors
|
51 |
try:
|
52 |
anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
|
53 |
except Exception as e:
|
54 |
+
LOGGER.info(f'{PREFIX}ERROR: {e}')
|
55 |
new_bpr = metric(anchors)[0]
|
56 |
if new_bpr > bpr: # replace anchors
|
57 |
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
|
58 |
m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
|
59 |
check_anchor_order(m)
|
60 |
+
LOGGER.info(f'{PREFIX}New anchors saved to model. Update model *.yaml to use these anchors in the future.')
|
61 |
else:
|
62 |
+
LOGGER.info(f'{PREFIX}Original anchors better than new anchors. Proceeding with original anchors.')
|
|
|
63 |
|
64 |
|
65 |
def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
|
|
|
82 |
from scipy.cluster.vq import kmeans
|
83 |
|
84 |
thr = 1 / thr
|
|
|
85 |
|
86 |
def metric(k, wh): # compute metrics
|
87 |
r = wh[:, None] / k[None]
|
|
|
93 |
_, best = metric(torch.tensor(k, dtype=torch.float32), wh)
|
94 |
return (best * (best > thr).float()).mean() # fitness
|
95 |
|
96 |
+
def print_results(k, verbose=True):
|
97 |
k = k[np.argsort(k.prod(1))] # sort small to large
|
98 |
x, best = metric(k, wh0)
|
99 |
bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
|
100 |
+
s = f'{PREFIX}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr\n' \
|
101 |
+
f'{PREFIX}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, ' \
|
102 |
+
f'past_thr={x[x > thr].mean():.3f}-mean: '
|
103 |
for i, x in enumerate(k):
|
104 |
+
s += '%i,%i, ' % (round(x[0]), round(x[1]))
|
105 |
+
if verbose:
|
106 |
+
LOGGER.info(s[:-2])
|
107 |
return k
|
108 |
|
109 |
if isinstance(dataset, str): # *.yaml file
|
|
|
119 |
# Filter
|
120 |
i = (wh0 < 3.0).any(1).sum()
|
121 |
if i:
|
122 |
+
LOGGER.info(f'{PREFIX}WARNING: Extremely small objects found. {i} of {len(wh0)} labels are < 3 pixels in size.')
|
123 |
wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
|
124 |
# wh = wh * (np.random.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1
|
125 |
|
126 |
# Kmeans calculation
|
127 |
+
LOGGER.info(f'{PREFIX}Running kmeans for {n} anchors on {len(wh)} points...')
|
128 |
s = wh.std(0) # sigmas for whitening
|
129 |
k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
|
130 |
+
assert len(k) == n, f'{PREFIX}ERROR: scipy.cluster.vq.kmeans requested {n} points but returned only {len(k)}'
|
131 |
k *= s
|
132 |
wh = torch.tensor(wh, dtype=torch.float32) # filtered
|
133 |
wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
|
134 |
+
k = print_results(k, verbose=False)
|
135 |
|
136 |
# Plot
|
137 |
# k, d = [None] * 20, [None] * 20
|
|
|
148 |
# Evolve
|
149 |
npr = np.random
|
150 |
f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
|
151 |
+
pbar = tqdm(range(gen), desc=f'{PREFIX}Evolving anchors with Genetic Algorithm:') # progress bar
|
152 |
for _ in pbar:
|
153 |
v = np.ones(sh)
|
154 |
while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
|
|
|
157 |
fg = anchor_fitness(kg)
|
158 |
if fg > f:
|
159 |
f, k = fg, kg.copy()
|
160 |
+
pbar.desc = f'{PREFIX}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}'
|
161 |
if verbose:
|
162 |
+
print_results(k, verbose)
|
163 |
|
164 |
return print_results(k)
|
utils/autobatch.py
CHANGED
@@ -9,7 +9,7 @@ import numpy as np
|
|
9 |
import torch
|
10 |
from torch.cuda import amp
|
11 |
|
12 |
-
from utils.general import colorstr
|
13 |
from utils.torch_utils import profile
|
14 |
|
15 |
|
@@ -27,11 +27,11 @@ def autobatch(model, imgsz=640, fraction=0.9, batch_size=16):
|
|
27 |
# model = torch.hub.load('ultralytics/yolov5', 'yolov5s', autoshape=False)
|
28 |
# print(autobatch(model))
|
29 |
|
30 |
-
prefix = colorstr('
|
31 |
-
|
32 |
device = next(model.parameters()).device # get model device
|
33 |
if device.type == 'cpu':
|
34 |
-
|
35 |
return batch_size
|
36 |
|
37 |
d = str(device).upper() # 'CUDA:0'
|
@@ -40,18 +40,18 @@ def autobatch(model, imgsz=640, fraction=0.9, batch_size=16):
|
|
40 |
r = torch.cuda.memory_reserved(device) / 1024 ** 3 # (GiB)
|
41 |
a = torch.cuda.memory_allocated(device) / 1024 ** 3 # (GiB)
|
42 |
f = t - (r + a) # free inside reserved
|
43 |
-
|
44 |
|
45 |
batch_sizes = [1, 2, 4, 8, 16]
|
46 |
try:
|
47 |
img = [torch.zeros(b, 3, imgsz, imgsz) for b in batch_sizes]
|
48 |
y = profile(img, model, n=3, device=device)
|
49 |
except Exception as e:
|
50 |
-
|
51 |
|
52 |
y = [x[2] for x in y if x] # memory [2]
|
53 |
batch_sizes = batch_sizes[:len(y)]
|
54 |
p = np.polyfit(batch_sizes, y, deg=1) # first degree polynomial fit
|
55 |
b = int((f * fraction - p[1]) / p[0]) # y intercept (optimal batch size)
|
56 |
-
|
57 |
return b
|
|
|
9 |
import torch
|
10 |
from torch.cuda import amp
|
11 |
|
12 |
+
from utils.general import LOGGER, colorstr
|
13 |
from utils.torch_utils import profile
|
14 |
|
15 |
|
|
|
27 |
# model = torch.hub.load('ultralytics/yolov5', 'yolov5s', autoshape=False)
|
28 |
# print(autobatch(model))
|
29 |
|
30 |
+
prefix = colorstr('AutoBatch: ')
|
31 |
+
LOGGER.info(f'{prefix}Computing optimal batch size for --imgsz {imgsz}')
|
32 |
device = next(model.parameters()).device # get model device
|
33 |
if device.type == 'cpu':
|
34 |
+
LOGGER.info(f'{prefix}CUDA not detected, using default CPU batch-size {batch_size}')
|
35 |
return batch_size
|
36 |
|
37 |
d = str(device).upper() # 'CUDA:0'
|
|
|
40 |
r = torch.cuda.memory_reserved(device) / 1024 ** 3 # (GiB)
|
41 |
a = torch.cuda.memory_allocated(device) / 1024 ** 3 # (GiB)
|
42 |
f = t - (r + a) # free inside reserved
|
43 |
+
LOGGER.info(f'{prefix}{d} ({properties.name}) {t:.2f}G total, {r:.2f}G reserved, {a:.2f}G allocated, {f:.2f}G free')
|
44 |
|
45 |
batch_sizes = [1, 2, 4, 8, 16]
|
46 |
try:
|
47 |
img = [torch.zeros(b, 3, imgsz, imgsz) for b in batch_sizes]
|
48 |
y = profile(img, model, n=3, device=device)
|
49 |
except Exception as e:
|
50 |
+
LOGGER.warning(f'{prefix}{e}')
|
51 |
|
52 |
y = [x[2] for x in y if x] # memory [2]
|
53 |
batch_sizes = batch_sizes[:len(y)]
|
54 |
p = np.polyfit(batch_sizes, y, deg=1) # first degree polynomial fit
|
55 |
b = int((f * fraction - p[1]) / p[0]) # y intercept (optimal batch size)
|
56 |
+
LOGGER.info(f'{prefix}Using batch-size {b} for {d} {t * fraction:.2f}G/{t:.2f}G ({fraction * 100:.0f}%)')
|
57 |
return b
|
utils/plots.py
CHANGED
@@ -17,8 +17,8 @@ import seaborn as sn
|
|
17 |
import torch
|
18 |
from PIL import Image, ImageDraw, ImageFont
|
19 |
|
20 |
-
from utils.general import (Timeout, check_requirements, clip_coords, increment_path, is_ascii, is_chinese,
|
21 |
-
user_config_dir, xywh2xyxy, xyxy2xywh)
|
22 |
from utils.metrics import fitness
|
23 |
|
24 |
# Settings
|
@@ -328,7 +328,7 @@ def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_
|
|
328 |
@Timeout(30) # known issue https://github.com/ultralytics/yolov5/issues/5611
|
329 |
def plot_labels(labels, names=(), save_dir=Path('')):
|
330 |
# plot dataset labels
|
331 |
-
|
332 |
c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
|
333 |
nc = int(c.max() + 1) # number of classes
|
334 |
x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
|
|
|
17 |
import torch
|
18 |
from PIL import Image, ImageDraw, ImageFont
|
19 |
|
20 |
+
from utils.general import (LOGGER, Timeout, check_requirements, clip_coords, increment_path, is_ascii, is_chinese,
|
21 |
+
try_except, user_config_dir, xywh2xyxy, xyxy2xywh)
|
22 |
from utils.metrics import fitness
|
23 |
|
24 |
# Settings
|
|
|
328 |
@Timeout(30) # known issue https://github.com/ultralytics/yolov5/issues/5611
|
329 |
def plot_labels(labels, names=(), save_dir=Path('')):
|
330 |
# plot dataset labels
|
331 |
+
LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ")
|
332 |
c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes
|
333 |
nc = int(c.max() + 1) # number of classes
|
334 |
x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height'])
|