def train(config): utils.init_seeds(1) results_file = os.path.join(config['logdir'], 'results.txt') # Remove previous results for f in glob.glob(os.path.join( config['logdir'], 'train_batch*.jpg')) + glob.glob(results_file): os.remove(f) epochs = config['epochs'] # 300 batch_size = config['batch_size'] # 64 weights = config['weights'] # initial training weights imgsz, imgsz_test = config['img_size'] strides = config['detect_strides'] num_classes = config['num_classes'] if config['only_3d']: config['notest'] = True config['include_scopes'] = ['model.24.bbox3d_headers'] config['giou'] = 0. config['obj'] = 0. config['cls'] = 0. elif config['only_2d']: config['exclude_scopes'] = ['model.24.bbox3d_headers'] config['conf'] = 0. config['orient'] = 0. config['dim'] = 0. config[ 'cls'] *= num_classes / 80. # scale coco-tuned config['cls'] to current dataset gs = int(max(strides)) # dataset with open(config['data']) as f: data_dict = yaml.load(f, Loader=yaml.FullLoader) # model dict dataset_path = data_dict['dataset_path'] # Trainloader test_cfg = {} test_cfg.update(config) dataloader, dataset = create_dataloader(dataset_path, config, transform=TrainAugmentation( cfg['img_size'][0], mean=config['brg_mean']), is_training=True) mlc = np.concatenate(dataset.labels, 0)[:, 0].max() # max label class assert mlc < num_classes, \ 'Label class %g exceeds nc=%g in %s. Correct your labels or your model.' % (mlc, num_classes, config['cfg']) # Testloader test_cfg['is_rect'] = True test_cfg['is_mosaic'] = False testloader = create_dataloader(dataset_path, test_cfg, transform=TestTransform( cfg['img_size'][0], mean=config['brg_mean']), is_training=False, split='test')[0] # Create model model = Model(config).to(device) nb = len(dataloader) # number of batches max_step_burn_in = max( 3 * nb, 1e3) # burn-in iterations, max(3 epochs, 1k iterations) solver = Solver(model, config, max_steps_burn_in=max_step_burn_in, apex=None) losser = losses.YoloLoss(model) # Load Model start_epoch, best_fitness = 0, 0.0 checkpointer = model_utils.CheckPointer(model, solver, save_dir='./weights', save_to_disk=True, device=device) if weights.endswith('.pt'): # pytorch format ckpt = checkpointer.load(weights, use_latest=False, load_solver=(not config['resume'])) # load results if ckpt.get('training_results') is not None: with open(results_file, 'w') as file: file.write(ckpt['training_results']) # write results.txt if not config['resume']: start_epoch = ckpt['epoch'] + 1 best_fitness = ckpt['best_fitness'] del ckpt else: solver.build_optim_and_scheduler() if tb_writer: # Class frequency labels = np.concatenate(dataset.labels, 0) c = torch.tensor(labels[:, 0]) # classes visual_utils.plot_labels(labels, config['logdir']) tb_writer.add_histogram('classes', c, 0) # Check anchors if not config['noautoanchor']: anchor_utils.check_anchors(dataset, model=model, thr=config['anchor_t'], imgsz=imgsz) # Start training t0 = time.time() results = ( 0, 0, 0, 0, 0, 0, 0 ) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification' print('Image sizes %g train, %g test' % (imgsz, imgsz_test)) print('Using %g dataloader workers' % dataloader.num_workers) print('Starting training for %g epochs...' % epochs) for epoch in range( start_epoch, epochs ): # epoch ------------------------------------------------------------------ model.train() mloss = torch.zeros(7, device=device) # mean losses print(('\n' + '%10s' * 12) % ('Epoch', 'gpu_mem', 'GIoU', 'obj', 'cls', 'conf', 'orient', 'dim', 'total', 'targets', 'img_size', 'lr')) pbar = tqdm.tqdm(enumerate(dataloader), total=nb) # progress bar for i, ( imgs, targets, paths, _ ) in pbar: # batch ------------------------------------------------------------- targets.delete_by_mask() targets.to_float32() targ = ParamList(targets.size, True) targ.copy_from(targets) img_id = targets.get_field('img_id') classes = targets.get_field('class') bboxes = targets.get_field('bbox') targets = torch.cat( [img_id.unsqueeze(-1), classes.unsqueeze(-1), bboxes], dim=-1) ni = i + nb * epoch # number integrated batches (since train start) imgs = imgs.to( device).float() / 1.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0 solver.update(epoch) # Multi-scale if config['multi_scale']: sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs # size sf = sz / max(imgs.shape[2:]) # scale factor if sf != 1: ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:] ] # new shape (stretched to gs-multiple) imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False) # Forward pred = model(imgs) # Loss # loss, loss_items = losses.calc_loss(pred, targets.to(device), model) loss, loss_items = losser(pred, targ) # print(loss_items) if not torch.isfinite(loss): print('WARNING: non-finite loss, ending training ', loss_items) return results solver.optimizer_step(loss) # Print mloss = (mloss * i + loss_items) / (i + 1) # update mean losses mem = '%.3gG' % (torch.cuda.memory_cached() / 1E9 if torch.cuda.is_available() else 0) # (GB) s = ('%10s' * 2 + '%10.4g' * 10) % ( '%g/%g' % (epoch, epochs - 1), mem, *mloss, targets.shape[0], imgs.shape[-1], solver.learn_rate) pbar.set_description(s) # Plot if ni < 3: f = os.path.join(config['logdir'], 'train_batch%g.jpg' % ni) # filename result = visual_utils.plot_images(images=imgs, targets=targets, paths=paths, fname=f) if tb_writer and result is not None: tb_writer.add_image(f, result, dataformats='HWC', global_step=epoch) # tb_writer.add_graph(model, imgs) # add model to tensorboard # end batch ============================================================================================ solver.scheduler_step() # mAP solver.ema.update_attr(model) final_epoch = epoch + 1 == epochs if not config['notest'] or final_epoch: # Calculate mAP results, maps, times = test.test( config['data'], batch_size=batch_size, imgsz=imgsz_test, save_json=final_epoch and config['data'].endswith(os.sep + 'kitti.yaml'), model=solver.ema.model, logdir=config['logdir'], dataloader=testloader) # Write with open(os.path.join(results_file), 'a') as f: f.write(s + '%10.4g' * 7 % results + '\n') # P, R, mAP, F1, test_losses=(GIoU, obj, cls) # Tensorboard if tb_writer: tags = [ 'train/giou_loss', 'train/obj_loss', 'train/cls_loss', 'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/F1', 'val/giou_loss', 'val/obj_loss', 'val/cls_loss' ] for x, tag in zip(list(mloss[:-1]) + list(results), tags): tb_writer.add_scalar(tag, x, epoch) # Update best mAP fi = utils.fitness(np.array(results).reshape( 1, -1)) # fitness_i = weighted combination of [P, R, mAP, F1] if fi > best_fitness: best_fitness = fi # Save model save = (not config['nosave']) or final_epoch if save: with open(results_file, 'r') as f: # create checkpoint ckpt = { 'epoch': epoch, 'best_fitness': best_fitness, 'training_results': f.read() } # Save last, best and delete checkpointer.save(last, **ckpt) if (best_fitness == fi) and not final_epoch: checkpointer.save(best, **ckpt) del ckpt # end epoch ================================================================================================= # end training print('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) torch.cuda.empty_cache() return results
def test( data, weights=None, batch_size=16, imgsz=640, conf_thres=0.001, iou_thres=0.6, # for NMS save_json=False, verbose=False, model=None, dataloader=None, logdir='./runs', merge=False): # Initialize/load model and set device if model is None: training = False device = torch_utils.select_device(opt.device, batch_size=batch_size) # Remove previous for f in glob.glob(os.path.join(logdir, 'test_batch*.jpg')): os.remove(f) # Load model model = torch.load( weights, map_location=device)['model'].float() # load to FP32 torch_utils.model_info(model) model.fuse() model.to(device) # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: # model = nn.DataParallel(model) else: # called by train.py training = True device = next(model.parameters()).device # get model device # Half half = device.type != 'cpu' and torch.cuda.device_count( ) == 1 # half precision only supported on single-GPU half = False if half: model.half() # to FP16 # Configure model.eval() with open(data) as f: data = yaml.load(f, Loader=yaml.FullLoader) # model dict nc = int(data['num_classes']) # number of classes iouv = torch.linspace(0.5, 0.95, 10).to(device) # iou vector for [email protected]:0.95 niou = iouv.numel() losser = YoloLoss(model) # Dataloader if dataloader is None: # not training merge = opt.merge # use Merge NMS img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img _ = model(img.half() if half else img ) if device.type != 'cpu' else None # run once path = data['test'] if opt.task == 'test' else data[ 'val'] # path to val/test images dataloader = kitti.create_dataloader(path, imgsz, batch_size, int(max(model.stride)), config=None, augment=False, cache=False, pad=0.5, rect=True)[0] seen = 0 names = data['names'] kitti8class = data_utils.kitti8_classes() s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', '[email protected]', '[email protected]:.95') p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0. loss = torch.zeros(3, device=device) jdict, stats, ap, ap_class = [], [], [], [] for batch_i, (img, targets, paths, shapes) in enumerate(tqdm.tqdm(dataloader, desc=s)): targets.delete_by_mask() targets.to_float32() targ = ParamList(targets.size, True) targ.copy_from(targets) img_id = targets.get_field('img_id') classes = targets.get_field('class') bboxes = targets.get_field('bbox') targets = torch.cat( [img_id.unsqueeze(-1), classes.unsqueeze(-1), bboxes], dim=-1) img = img.to(device) img = img.half() if half else img.float() # uint8 to fp16/32 # img /= 1.0 # 0 - 255 to 0.0 - 1.0 targets = targets.to(device) nb, _, height, width = img.shape # batch size, channels, height, width whwh = torch.Tensor([width, height, width, height]).to(device) # Disable gradients with torch.no_grad(): # Run model t = torch_utils.time_synchronized() inf_out, train_out = model(img) # inference and training outputs t0 += torch_utils.time_synchronized() - t # Compute loss if training: # if model has loss hyperparameters # loss += calc_loss([x.float() for x in train_out], targets, model)[1][:3] # GIoU, obj, cls loss += losser([x.float() for x in train_out], targ)[1][:3] # Run NMS t = torch_utils.time_synchronized() output = postprocess.apply_nms(inf_out, nc, conf_thres=conf_thres, iou_thres=iou_thres, merge=merge) t1 += torch_utils.time_synchronized() - t # Statistics per image for si, pred in enumerate(output): labels = targets[targets[:, 0] == si, 1:] nl = len(labels) tcls = labels[:, 0].tolist() if nl else [] # target class seen += 1 if pred is None: if nl: stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls)) continue # Append to text file # with open('test.txt', 'a') as file: # [file.write('%11.5g' * 7 % tuple(x) + '\n') for x in pred] # Clip boxes to image bounds utils.clip_coords(pred, (height, width)) # Append to pycocotools JSON dictionary if save_json: # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ... image_id = int(Path(paths[si]).stem.split('_')[-1]) box = pred[:, :4].clone() # xyxy utils.scale_coords(img[si].shape[1:], box, shapes[si][0], shapes[si][1]) # to original shape box = data_utils.xyxy2xywh(box) # xywh box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner for p, b in zip(pred.tolist(), box.tolist()): jdict.append({ 'image_id': image_id, 'category_id': kitti8class[int(p[5])], 'bbox': [round(x, 3) for x in b], 'score': round(p[4], 5) }) # Assign all predictions as incorrect correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool, device=device) if nl: detected = [] # target indices tcls_tensor = labels[:, 0] # target boxes tbox = data_utils.xywh2xyxy(labels[:, 1:5]) * whwh # Per target class for cls in torch.unique(tcls_tensor): ti = (cls == tcls_tensor).nonzero(as_tuple=False).view( -1) # prediction indices pi = (cls == pred[:, 5]).nonzero(as_tuple=False).view( -1) # target indices # Search for detections if pi.shape[0]: # Prediction to target ious ious, i = metrics_utils.box_iou( pred[pi, :4], tbox[ti]).max(1) # best ious, indices # Append detections for j in (ious > iouv[0]).nonzero(as_tuple=False): d = ti[i[j]] # detected target if d not in detected: detected.append(d) correct[ pi[j]] = ious[j] > iouv # iou_thres is 1xn if len( detected ) == nl: # all targets already located in image break # Append statistics (correct, conf, pcls, tcls) stats.append( (correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls)) # Plot images if batch_i < 1: f = os.path.join(logdir, 'test_batch%g_gt.jpg' % batch_i) # filename visual_utils.plot_images(img, targets, paths, f, names) # ground truth f = os.path.join(logdir, 'test_batch%g_pred.jpg' % batch_i) visual_utils.plot_images(img, utils.output_to_target( output, width, height), paths, f, names) # predictions # Compute statistics stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy if len(stats): p, r, ap, f1, ap_class = metrics_utils.ap_per_class(*stats) p, r, ap50, ap = p[:, 0], r[:, 0], ap[:, 0], ap.mean( 1) # [P, R, [email protected], [email protected]:0.95] mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean() nt = np.bincount(stats[3].astype(np.int64), minlength=nc) # number of targets per class else: nt = torch.zeros(1) # Print results pf = '%20s' + '%12.3g' * 6 # print format print(pf % ('all', seen, nt.sum(), mp, mr, map50, map)) # Print results per class if verbose and nc > 1 and len(stats): for i, c in enumerate(ap_class): print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i])) # Print speeds t = tuple(x / seen * 1E3 for x in (t0, t1, t0 + t1)) + (imgsz, imgsz, batch_size) # tuple if not training: print( 'Speed: %.1f/%.1f/%.1f ms inference/NMS/total per %gx%g image at batch-size %g' % t) # Save JSON if save_json and map50 and len(jdict): imgIds = [ int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files ] f = 'detections_val2017_%s_results.json' % \ (weights.split(os.sep)[-1].replace('.pt', '') if weights else '') # filename print('\nCOCO mAP with pycocotools... saving %s...' % f) with open(f, 'w') as file: json.dump(jdict, file) try: from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb cocoGt = COCO( glob.glob('../coco/annotations/instances_val*.json') [0]) # initialize COCO ground truth api cocoDt = cocoGt.loadRes(f) # initialize COCO pred api cocoEval = COCOeval(cocoGt, cocoDt, 'bbox') cocoEval.params.imgIds = imgIds # image IDs to evaluate cocoEval.evaluate() cocoEval.accumulate() cocoEval.summarize() map, map50 = cocoEval.stats[: 2] # update results ([email protected]:0.95, [email protected]) except: print( 'WARNING: pycocotools must be installed with numpy==1.17 to run correctly. ' 'See https://github.com/cocodataset/cocoapi/issues/356') # Return results model.float() # for training maps = np.zeros(nc) + map for i, c in enumerate(ap_class): maps[c] = ap[i] return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t