josedolot commited on
Commit
aff8ba8
·
1 Parent(s): c432040

Upload hybridnets/autoanchor.py

Browse files
Files changed (1) hide show
  1. hybridnets/autoanchor.py +149 -0
hybridnets/autoanchor.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Auto-anchor utils
2
+
3
+ import numpy as np
4
+ import torch
5
+ import yaml
6
+ from scipy.cluster.vq import kmeans
7
+ from tqdm import tqdm
8
+ import math
9
+
10
+
11
+ def check_anchor_order(anchors, anchor_grid, stride):
12
+ # Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary
13
+ a = anchor_grid.prod(-1).view(-1) # anchor area
14
+ da = a[-1] - a[0] # delta a
15
+ ds = stride[-1] - stride[0] # delta s
16
+ if da.sign() != ds.sign(): # same order
17
+ print('Reversing anchor order')
18
+ anchors[:] = anchors.flip(0)
19
+ anchor_grid[:] = anchor_grid.flip(0)
20
+ return anchors, anchor_grid, stride
21
+
22
+
23
+ def run_anchor(logger, dataset, thr=4.0, imgsz=640):
24
+ # default_anchors = [[3, 9, 5, 11, 4, 20], [7, 18, 6, 39, 12, 31], [19, 50, 38, 81, 68, 157]]
25
+ # nl = len(default_anchors) # number of detection layers 3
26
+ # na = len(default_anchors[0]) // 2 # number of anchors 3
27
+ # anchors = torch.tensor(default_anchors,
28
+ # device=torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
29
+ # ).float().view(nl, -1, 2)
30
+ # anchor_num = na * nl
31
+ anchor_num = 9
32
+ new_anchors = kmean_anchors(dataset, n=anchor_num, img_size=imgsz, thr=thr, gen=1000, verbose=False)
33
+
34
+ scales = [0, None, None]
35
+ scales[1] = math.log2(np.mean(new_anchors[1::3][:, 0] / new_anchors[0::3][:, 0]))
36
+ scales[2] = math.log2(np.mean(new_anchors[2::3][:, 0] / new_anchors[0::3][:, 0]))
37
+ scales = [round(2 ** x, 2) for x in scales]
38
+
39
+ normalized_anchors = new_anchors / np.sqrt(new_anchors.prod(axis=1, keepdims=True))
40
+ ratios = [(1.0, 1.0), None, None]
41
+ ratios[1] = (np.mean(normalized_anchors[:, 0]), np.mean(normalized_anchors[:, 1]))
42
+ ratios[2] = (np.mean(normalized_anchors[:, 1]), np.mean(normalized_anchors[:, 0]))
43
+ ratios = [(round(x, 2), round(y, 2)) for x, y in ratios]
44
+ print("New scales:", scales)
45
+ print("New ratios:", ratios)
46
+ print('New anchors saved to model. Update model config to use these anchors in the future.')
47
+ return str(scales), str(ratios)
48
+
49
+
50
+ def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
51
+ """ Creates kmeans-evolved anchors from training dataset
52
+
53
+ Arguments:
54
+ path: path to dataset *.yaml, or a loaded dataset
55
+ n: number of anchors
56
+ img_size: image size used for training
57
+ thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
58
+ gen: generations to evolve anchors using genetic algorithm
59
+ verbose: print all results
60
+
61
+ Return:
62
+ k: kmeans evolved anchors
63
+
64
+ Usage:
65
+ from utils.autoanchor import *; _ = kmean_anchors()
66
+ """
67
+ thr = 1. / thr
68
+
69
+ def metric(k, wh): # compute metrics
70
+ r = wh[:, None] / k[None]
71
+ x = torch.min(r, 1. / r).min(2)[0] # ratio metric
72
+ # x = wh_iou(wh, torch.tensor(k)) # iou metric
73
+ return x, x.max(1)[0] # x, best_x
74
+
75
+ def anchor_fitness(k): # mutation fitness
76
+ _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
77
+ return (best * (best > thr).float()).mean() # fitness
78
+
79
+ def print_results(k):
80
+ k = k[np.argsort(k.prod(1))] # sort small to large
81
+ x, best = metric(k, wh0)
82
+ bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
83
+ print('thr=%.2f: %.4f best possible recall, %.2f anchors past thr' % (thr, bpr, aat))
84
+ print('n=%g, img_size=%s, metric_all=%.3f/%.3f-mean/best, past_thr=%.3f-mean: ' %
85
+ (n, img_size, x.mean(), best.mean(), x[x > thr].mean()), end='')
86
+ for i, x in enumerate(k):
87
+ print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg
88
+ return k
89
+
90
+ if isinstance(path, str): # not class
91
+ raise TypeError('Dataset must be class, but found str')
92
+ else:
93
+ dataset = path # dataset
94
+
95
+ labels = [db['label'] for db in dataset.db]
96
+ labels = np.vstack(labels)
97
+ if not (labels[:, 1:] <= 1).all():
98
+ # normalize label
99
+ labels[:, [2, 4]] /= dataset.shapes[0]
100
+ labels[:, [1, 3]] /= dataset.shapes[1]
101
+ # Get label wh
102
+ shapes = img_size * dataset.shapes / dataset.shapes.max()
103
+ # wh0 = np.concatenate([l[:, 3:5] * shapes for l in labels]) # wh
104
+ wh0 = labels[:, 3:5] * shapes
105
+ # Filter
106
+ i = (wh0 < 3.0).any(1).sum()
107
+ if i:
108
+ print('WARNING: Extremely small objects found. '
109
+ '%g of %g labels are < 3 pixels in width or height.' % (i, len(wh0)))
110
+ wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
111
+
112
+ # Kmeans calculation
113
+ print('Running kmeans for %g anchors on %g points...' % (n, len(wh)))
114
+ s = wh.std(0) # sigmas for whitening
115
+ k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
116
+ k *= s
117
+ wh = torch.tensor(wh, dtype=torch.float32) # filtered
118
+ wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
119
+ k = print_results(k)
120
+
121
+ # Plot
122
+ # k, d = [None] * 20, [None] * 20
123
+ # for i in tqdm(range(1, 21)):
124
+ # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
125
+ # fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True)
126
+ # ax = ax.ravel()
127
+ # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
128
+ # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
129
+ # ax[0].hist(wh[wh[:, 0]<100, 0],400)
130
+ # ax[1].hist(wh[wh[:, 1]<100, 1],400)
131
+ # fig.savefig('wh.png', dpi=200)
132
+
133
+ # Evolve
134
+ npr = np.random
135
+ f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
136
+ pbar = tqdm(range(gen), desc='Evolving anchors with Genetic Algorithm') # progress bar
137
+ for _ in pbar:
138
+ v = np.ones(sh)
139
+ while (v == 1).all(): # mutate until a change occurs (prevent duplicates)
140
+ v = ((npr.random(sh) < mp) * npr.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
141
+ kg = (k.copy() * v).clip(min=2.0)
142
+ fg = anchor_fitness(kg)
143
+ if fg > f:
144
+ f, k = fg, kg.copy()
145
+ pbar.desc = 'Evolving anchors with Genetic Algorithm: fitness = %.4f' % f
146
+ if verbose:
147
+ print_results(k)
148
+
149
+ return print_results(k)