Skip to content

Commit 6db46e5

Browse files
authored
Multi gpu (yhenon#6)
* support for multi gpu training * updated training script * working multi-gpu * small bug fix * removed unneeded print * removed commented out code * now computes mAP on CSV datasets * Implement OID dataset generator
1 parent 7615075 commit 6db46e5

File tree

7 files changed

+655
-143
lines changed

7 files changed

+655
-143
lines changed

coco_eval.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,32 @@ def evaluate_coco(dataset, model, threshold=0.05):
3232
# correct boxes for image scale
3333
boxes /= scale
3434

35-
# change to (x, y, w, h) (MS COCO standard)
36-
boxes[:, 2] -= boxes[:, 0]
37-
boxes[:, 3] -= boxes[:, 1]
38-
39-
# compute predicted labels and scores
40-
#for box, score, label in zip(boxes[0], scores[0], labels[0]):
41-
for box_id in range(boxes.shape[0]):
42-
score = float(scores[box_id])
43-
label = int(labels[box_id])
44-
box = boxes[box_id, :]
45-
46-
# scores are sorted, so we can break
47-
if score < threshold:
48-
break
49-
50-
# append detection for each positively labeled class
51-
image_result = {
52-
'image_id' : dataset.image_ids[index],
53-
'category_id' : dataset.label_to_coco_label(label),
54-
'score' : float(score),
55-
'bbox' : box.tolist(),
56-
}
57-
58-
# append detection to results
59-
results.append(image_result)
35+
if boxes.shape[0] > 0:
36+
# change to (x, y, w, h) (MS COCO standard)
37+
boxes[:, 2] -= boxes[:, 0]
38+
boxes[:, 3] -= boxes[:, 1]
39+
40+
# compute predicted labels and scores
41+
#for box, score, label in zip(boxes[0], scores[0], labels[0]):
42+
for box_id in range(boxes.shape[0]):
43+
score = float(scores[box_id])
44+
label = int(labels[box_id])
45+
box = boxes[box_id, :]
46+
47+
# scores are sorted, so we can break
48+
if score < threshold:
49+
break
50+
51+
# append detection for each positively labeled class
52+
image_result = {
53+
'image_id' : dataset.image_ids[index],
54+
'category_id' : dataset.label_to_coco_label(label),
55+
'score' : float(score),
56+
'bbox' : box.tolist(),
57+
}
58+
59+
# append detection to results
60+
results.append(image_result)
6061

6162
# append image to list of processed images
6263
image_ids.append(dataset.image_ids[index])

csv_eval.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
from __future__ import print_function
2+
3+
import numpy as np
4+
import json
5+
import os
6+
7+
import torch
8+
9+
10+
11+
def compute_overlap(a, b):
12+
"""
13+
Parameters
14+
----------
15+
a: (N, 4) ndarray of float
16+
b: (K, 4) ndarray of float
17+
Returns
18+
-------
19+
overlaps: (N, K) ndarray of overlap between boxes and query_boxes
20+
"""
21+
area = (b[:, 2] - b[:, 0]) * (b[:, 3] - b[:, 1])
22+
23+
iw = np.minimum(np.expand_dims(a[:, 2], axis=1), b[:, 2]) - np.maximum(np.expand_dims(a[:, 0], 1), b[:, 0])
24+
ih = np.minimum(np.expand_dims(a[:, 3], axis=1), b[:, 3]) - np.maximum(np.expand_dims(a[:, 1], 1), b[:, 1])
25+
26+
iw = np.maximum(iw, 0)
27+
ih = np.maximum(ih, 0)
28+
29+
ua = np.expand_dims((a[:, 2] - a[:, 0]) * (a[:, 3] - a[:, 1]), axis=1) + area - iw * ih
30+
31+
ua = np.maximum(ua, np.finfo(float).eps)
32+
33+
intersection = iw * ih
34+
35+
return intersection / ua
36+
37+
38+
def _compute_ap(recall, precision):
39+
""" Compute the average precision, given the recall and precision curves.
40+
Code originally from https://github.com/rbgirshick/py-faster-rcnn.
41+
# Arguments
42+
recall: The recall curve (list).
43+
precision: The precision curve (list).
44+
# Returns
45+
The average precision as computed in py-faster-rcnn.
46+
"""
47+
# correct AP calculation
48+
# first append sentinel values at the end
49+
mrec = np.concatenate(([0.], recall, [1.]))
50+
mpre = np.concatenate(([0.], precision, [0.]))
51+
52+
# compute the precision envelope
53+
for i in range(mpre.size - 1, 0, -1):
54+
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
55+
56+
# to calculate area under PR curve, look for points
57+
# where X axis (recall) changes value
58+
i = np.where(mrec[1:] != mrec[:-1])[0]
59+
60+
# and sum (\Delta recall) * prec
61+
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
62+
return ap
63+
64+
65+
def _get_detections(dataset, retinanet, score_threshold=0.05, max_detections=100, save_path=None):
66+
""" Get the detections from the retinanet using the generator.
67+
The result is a list of lists such that the size is:
68+
all_detections[num_images][num_classes] = detections[num_detections, 4 + num_classes]
69+
# Arguments
70+
dataset : The generator used to run images through the retinanet.
71+
retinanet : The retinanet to run on the images.
72+
score_threshold : The score confidence threshold to use.
73+
max_detections : The maximum number of detections to use per image.
74+
save_path : The path to save the images with visualized detections to.
75+
# Returns
76+
A list of lists containing the detections for each image in the generator.
77+
"""
78+
all_detections = [[None for i in range(dataset.num_classes())] for j in range(len(dataset))]
79+
80+
retinanet.eval()
81+
82+
with torch.no_grad():
83+
84+
for index in range(len(dataset)):
85+
data = dataset[index]
86+
scale = data['scale']
87+
88+
# run network
89+
scores, labels, boxes = retinanet(data['img'].permute(2, 0, 1).cuda().float().unsqueeze(dim=0))
90+
scores = scores.cpu().numpy()
91+
labels = labels.cpu().numpy()
92+
boxes = boxes.cpu().numpy()
93+
94+
# correct boxes for image scale
95+
boxes /= scale
96+
97+
# select indices which have a score above the threshold
98+
indices = np.where(scores > score_threshold)[0]
99+
if indices.shape[0] > 0:
100+
# select those scores
101+
scores = scores[indices]
102+
103+
# find the order with which to sort the scores
104+
scores_sort = np.argsort(-scores)[:max_detections]
105+
106+
# select detections
107+
image_boxes = boxes[indices[scores_sort], :]
108+
image_scores = scores[scores_sort]
109+
image_labels = labels[indices[scores_sort]]
110+
image_detections = np.concatenate([image_boxes, np.expand_dims(image_scores, axis=1), np.expand_dims(image_labels, axis=1)], axis=1)
111+
112+
# copy detections to all_detections
113+
for label in range(dataset.num_classes()):
114+
all_detections[index][label] = image_detections[image_detections[:, -1] == label, :-1]
115+
else:
116+
# copy detections to all_detections
117+
for label in range(dataset.num_classes()):
118+
all_detections[index][label] = np.zeros((0, 5))
119+
120+
print('{}/{}'.format(index + 1, len(dataset)), end='\r')
121+
122+
return all_detections
123+
124+
125+
def _get_annotations(generator):
126+
""" Get the ground truth annotations from the generator.
127+
The result is a list of lists such that the size is:
128+
all_detections[num_images][num_classes] = annotations[num_detections, 5]
129+
# Arguments
130+
generator : The generator used to retrieve ground truth annotations.
131+
# Returns
132+
A list of lists containing the annotations for each image in the generator.
133+
"""
134+
all_annotations = [[None for i in range(generator.num_classes())] for j in range(len(generator))]
135+
136+
for i in range(len(generator)):
137+
# load the annotations
138+
annotations = generator.load_annotations(i)
139+
140+
# copy detections to all_annotations
141+
for label in range(generator.num_classes()):
142+
all_annotations[i][label] = annotations[annotations[:, 4] == label, :4].copy()
143+
144+
print('{}/{}'.format(i + 1, len(generator)), end='\r')
145+
146+
return all_annotations
147+
148+
149+
def evaluate(
150+
generator,
151+
retinanet,
152+
iou_threshold=0.5,
153+
score_threshold=0.05,
154+
max_detections=100,
155+
save_path=None
156+
):
157+
""" Evaluate a given dataset using a given retinanet.
158+
# Arguments
159+
generator : The generator that represents the dataset to evaluate.
160+
retinanet : The retinanet to evaluate.
161+
iou_threshold : The threshold used to consider when a detection is positive or negative.
162+
score_threshold : The score confidence threshold to use for detections.
163+
max_detections : The maximum number of detections to use per image.
164+
save_path : The path to save images with visualized detections to.
165+
# Returns
166+
A dict mapping class names to mAP scores.
167+
"""
168+
169+
170+
171+
# gather all detections and annotations
172+
173+
all_detections = _get_detections(generator, retinanet, score_threshold=score_threshold, max_detections=max_detections, save_path=save_path)
174+
all_annotations = _get_annotations(generator)
175+
176+
average_precisions = {}
177+
178+
for label in range(generator.num_classes()):
179+
false_positives = np.zeros((0,))
180+
true_positives = np.zeros((0,))
181+
scores = np.zeros((0,))
182+
num_annotations = 0.0
183+
184+
for i in range(len(generator)):
185+
detections = all_detections[i][label]
186+
annotations = all_annotations[i][label]
187+
num_annotations += annotations.shape[0]
188+
detected_annotations = []
189+
190+
for d in detections:
191+
scores = np.append(scores, d[4])
192+
193+
if annotations.shape[0] == 0:
194+
false_positives = np.append(false_positives, 1)
195+
true_positives = np.append(true_positives, 0)
196+
continue
197+
198+
overlaps = compute_overlap(np.expand_dims(d, axis=0), annotations)
199+
assigned_annotation = np.argmax(overlaps, axis=1)
200+
max_overlap = overlaps[0, assigned_annotation]
201+
202+
if max_overlap >= iou_threshold and assigned_annotation not in detected_annotations:
203+
false_positives = np.append(false_positives, 0)
204+
true_positives = np.append(true_positives, 1)
205+
detected_annotations.append(assigned_annotation)
206+
else:
207+
false_positives = np.append(false_positives, 1)
208+
true_positives = np.append(true_positives, 0)
209+
210+
# no annotations -> AP for this class is 0 (is this correct?)
211+
if num_annotations == 0:
212+
average_precisions[label] = 0, 0
213+
continue
214+
215+
# sort by score
216+
indices = np.argsort(-scores)
217+
false_positives = false_positives[indices]
218+
true_positives = true_positives[indices]
219+
220+
# compute false positives and true positives
221+
false_positives = np.cumsum(false_positives)
222+
true_positives = np.cumsum(true_positives)
223+
224+
# compute recall and precision
225+
recall = true_positives / num_annotations
226+
precision = true_positives / np.maximum(true_positives + false_positives, np.finfo(np.float64).eps)
227+
228+
# compute average precision
229+
average_precision = _compute_ap(recall, precision)
230+
average_precisions[label] = average_precision, num_annotations
231+
232+
print('\nmAP:')
233+
for label in range(generator.num_classes()):
234+
label_name = generator.label_to_name(label)
235+
print('{}: {}'.format(label_name, average_precisions[label][0]))
236+
237+
return average_precisions
238+

dataloader.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,19 @@ def collater(data):
323323
img = imgs[i]
324324
padded_imgs[i, :int(img.shape[0]), :int(img.shape[1]), :] = img
325325

326+
max_num_annots = max(annot.shape[0] for annot in annots)
327+
328+
annot_padded = torch.ones((len(annots), max_num_annots, 5)) * -1
329+
#print(annot_padded.shape)
330+
if max_num_annots > 0:
331+
for idx, annot in enumerate(annots):
332+
#print(annot.shape)
333+
if annot.shape[0] > 0:
334+
annot_padded[idx, :annot.shape[0], :] = annot
335+
326336
padded_imgs = padded_imgs.permute(0, 3, 1, 2)
327337

328-
return {'img': padded_imgs, 'annot': annots, 'scale': scales}
338+
return {'img': padded_imgs, 'annot': annot_padded, 'scale': scales}
329339

330340
class Resizer(object):
331341
"""Convert ndarrays in sample to Tensors."""

0 commit comments

Comments
 (0)