├── README.md └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # Repulsion-loss-yolo-pytorch 2 | 3 | This program is only available on the single class dataset. We apply it on the vehicle detection in the crowd situation and get a feasible result. For multiple classes, only modify ultis.py to compute the repulsion loss separately. 4 | 5 | The codes are based on the YOLO in Pytorch(https://github.com/ultralytics/yolov3) 6 | 7 | 8 | ## Datasets 9 | * Make own datasets by voc_label.py 10 | * Modify cfg files, voc.name and voc.data according to https://github.com/ultralytics/yolov3/wiki/Example:-Train-Single-Class 11 | 12 | ## Yolo 13 | main modifications are in the train.py and ultis.py 14 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import random 4 | import shutil 5 | from pathlib import Path 6 | 7 | import cv2 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | import numpy as np 11 | import torch 12 | import torch.nn as nn 13 | from tqdm import tqdm 14 | 15 | from . import torch_utils # , google_utils 16 | 17 | matplotlib.rc('font', **{'size': 11}) 18 | 19 | # Set printoptions 20 | torch.set_printoptions(linewidth=320, precision=5, profile='long') 21 | np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5 22 | 23 | # Prevent OpenCV from multithreading (to use PyTorch DataLoader) 24 | cv2.setNumThreads(0) 25 | 26 | 27 | def floatn(x, n=3): # format floats to n decimals 28 | return float(format(x, '.%gf' % n)) 29 | 30 | 31 | def init_seeds(seed=0): 32 | random.seed(seed) 33 | np.random.seed(seed) 34 | torch_utils.init_seeds(seed=seed) 35 | 36 | 37 | def load_classes(path): 38 | # Loads *.names file at 'path' 39 | with open(path, 'r') as f: 40 | names = f.read().split('\n') 41 | return list(filter(None, names)) # filter removes empty strings (such as last line) 42 | 43 | 44 | def labels_to_class_weights(labels, nc=80): 45 | # Get class weights (inverse frequency) from training labels 46 | ni = len(labels) # number of images 47 | labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO 48 | classes = labels[:, 0].astype(np.int) # labels = [class xywh] 49 | weights = np.bincount(classes, minlength=nc) # occurences per class 50 | 51 | # Prepend gridpoint count (for uCE trianing) 52 | gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image 53 | weights = np.hstack([gpi * ni - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start 54 | 55 | weights[weights == 0] = 1 # replace empty bins with 1 56 | weights = 1 / weights # number of targets per class 57 | weights /= weights.sum() # normalize 58 | return torch.from_numpy(weights) 59 | 60 | 61 | def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)): 62 | # Produces image weights based on class mAPs 63 | n = len(labels) 64 | class_counts = np.array([np.bincount(labels[i][:, 0].astype(np.int), minlength=nc) for i in range(n)]) 65 | image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1) 66 | # index = random.choices(range(n), weights=image_weights, k=1) # weight image sample 67 | return image_weights 68 | 69 | 70 | def coco_class_weights(): # frequency of each class in coco train2014 71 | n = [187437, 4955, 30920, 6033, 3838, 4332, 3160, 7051, 7677, 9167, 1316, 1372, 833, 6757, 7355, 3302, 3776, 4671, 72 | 6769, 5706, 3908, 903, 3686, 3596, 6200, 7920, 8779, 4505, 4272, 1862, 4698, 1962, 4403, 6659, 2402, 2689, 73 | 4012, 4175, 3411, 17048, 5637, 14553, 3923, 5539, 4289, 10084, 7018, 4314, 3099, 4638, 4939, 5543, 2038, 4004, 74 | 5053, 4578, 27292, 4113, 5931, 2905, 11174, 2873, 4036, 3415, 1517, 4122, 1980, 4464, 1190, 2302, 156, 3933, 75 | 1877, 17630, 4337, 4624, 1075, 3468, 135, 1380] 76 | weights = 1 / torch.Tensor(n) 77 | weights /= weights.sum() 78 | # with open('data/coco.names', 'r') as f: 79 | # for k, v in zip(f.read().splitlines(), n): 80 | # print('%20s: %g' % (k, v)) 81 | return weights 82 | 83 | 84 | def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper) 85 | # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/ 86 | # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n') 87 | # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n') 88 | # x = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco 89 | x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, 90 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 91 | 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90] 92 | return x 93 | 94 | 95 | def weights_init_normal(m): 96 | classname = m.__class__.__name__ 97 | if classname.find('Conv') != -1: 98 | torch.nn.init.normal_(m.weight.data, 0.0, 0.03) 99 | elif classname.find('BatchNorm2d') != -1: 100 | torch.nn.init.normal_(m.weight.data, 1.0, 0.03) 101 | torch.nn.init.constant_(m.bias.data, 0.0) 102 | 103 | 104 | def xyxy2xywh(x): 105 | # Convert bounding box format from [x1, y1, x2, y2] to [x, y, w, h] 106 | y = torch.zeros_like(x) if isinstance(x, torch.Tensor) else np.zeros_like(x) 107 | y[:, 0] = (x[:, 0] + x[:, 2]) / 2 108 | y[:, 1] = (x[:, 1] + x[:, 3]) / 2 109 | y[:, 2] = x[:, 2] - x[:, 0] 110 | y[:, 3] = x[:, 3] - x[:, 1] 111 | return y 112 | 113 | 114 | def xywh2xyxy(x): 115 | # Convert bounding box format from [x, y, w, h] to [x1, y1, x2, y2] 116 | y = torch.zeros_like(x) if isinstance(x, torch.Tensor) else np.zeros_like(x) 117 | y[:, 0] = x[:, 0] - x[:, 2] / 2 118 | y[:, 1] = x[:, 1] - x[:, 3] / 2 119 | y[:, 2] = x[:, 0] + x[:, 2] / 2 120 | y[:, 3] = x[:, 1] + x[:, 3] / 2 121 | return y 122 | 123 | 124 | def scale_coords(img1_shape, coords, img0_shape): 125 | # Rescale coords (xyxy) from img1_shape to img0_shape 126 | gain = max(img1_shape) / max(img0_shape) # gain = old / new 127 | coords[:, [0, 2]] -= (img1_shape[1] - img0_shape[1] * gain) / 2 # x padding 128 | coords[:, [1, 3]] -= (img1_shape[0] - img0_shape[0] * gain) / 2 # y padding 129 | coords[:, :4] /= gain 130 | clip_coords(coords, img0_shape) 131 | return coords 132 | 133 | 134 | def clip_coords(boxes, img_shape): 135 | # Clip bounding xyxy bounding boxes to image shape (height, width) 136 | boxes[:, [0, 2]] = boxes[:, [0, 2]].clamp(min=0, max=img_shape[1]) # clip x 137 | boxes[:, [1, 3]] = boxes[:, [1, 3]].clamp(min=0, max=img_shape[0]) # clip y 138 | 139 | 140 | def ap_per_class(tp, conf, pred_cls, target_cls): 141 | """ Compute the average precision, given the recall and precision curves. 142 | Source: https://github.com/rafaelpadilla/Object-Detection-Metrics. 143 | # Arguments 144 | tp: True positives (list). 145 | conf: Objectness value from 0-1 (list). 146 | pred_cls: Predicted object classes (list). 147 | target_cls: True object classes (list). 148 | # Returns 149 | The average precision as computed in py-faster-rcnn. 150 | """ 151 | 152 | # Sort by objectness 153 | i = np.argsort(-conf) 154 | tp, conf, pred_cls = tp[i], conf[i], pred_cls[i] 155 | 156 | # Find unique classes 157 | unique_classes = np.unique(target_cls) 158 | 159 | # Create Precision-Recall curve and compute AP for each class 160 | ap, p, r = [], [], [] 161 | for c in unique_classes: 162 | i = pred_cls == c 163 | n_gt = (target_cls == c).sum() # Number of ground truth objects 164 | n_p = i.sum() # Number of predicted objects 165 | 166 | if n_p == 0 and n_gt == 0: 167 | continue 168 | elif n_p == 0 or n_gt == 0: 169 | ap.append(0) 170 | r.append(0) 171 | p.append(0) 172 | else: 173 | # Accumulate FPs and TPs 174 | fpc = (1 - tp[i]).cumsum() 175 | tpc = (tp[i]).cumsum() 176 | 177 | # Recall 178 | recall = tpc / (n_gt + 1e-16) # recall curve 179 | r.append(recall[-1]) 180 | 181 | # Precision 182 | precision = tpc / (tpc + fpc) # precision curve 183 | p.append(precision[-1]) 184 | 185 | # AP from recall-precision curve 186 | ap.append(compute_ap(recall, precision)) 187 | 188 | # Plot 189 | # fig, ax = plt.subplots(1, 1, figsize=(4, 4)) 190 | # ax.plot(np.concatenate(([0.], recall)), np.concatenate(([0.], precision))) 191 | # ax.set_xlabel('YOLOv3-SPP') 192 | # ax.set_xlabel('Recall') 193 | # ax.set_ylabel('Precision') 194 | # ax.set_xlim(0, 1) 195 | # fig.tight_layout() 196 | # fig.savefig('PR_curve.png', dpi=300) 197 | 198 | # Compute F1 score (harmonic mean of precision and recall) 199 | p, r, ap = np.array(p), np.array(r), np.array(ap) 200 | f1 = 2 * p * r / (p + r + 1e-16) 201 | 202 | return p, r, ap, f1, unique_classes.astype('int32') 203 | 204 | 205 | def compute_ap(recall, precision): 206 | """ Compute the average precision, given the recall and precision curves. 207 | Source: https://github.com/rbgirshick/py-faster-rcnn. 208 | # Arguments 209 | recall: The recall curve (list). 210 | precision: The precision curve (list). 211 | # Returns 212 | The average precision as computed in py-faster-rcnn. 213 | """ 214 | 215 | # Append sentinel values to beginning and end 216 | mrec = np.concatenate(([0.], recall, [min(recall[-1] + 1E-3, 1.)])) 217 | mpre = np.concatenate(([0.], precision, [0.])) 218 | 219 | # Compute the precision envelope 220 | for i in range(mpre.size - 1, 0, -1): 221 | mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i]) 222 | 223 | # Integrate area under curve 224 | method = 'interp' # methods: 'continuous', 'interp' 225 | if method == 'interp': 226 | x = np.linspace(0, 1, 101) # 101-point interp (COCO) 227 | ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate 228 | else: # 'continuous' 229 | i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes 230 | ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve 231 | 232 | return ap 233 | 234 | 235 | def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False): 236 | # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4 237 | box2 = box2.t() 238 | 239 | # Get the coordinates of bounding boxes 240 | if x1y1x2y2: 241 | # x1, y1, x2, y2 = box1 242 | b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3] 243 | b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3] 244 | else: 245 | # x, y, w, h = box1 246 | b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2 247 | b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2 248 | b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2 249 | b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2 250 | 251 | # Intersection area 252 | inter_area = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \ 253 | (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0) 254 | 255 | # Union Area 256 | union_area = ((b1_x2 - b1_x1) * (b1_y2 - b1_y1) + 1e-16) + \ 257 | (b2_x2 - b2_x1) * (b2_y2 - b2_y1) - inter_area 258 | 259 | iou = inter_area / union_area # iou 260 | if GIoU: # Generalized IoU https://arxiv.org/pdf/1902.09630.pdf 261 | c_x1, c_x2 = torch.min(b1_x1, b2_x1), torch.max(b1_x2, b2_x2) 262 | c_y1, c_y2 = torch.min(b1_y1, b2_y1), torch.max(b1_y2, b2_y2) 263 | c_area = (c_x2 - c_x1) * (c_y2 - c_y1) + 1e-16 # convex area 264 | return iou - (c_area - union_area) / c_area # GIoU 265 | 266 | return iou 267 | 268 | def RepGT_iog(box1, box2, x1y1x2y2=True): 269 | box2 = box2.t() 270 | if x1y1x2y2: 271 | # x1, y1, x2, y2 = box1 272 | b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3] 273 | b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3] 274 | else: 275 | # x, y, w, h = box1 276 | b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2 277 | b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2 278 | b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2 279 | b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2 280 | inter_area = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \ 281 | (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0) 282 | g_area = torch.abs(b2_x2-b2_x1) * torch.abs(b2_y2-b2_y1) 283 | iog = inter_area/g_area 284 | return iog 285 | 286 | def RepGT_loss(box1, box2, x1y1x2y2=False): 287 | #box2 = box2.t() 288 | iog_loss = 0 289 | #print(box1.shape, box2.shape) 290 | proposal = bbox_iou(box1, box2, x1y1x2y2)>0.5 291 | #print('RepGT bbox:', box1) 292 | #print('RepGT gt:', box2.t()) 293 | for m in range(box1.size(1)): 294 | if proposal[m]: 295 | iou=bbox_iou(box1[:, m], box2, x1y1x2y2) 296 | iou[m] = 0 297 | #print(iou.shape) 298 | #print(iou) 299 | max = torch.argmax(iou) 300 | #print('max=', max) 301 | IOG = RepGT_iog(box1[:, m], box2[max.item(),:], x1y1x2y2) 302 | if IOG >0.5: 303 | iog_loss += 2*IOG-0.3 #alfa=0.5 304 | #print('iog_loss1=', iog_loss) 305 | else: 306 | IOG = 1-IOG 307 | iog_loss += -IOG.log() 308 | #print('iog_loss2=', iog_loss) 309 | 310 | if proposal.sum(): 311 | return iog_loss/proposal.sum() 312 | else: 313 | return 0 314 | 315 | def RepBox_loss(box, x1y1x2y2=False): 316 | total = 0 317 | bbox_sum = 0 318 | for m in range(box.size(1)): 319 | iou_list = bbox_iou(box[:, m], box[:, m:].t(), x1y1x2y2) 320 | print('iou_list=', m, iou_list) 321 | counter = iou_list > 0 322 | #print('iou_list=', iou_list.sum()) 323 | counter = counter.sum() - 1 324 | #print('counter=', m, counter) 325 | if counter > 0: 326 | for iou_unit in range(len(iou_list)): 327 | if iou_list[iou_unit] > 0.5: 328 | iou_list[iou_unit] = 2 * iou_list[iou_unit] - 0.3 329 | else: 330 | iou_list[iou_unit] = 1 - iou_list[iou_unit] 331 | #iou_list[iou_unit] = -torch.log(iou_list[iou_unit]) 332 | print('iou_loss1=', iou_list) 333 | bbox_sum += (iou_list.sum()-1.7) 334 | print(bbox_sum) 335 | total += counter 336 | #print('bbox_sum','total', bbox_sum, total) 337 | if total: 338 | return bbox_sum/total 339 | else: 340 | return 0 341 | 342 | def wh_iou(box1, box2): 343 | # Returns the IoU of wh1 to wh2. wh1 is 2, wh2 is nx2 344 | box2 = box2.t() 345 | 346 | # w, h = box1 347 | w1, h1 = box1[0], box1[1] 348 | w2, h2 = box2[0], box2[1] 349 | 350 | # Intersection area 351 | inter_area = torch.min(w1, w2) * torch.min(h1, h2) 352 | 353 | # Union Area 354 | union_area = (w1 * h1 + 1e-16) + w2 * h2 - inter_area 355 | 356 | return inter_area / union_area # iou 357 | 358 | 359 | class FocalLoss(nn.Module): 360 | # Wraps focal loss around existing loss_fcn() https://arxiv.org/pdf/1708.02002.pdf 361 | # i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=2.5) 362 | def __init__(self, loss_fcn, gamma=0.5, alpha=1, reduction='mean'): 363 | super(FocalLoss, self).__init__() 364 | loss_fcn.reduction = 'none' # required to apply FL to each element 365 | self.loss_fcn = loss_fcn 366 | self.gamma = gamma 367 | self.alpha = alpha 368 | self.reduction = reduction 369 | 370 | def forward(self, input, target): 371 | loss = self.loss_fcn(input, target) 372 | loss *= self.alpha * (1.000001 - torch.exp(-loss)) ** self.gamma # non-zero power for gradient stability 373 | 374 | if self.reduction == 'mean': 375 | return loss.mean() 376 | elif self.reduction == 'sum': 377 | return loss.sum() 378 | else: # 'none' 379 | return loss 380 | 381 | 382 | def compute_loss(p, targets, model): # predictions, targets, model 383 | ft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensor 384 | lcls, lbox, lobj, lrep, lrep0= ft([0]), ft([0]), ft([0]), ft([0]), ft([0]) 385 | tcls, tbox, indices, anchor_vec = build_targets(model, targets) 386 | h = model.hyp # hyperparameters 387 | #print('tbox',tbox) 388 | #print(len(tbox)) 389 | #print('indices',indices) 390 | #print(indices.shape) 391 | arc = model.arc # # (default, uCE, uBCE) detection architectures 392 | 393 | # Define criteria 394 | BCEcls = nn.BCEWithLogitsLoss(pos_weight=ft([h['cls_pw']])) 395 | BCEobj = nn.BCEWithLogitsLoss(pos_weight=ft([h['obj_pw']])) 396 | BCE = nn.BCEWithLogitsLoss() 397 | CE = nn.CrossEntropyLoss() # weight=model.class_weights 398 | 399 | if 'F' in arc: # add focal loss 400 | g = h['fl_gamma'] 401 | BCEcls, BCEobj, BCE, CE = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g), FocalLoss(BCE, g), FocalLoss(CE, g) 402 | 403 | # Compute losses 404 | for i, pi in enumerate(p): # layer index, layer predictions 405 | b, a, gj, gi = indices[i] # image, anchor, gridy, gridx 406 | print('b=', b) 407 | #print('a=', a) 408 | #print('gj=', gj) 409 | #print('gi=', gi) 410 | tobj = torch.zeros_like(pi[..., 0]) # target obj 411 | #print(pi.shape) 412 | # Compute losses 413 | nb = len(b) 414 | if nb: # number of targets 415 | ps = pi[b, a, gj, gi] # prediction subset corresponding to targets 416 | #print(ps.shape) 417 | #print('p=', ps) 418 | tobj[b, a, gj, gi] = 1.0 # obj 419 | # ps[:, 2:4] = torch.sigmoid(ps[:, 2:4]) # wh power loss (uncomment) 420 | 421 | # GIoU 422 | pxy = torch.sigmoid(ps[:, 0:2]) # pxy = pxy * s - (s - 1) / 2, s = 1.5 (scale_xy) 423 | pbox = torch.cat((pxy, torch.exp(ps[:, 2:4]) * anchor_vec[i]), 1) # predicted box 424 | print(pbox) 425 | giou = bbox_iou(pbox.t(), tbox[i], x1y1x2y2=False, GIoU=True) # giou computation 426 | #print(pbox.t().shape, tbox[i].shape) 427 | lbox += (1.0 - giou).mean() # giou loss 428 | dic = {0: [], 1: [], 2: [],3: [],4: [],5: [],6: [],7: [],8: [],9: [],10: [],11: [],12: [],13: [],14: [],15: [],16: [],17: [],18: [],19: [],20: [],21: [],22: [],23: [],24: [],25: [],26: [],27: [],28: [],29: [],30: [],31: []} 429 | for indexs, value in enumerate(b): 430 | #print(indexs, value) 431 | dic[int(value)].append(indexs) 432 | print('dic', dic) 433 | for id, indexs in dic.items(): 434 | #print('pbox_same=', pbox[indexs]) 435 | if indexs: 436 | lrep0 += RepBox_loss(pbox[indexs].t()) 437 | lrep += RepGT_loss(pbox[indexs].t(), tbox[i][indexs], x1y1x2y2=False) 438 | #print(lrep) 439 | print(pbox[indexs]) 440 | #print(tbox[i][indexs].shape) 441 | print(lrep0) 442 | print(lrep) 443 | if 'default' in arc and model.nc > 1: # cls loss (only if multiple classes) 444 | t = torch.zeros_like(ps[:, 5:]) # targets 445 | t[range(nb), tcls[i]] = 1.0 446 | lcls += BCEcls(ps[:, 5:], t) # BCE 447 | # lcls += CE(ps[:, 5:], tcls[i]) # CE 448 | 449 | # Instance-class weighting (use with reduction='none') 450 | # nt = t.sum(0) + 1 # number of targets per class 451 | # lcls += (BCEcls(ps[:, 5:], t) / nt).mean() * nt.mean() # v1 452 | # lcls += (BCEcls(ps[:, 5:], t) / nt[tcls[i]].view(-1,1)).mean() * nt.mean() # v2 453 | 454 | # Append targets to text file 455 | # with open('targets.txt', 'a') as file: 456 | # [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)] 457 | 458 | if 'default' in arc: # separate obj and cls 459 | lobj += BCEobj(pi[..., 4], tobj) # obj loss 460 | 461 | elif 'BCE' in arc: # unified BCE (80 classes) 462 | t = torch.zeros_like(pi[..., 5:]) # targets 463 | if nb: 464 | t[b, a, gj, gi, tcls[i]] = 1.0 465 | lobj += BCE(pi[..., 5:], t) 466 | 467 | elif 'CE' in arc: # unified CE (1 background + 80 classes) 468 | t = torch.zeros_like(pi[..., 0], dtype=torch.long) # targets 469 | if nb: 470 | t[b, a, gj, gi] = tcls[i] + 1 471 | lcls += CE(pi[..., 4:].view(-1, model.nc + 1), t.view(-1)) 472 | 473 | lbox *= h['giou'] 474 | lobj *= h['obj'] 475 | lcls *= h['cls'] 476 | #print('lrep', lrep) 477 | #print('lrep0', lrep0) 478 | lrep = (lrep+lrep0)/32 479 | #print('lbox0 =', lrep) 480 | loss = lbox + lobj + lcls + lrep 481 | return loss, torch.cat((lbox, lobj, lcls, lrep, loss)).detach() 482 | 483 | 484 | def build_targets(model, targets): 485 | # targets = [image, class, x, y, w, h] 486 | 487 | nt = len(targets) 488 | tcls, tbox, indices, av = [], [], [], [] 489 | multi_gpu = type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) 490 | for i in model.yolo_layers: 491 | # get number of grid points and anchor vec for this yolo layer 492 | if multi_gpu: 493 | ng, anchor_vec = model.module.module_list[i].ng, model.module.module_list[i].anchor_vec 494 | else: 495 | ng, anchor_vec = model.module_list[i].ng, model.module_list[i].anchor_vec 496 | 497 | # iou of targets-anchors 498 | t, a = targets, [] 499 | gwh = t[:, 4:6] * ng 500 | if nt: 501 | iou = torch.stack([wh_iou(x, gwh) for x in anchor_vec], 0) 502 | 503 | use_best_anchor = False 504 | if use_best_anchor: 505 | iou, a = iou.max(0) # best iou and anchor 506 | else: # use all anchors 507 | na = len(anchor_vec) # number of anchors 508 | a = torch.arange(na).view((-1, 1)).repeat([1, nt]).view(-1) 509 | t = targets.repeat([na, 1]) 510 | gwh = gwh.repeat([na, 1]) 511 | iou = iou.view(-1) # use all ious 512 | 513 | # reject anchors below iou_thres (OPTIONAL, increases P, lowers R) 514 | reject = True 515 | if reject: 516 | j = iou > model.hyp['iou_t'] # iou threshold hyperparameter 517 | t, a, gwh = t[j], a[j], gwh[j] 518 | 519 | # Indices 520 | b, c = t[:, :2].long().t() # target image, class 521 | gxy = t[:, 2:4] * ng # grid x, y 522 | gi, gj = gxy.long().t() # grid x, y indices 523 | indices.append((b, a, gj, gi)) 524 | 525 | # GIoU 526 | gxy -= gxy.floor() # xy 527 | tbox.append(torch.cat((gxy, gwh), 1)) # xywh (grids) 528 | av.append(anchor_vec[a]) # anchor vec 529 | 530 | # Class 531 | tcls.append(c) 532 | if c.shape[0]: # if any targets 533 | assert c.max() <= model.nc, 'Target classes exceed model classes' 534 | #print(indices) 535 | return tcls, tbox, indices, av 536 | 537 | 538 | def non_max_suppression(prediction, conf_thres=0.5, nms_thres=0.5): 539 | """ 540 | Removes detections with lower object confidence score than 'conf_thres' 541 | Non-Maximum Suppression to further filter detections. 542 | Returns detections with shape: 543 | (x1, y1, x2, y2, object_conf, class_conf, class) 544 | """ 545 | 546 | min_wh = 2 # (pixels) minimum box width and height 547 | 548 | output = [None] * len(prediction) 549 | for image_i, pred in enumerate(prediction): 550 | # Experiment: Prior class size rejection 551 | # x, y, w, h = pred[:, 0], pred[:, 1], pred[:, 2], pred[:, 3] 552 | # a = w * h # area 553 | # ar = w / (h + 1e-16) # aspect ratio 554 | # n = len(w) 555 | # log_w, log_h, log_a, log_ar = torch.log(w), torch.log(h), torch.log(a), torch.log(ar) 556 | # shape_likelihood = np.zeros((n, 60), dtype=np.float32) 557 | # x = np.concatenate((log_w.reshape(-1, 1), log_h.reshape(-1, 1)), 1) 558 | # from scipy.stats import multivariate_normal 559 | # for c in range(60): 560 | # shape_likelihood[:, c] = 561 | # multivariate_normal.pdf(x, mean=mat['class_mu'][c, :2], cov=mat['class_cov'][c, :2, :2]) 562 | 563 | # Multiply conf by class conf to get combined confidence 564 | class_conf, class_pred = pred[:, 5:].max(1) 565 | pred[:, 4] *= class_conf 566 | 567 | # # Merge classes (optional) 568 | # class_pred[(class_pred.view(-1,1) == torch.LongTensor([2, 3, 5, 6, 7]).view(1,-1)).any(1)] = 2 569 | # 570 | # # Remove classes (optional) 571 | # pred[class_pred != 2, 4] = 0.0 572 | 573 | # Select only suitable predictions 574 | i = (pred[:, 4] > conf_thres) & (pred[:, 2:4] > min_wh).all(1) & torch.isfinite(pred).all(1) 575 | pred = pred[i] 576 | 577 | # If none are remaining => process next image 578 | if len(pred) == 0: 579 | continue 580 | 581 | # Select predicted classes 582 | class_conf = class_conf[i] 583 | class_pred = class_pred[i].unsqueeze(1).float() 584 | 585 | # Box (center x, center y, width, height) to (x1, y1, x2, y2) 586 | pred[:, :4] = xywh2xyxy(pred[:, :4]) 587 | # pred[:, 4] *= class_conf # improves mAP from 0.549 to 0.551 588 | 589 | # Detections ordered as (x1y1x2y2, obj_conf, class_conf, class_pred) 590 | pred = torch.cat((pred[:, :5], class_conf.unsqueeze(1), class_pred), 1) 591 | 592 | # Get detections sorted by decreasing confidence scores 593 | pred = pred[(-pred[:, 4]).argsort()] 594 | 595 | det_max = [] 596 | nms_style = 'MERGE' # 'OR' (default), 'AND', 'MERGE' (experimental) 597 | for c in pred[:, -1].unique(): 598 | dc = pred[pred[:, -1] == c] # select class c 599 | n = len(dc) 600 | if n == 1: 601 | det_max.append(dc) # No NMS required if only 1 prediction 602 | continue 603 | elif n > 100: 604 | dc = dc[:100] # limit to first 100 boxes: https://github.com/ultralytics/yolov3/issues/117 605 | 606 | # Non-maximum suppression 607 | if nms_style == 'OR': # default 608 | # METHOD1 609 | # ind = list(range(len(dc))) 610 | # while len(ind): 611 | # j = ind[0] 612 | # det_max.append(dc[j:j + 1]) # save highest conf detection 613 | # reject = (bbox_iou(dc[j], dc[ind]) > nms_thres).nonzero() 614 | # [ind.pop(i) for i in reversed(reject)] 615 | 616 | # METHOD2 617 | while dc.shape[0]: 618 | det_max.append(dc[:1]) # save highest conf detection 619 | if len(dc) == 1: # Stop if we're at the last detection 620 | break 621 | iou = bbox_iou(dc[0], dc[1:]) # iou with other boxes 622 | dc = dc[1:][iou < nms_thres] # remove ious > threshold 623 | 624 | elif nms_style == 'AND': # requires overlap, single boxes erased 625 | while len(dc) > 1: 626 | iou = bbox_iou(dc[0], dc[1:]) # iou with other boxes 627 | if iou.max() > 0.5: 628 | det_max.append(dc[:1]) 629 | dc = dc[1:][iou < nms_thres] # remove ious > threshold 630 | 631 | elif nms_style == 'MERGE': # weighted mixture box 632 | while len(dc): 633 | if len(dc) == 1: 634 | det_max.append(dc) 635 | break 636 | i = bbox_iou(dc[0], dc) > nms_thres # iou with other boxes 637 | weights = dc[i, 4:5] 638 | dc[0, :4] = (weights * dc[i, :4]).sum(0) / weights.sum() 639 | det_max.append(dc[:1]) 640 | dc = dc[i == 0] 641 | 642 | elif nms_style == 'SOFT': # soft-NMS https://arxiv.org/abs/1704.04503 643 | sigma = 0.5 # soft-nms sigma parameter 644 | while len(dc): 645 | if len(dc) == 1: 646 | det_max.append(dc) 647 | break 648 | det_max.append(dc[:1]) 649 | iou = bbox_iou(dc[0], dc[1:]) # iou with other boxes 650 | dc = dc[1:] 651 | dc[:, 4] *= torch.exp(-iou ** 2 / sigma) # decay confidences 652 | # dc = dc[dc[:, 4] > nms_thres] # new line per https://github.com/ultralytics/yolov3/issues/362 653 | 654 | if len(det_max): 655 | det_max = torch.cat(det_max) # concatenate 656 | output[image_i] = det_max[(-det_max[:, 4]).argsort()] # sort 657 | 658 | return output 659 | 660 | 661 | def get_yolo_layers(model): 662 | bool_vec = [x['type'] == 'yolo' for x in model.module_defs] 663 | return [i for i, x in enumerate(bool_vec) if x] # [82, 94, 106] for yolov3 664 | 665 | 666 | def print_model_biases(model): 667 | # prints the bias neurons preceding each yolo layer 668 | print('\nModel Bias Summary (per output layer):') 669 | multi_gpu = type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) 670 | for l in model.yolo_layers: # print pretrained biases 671 | if multi_gpu: 672 | na = model.module.module_list[l].na # number of anchors 673 | b = model.module.module_list[l - 1][0].bias.view(na, -1) # bias 3x85 674 | else: 675 | na = model.module_list[l].na 676 | b = model.module_list[l - 1][0].bias.view(na, -1) # bias 3x85 677 | print('regression: %5.2f+/-%-5.2f ' % (b[:, :4].mean(), b[:, :4].std()), 678 | 'objectness: %5.2f+/-%-5.2f ' % (b[:, 4].mean(), b[:, 4].std()), 679 | 'classification: %5.2f+/-%-5.2f' % (b[:, 5:].mean(), b[:, 5:].std())) 680 | 681 | 682 | def strip_optimizer(f='weights/last.pt'): # from utils.utils import *; strip_optimizer() 683 | # Strip optimizer from *.pt files for lighter files (reduced by 2/3 size) 684 | x = torch.load(f) 685 | x['optimizer'] = None 686 | torch.save(x, f) 687 | 688 | 689 | def create_backbone(f='weights/last.pt'): # from utils.utils import *; create_backbone() 690 | # create a backbone from a *.pt file 691 | x = torch.load(f) 692 | x['optimizer'] = None 693 | x['training_results'] = None 694 | x['epoch'] = -1 695 | for p in x['model'].values(): 696 | try: 697 | p.requires_grad = True 698 | except: 699 | pass 700 | torch.save(x, 'weights/backbone.pt') 701 | 702 | 703 | def coco_class_count(path='../coco/labels/train2014/'): 704 | # Histogram of occurrences per class 705 | nc = 80 # number classes 706 | x = np.zeros(nc, dtype='int32') 707 | files = sorted(glob.glob('%s/*.*' % path)) 708 | for i, file in enumerate(files): 709 | labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5) 710 | x += np.bincount(labels[:, 0].astype('int32'), minlength=nc) 711 | print(i, len(files)) 712 | 713 | 714 | def coco_only_people(path='../coco/labels/val2014/'): 715 | # Find images with only people 716 | files = sorted(glob.glob('%s/*.*' % path)) 717 | for i, file in enumerate(files): 718 | labels = np.loadtxt(file, dtype=np.float32).reshape(-1, 5) 719 | if all(labels[:, 0] == 0): 720 | print(labels.shape[0], file) 721 | 722 | 723 | def select_best_evolve(path='evolve*.txt'): # from utils.utils import *; select_best_evolve() 724 | # Find best evolved mutation 725 | for file in sorted(glob.glob(path)): 726 | x = np.loadtxt(file, dtype=np.float32, ndmin=2) 727 | print(file, x[fitness(x).argmax()]) 728 | 729 | 730 | def crop_images_random(path='../images/', scale=0.50): # from utils.utils import *; crop_images_random() 731 | # crops images into random squares up to scale fraction 732 | # WARNING: overwrites images! 733 | for file in tqdm(sorted(glob.glob('%s/*.*' % path))): 734 | img = cv2.imread(file) # BGR 735 | if img is not None: 736 | h, w = img.shape[:2] 737 | 738 | # create random mask 739 | a = 30 # minimum size (pixels) 740 | mask_h = random.randint(a, int(max(a, h * scale))) # mask height 741 | mask_w = mask_h # mask width 742 | 743 | # box 744 | xmin = max(0, random.randint(0, w) - mask_w // 2) 745 | ymin = max(0, random.randint(0, h) - mask_h // 2) 746 | xmax = min(w, xmin + mask_w) 747 | ymax = min(h, ymin + mask_h) 748 | 749 | # apply random color mask 750 | cv2.imwrite(file, img[ymin:ymax, xmin:xmax]) 751 | 752 | 753 | def coco_single_class_labels(path='../coco/labels/train2014/', label_class=43): 754 | # Makes single-class coco datasets. from utils.utils import *; coco_single_class_labels() 755 | if os.path.exists('new/'): 756 | shutil.rmtree('new/') # delete output folder 757 | os.makedirs('new/') # make new output folder 758 | os.makedirs('new/labels/') 759 | os.makedirs('new/images/') 760 | for file in tqdm(sorted(glob.glob('%s/*.*' % path))): 761 | with open(file, 'r') as f: 762 | labels = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) 763 | i = labels[:, 0] == label_class 764 | if any(i): 765 | img_file = file.replace('labels', 'images').replace('txt', 'jpg') 766 | labels[:, 0] = 0 # reset class to 0 767 | with open('new/images.txt', 'a') as f: # add image to dataset list 768 | f.write(img_file + '\n') 769 | with open('new/labels/' + Path(file).name, 'a') as f: # write label 770 | for l in labels[i]: 771 | f.write('%g %.6f %.6f %.6f %.6f\n' % tuple(l)) 772 | shutil.copyfile(src=img_file, dst='new/images/' + Path(file).name.replace('txt', 'jpg')) # copy images 773 | 774 | 775 | def kmeans_targets(path='../coco/trainvalno5k.txt', n=9, img_size=512): # from utils.utils import *; kmeans_targets() 776 | # Produces a list of target kmeans suitable for use in *.cfg files 777 | from utils.datasets import LoadImagesAndLabels 778 | from scipy import cluster 779 | 780 | # Get label wh 781 | dataset = LoadImagesAndLabels(path, augment=True, rect=True, cache_labels=True) 782 | for s, l in zip(dataset.shapes, dataset.labels): 783 | l[:, [1, 3]] *= s[0] # normalized to pixels 784 | l[:, [2, 4]] *= s[1] 785 | l[:, 1:] *= img_size / max(s) * random.uniform(0.99, 1.01) # nominal img_size for training 786 | wh = np.concatenate(dataset.labels, 0)[:, 3:5] # wh from cxywh 787 | 788 | # Kmeans calculation 789 | k = cluster.vq.kmeans(wh, n)[0] 790 | k = k[np.argsort(k.prod(1))] # sort small to large 791 | 792 | # Measure IoUs 793 | iou = torch.stack([wh_iou(torch.Tensor(wh).T, torch.Tensor(x).T) for x in k], 0) 794 | biou = iou.max(0)[0] # closest anchor IoU 795 | print('Best possible recall: %.3f' % (biou > 0.2635).float().mean()) # BPR (best possible recall) 796 | 797 | # Print 798 | print('kmeans anchors (n=%g, img_size=%g, IoU=%.2f/%.2f/%.2f-min/mean/best): ' % 799 | (n, img_size, biou.min(), iou.mean(), biou.mean()), end='') 800 | for i, x in enumerate(k): 801 | print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg 802 | 803 | # Plot 804 | # plt.hist(biou.numpy().ravel(), 100) 805 | 806 | 807 | def print_mutation(hyp, results, bucket=''): 808 | # Print mutation results to evolve.txt (for use with train.py --evolve) 809 | a = '%10s' * len(hyp) % tuple(hyp.keys()) # hyperparam keys 810 | b = '%10.3g' * len(hyp) % tuple(hyp.values()) # hyperparam values 811 | c = '%10.3g' * len(results) % results # results (P, R, mAP, F1, test_loss) 812 | print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c)) 813 | 814 | if bucket: 815 | os.system('gsutil cp gs://%s/evolve.txt .' % bucket) # download evolve.txt 816 | 817 | with open('evolve.txt', 'a') as f: # append result 818 | f.write(c + b + '\n') 819 | x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0) # load unique rows 820 | np.savetxt('evolve.txt', x[np.argsort(-fitness(x))], '%10.3g') # save sort by fitness 821 | 822 | if bucket: 823 | os.system('gsutil cp evolve.txt gs://%s' % bucket) # upload evolve.txt 824 | 825 | 826 | def apply_classifier(x, model, img, im0): 827 | # applies a second stage classifier to yolo outputs 828 | 829 | for i, d in enumerate(x): # per image 830 | if d is not None and len(d): 831 | d = d.clone() 832 | 833 | # Reshape and pad cutouts 834 | b = xyxy2xywh(d[:, :4]) # boxes 835 | b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square 836 | b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad 837 | d[:, :4] = xywh2xyxy(b).long() 838 | 839 | # Rescale boxes from img_size to im0 size 840 | scale_coords(img.shape[2:], d[:, :4], im0.shape) 841 | 842 | # Classes 843 | pred_cls1 = d[:, 6].long() 844 | ims = [] 845 | for j, a in enumerate(d): # per item 846 | cutout = im0[int(a[1]):int(a[3]), int(a[0]):int(a[2])] 847 | im = cv2.resize(cutout, (224, 224)) # BGR 848 | # cv2.imwrite('test%i.jpg' % j, cutout) 849 | 850 | im = im[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416 851 | im = np.ascontiguousarray(im, dtype=np.float32) # uint8 to float32 852 | im /= 255.0 # 0 - 255 to 0.0 - 1.0 853 | ims.append(im) 854 | 855 | pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1) # classifier prediction 856 | x[i] = x[i][pred_cls1 == pred_cls2] # retain matching class detections 857 | 858 | return x 859 | 860 | 861 | def fitness(x): 862 | # Returns fitness (for use with results.txt or evolve.txt) 863 | return x[:, 2] * 0.8 + x[:, 3] * 0.2 # weighted mAP and F1 combination 864 | 865 | 866 | # Plotting functions --------------------------------------------------------------------------------------------------- 867 | def plot_one_box(x, img, color=None, label=None, line_thickness=None): 868 | # Plots one bounding box on image img 869 | tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line thickness 870 | color = color or [random.randint(0, 255) for _ in range(3)] 871 | c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3])) 872 | cv2.rectangle(img, c1, c2, color, thickness=tl) 873 | if label: 874 | tf = max(tl - 1, 1) # font thickness 875 | t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0] 876 | c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3 877 | cv2.rectangle(img, c1, c2, color, -1) # filled 878 | cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA) 879 | 880 | 881 | def plot_wh_methods(): # from utils.utils import *; plot_wh_methods() 882 | # Compares the two methods for width-height anchor multiplication 883 | # https://github.com/ultralytics/yolov3/issues/168 884 | x = np.arange(-4.0, 4.0, .1) 885 | ya = np.exp(x) 886 | yb = torch.sigmoid(torch.from_numpy(x)).numpy() * 2 887 | 888 | fig = plt.figure(figsize=(6, 3), dpi=150) 889 | plt.plot(x, ya, '.-', label='yolo method') 890 | plt.plot(x, yb ** 2, '.-', label='^2 power method') 891 | plt.plot(x, yb ** 2.5, '.-', label='^2.5 power method') 892 | plt.xlim(left=-4, right=4) 893 | plt.ylim(bottom=0, top=6) 894 | plt.xlabel('input') 895 | plt.ylabel('output') 896 | plt.legend() 897 | fig.tight_layout() 898 | fig.savefig('comparison.png', dpi=200) 899 | 900 | 901 | def plot_images(imgs, targets, paths=None, fname='images.jpg'): 902 | # Plots training images overlaid with targets 903 | imgs = imgs.cpu().numpy() 904 | targets = targets.cpu().numpy() 905 | # targets = targets[targets[:, 1] == 21] # plot only one class 906 | 907 | fig = plt.figure(figsize=(10, 10)) 908 | bs, _, h, w = imgs.shape # batch size, _, height, width 909 | bs = min(bs, 16) # limit plot to 16 images 910 | ns = np.ceil(bs ** 0.5) # number of subplots 911 | 912 | for i in range(bs): 913 | boxes = xywh2xyxy(targets[targets[:, 0] == i, 2:6]).T 914 | boxes[[0, 2]] *= w 915 | boxes[[1, 3]] *= h 916 | plt.subplot(ns, ns, i + 1).imshow(imgs[i].transpose(1, 2, 0)) 917 | plt.plot(boxes[[0, 2, 2, 0, 0]], boxes[[1, 1, 3, 3, 1]], '.-') 918 | plt.axis('off') 919 | if paths is not None: 920 | s = Path(paths[i]).name 921 | plt.title(s[:min(len(s), 40)], fontdict={'size': 8}) # limit to 40 characters 922 | fig.tight_layout() 923 | fig.savefig(fname, dpi=200) 924 | plt.close() 925 | 926 | 927 | def plot_test_txt(): # from utils.utils import *; plot_test() 928 | # Plot test.txt histograms 929 | x = np.loadtxt('test.txt', dtype=np.float32) 930 | box = xyxy2xywh(x[:, :4]) 931 | cx, cy = box[:, 0], box[:, 1] 932 | 933 | fig, ax = plt.subplots(1, 1, figsize=(6, 6)) 934 | ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0) 935 | ax.set_aspect('equal') 936 | fig.tight_layout() 937 | plt.savefig('hist2d.jpg', dpi=300) 938 | 939 | fig, ax = plt.subplots(1, 2, figsize=(12, 6)) 940 | ax[0].hist(cx, bins=600) 941 | ax[1].hist(cy, bins=600) 942 | fig.tight_layout() 943 | plt.savefig('hist1d.jpg', dpi=200) 944 | 945 | 946 | def plot_targets_txt(): # from utils.utils import *; plot_targets_txt() 947 | # Plot test.txt histograms 948 | x = np.loadtxt('targets.txt', dtype=np.float32) 949 | x = x.T 950 | 951 | s = ['x targets', 'y targets', 'width targets', 'height targets'] 952 | fig, ax = plt.subplots(2, 2, figsize=(8, 8)) 953 | ax = ax.ravel() 954 | for i in range(4): 955 | ax[i].hist(x[i], bins=100, label='%.3g +/- %.3g' % (x[i].mean(), x[i].std())) 956 | ax[i].legend() 957 | ax[i].set_title(s[i]) 958 | fig.tight_layout() 959 | plt.savefig('targets.jpg', dpi=200) 960 | 961 | 962 | def plot_evolution_results(hyp): # from utils.utils import *; plot_evolution_results(hyp) 963 | # Plot hyperparameter evolution results in evolve.txt 964 | x = np.loadtxt('evolve.txt', ndmin=2) 965 | f = fitness(x) 966 | weights = (f - f.min()) ** 2 # for weighted results 967 | fig = plt.figure(figsize=(12, 10)) 968 | matplotlib.rc('font', **{'size': 8}) 969 | for i, (k, v) in enumerate(hyp.items()): 970 | y = x[:, i + 5] 971 | # mu = (y * weights).sum() / weights.sum() # best weighted result 972 | mu = y[f.argmax()] # best single result 973 | plt.subplot(4, 5, i + 1) 974 | plt.plot(mu, f.max(), 'o', markersize=10) 975 | plt.plot(y, f, '.') 976 | plt.title('%s = %.3g' % (k, mu), fontdict={'size': 9}) # limit to 40 characters 977 | print('%15s: %.3g' % (k, mu)) 978 | fig.tight_layout() 979 | plt.savefig('evolve.png', dpi=200) 980 | 981 | 982 | def plot_results(start=0, stop=0): # from utils.utils import *; plot_results() 983 | # Plot training results files 'results*.txt' 984 | fig, ax = plt.subplots(2, 5, figsize=(14, 7)) 985 | ax = ax.ravel() 986 | s = ['GIoU', 'Objectness', 'Classification', 'Precision', 'Recall', 987 | 'val GIoU', 'val Objectness', 'val Classification', 'mAP@0.5', 'F1'] 988 | for f in sorted(glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')): 989 | results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T 990 | n = results.shape[1] # number of rows 991 | x = range(start, min(stop, n) if stop else n) 992 | for i in range(10): 993 | y = results[i, x] 994 | if i in [0, 1, 2, 5, 6, 7]: 995 | y[y == 0] = np.nan # dont show zero loss values 996 | ax[i].plot(x, y, marker='.', label=f.replace('.txt', '')) 997 | ax[i].set_title(s[i]) 998 | if i in [5, 6, 7]: # share train and val loss y axes 999 | ax[i].get_shared_y_axes().join(ax[i], ax[i - 5]) 1000 | 1001 | fig.tight_layout() 1002 | ax[1].legend() 1003 | fig.savefig('results.png', dpi=200) 1004 | 1005 | 1006 | def plot_results_overlay(start=0, stop=0): # from utils.utils import *; plot_results_overlay() 1007 | # Plot training results files 'results*.txt', overlaying train and val losses 1008 | s = ['train', 'train', 'train', 'Precision', 'mAP@0.5', 'val', 'val', 'val', 'Recall', 'F1'] # legends 1009 | t = ['GIoU', 'Objectness', 'Classification', 'P-R', 'mAP-F1'] # titles 1010 | for f in sorted(glob.glob('results*.txt') + glob.glob('../../Downloads/results*.txt')): 1011 | results = np.loadtxt(f, usecols=[2, 3, 4, 8, 9, 12, 13, 14, 10, 11], ndmin=2).T 1012 | n = results.shape[1] # number of rows 1013 | x = range(start, min(stop, n) if stop else n) 1014 | fig, ax = plt.subplots(1, 5, figsize=(14, 3.5)) 1015 | ax = ax.ravel() 1016 | for i in range(5): 1017 | for j in [i, i + 5]: 1018 | y = results[j, x] 1019 | if i in [0, 1, 2]: 1020 | y[y == 0] = np.nan # dont show zero loss values 1021 | ax[i].plot(x, y, marker='.', label=s[j]) 1022 | ax[i].set_title(t[i]) 1023 | ax[i].legend() 1024 | ax[i].set_ylabel(f) if i == 0 else None # add filename 1025 | fig.tight_layout() 1026 | fig.savefig(f.replace('.txt', '.png'), dpi=200) 1027 | 1028 | 1029 | def version_to_tuple(version): 1030 | # Used to compare versions of library 1031 | return tuple(map(int, (version.split(".")))) 1032 | --------------------------------------------------------------------------------