| """Benchmark different ensemble configs on GPU. Measures combined mAP score.""" |
| import json |
| import time |
| import itertools |
| import numpy as np |
| from pathlib import Path |
| from pycocotools.coco import COCO |
| from pycocotools.cocoeval import COCOeval |
|
|
| |
| import torch |
| _orig = torch.load |
| def _safe(*a, **kw): kw["weights_only"] = False; return _orig(*a, **kw) |
| torch.load = _safe |
|
|
| from ultralytics import YOLO |
| from ensemble_boxes import weighted_boxes_fusion |
|
|
| ANN = "input/train/annotations.json" |
| IMG_DIR = Path("input/train/images") |
|
|
| |
| coco_gt = COCO(ANN) |
| img_ids = sorted(coco_gt.getImgIds()) |
| img_infos = coco_gt.loadImgs(img_ids) |
|
|
| def get_predictions_single(model, imgsz=1280, conf=0.001, iou=0.6, max_det=500): |
| """Run single model, return predictions list.""" |
| preds = [] |
| for img_info in img_infos: |
| img_path = IMG_DIR / img_info["file_name"] |
| results = model.predict(str(img_path), verbose=False, imgsz=imgsz, |
| conf=conf, iou=iou, max_det=max_det, augment=False) |
| for r in results: |
| if r.boxes is None: continue |
| for box, sc, cl in zip(r.boxes.xyxy.cpu().numpy(), |
| r.boxes.conf.cpu().numpy(), |
| r.boxes.cls.cpu().numpy()): |
| x1, y1, x2, y2 = box |
| preds.append({ |
| "image_id": img_info["id"], "category_id": int(cl), |
| "bbox": [float(x1), float(y1), float(x2-x1), float(y2-y1)], |
| "score": float(sc) |
| }) |
| return preds |
|
|
| def get_predictions_ensemble(models, imgsz=1280, conf=0.001, iou=0.7, |
| max_det=500, wbf_iou=0.55, skip_thr=0.001): |
| """Run ensemble with WBF, return predictions list.""" |
| preds = [] |
| for img_info in img_infos: |
| img_path = IMG_DIR / img_info["file_name"] |
| all_boxes, all_scores, all_labels = [], [], [] |
| img_w, img_h = None, None |
|
|
| for model in models: |
| results = model.predict(str(img_path), verbose=False, imgsz=imgsz, |
| conf=conf, iou=iou, max_det=max_det, augment=False) |
| boxes, scores, labels = [], [], [] |
| for r in results: |
| if r.boxes is None: continue |
| if img_w is None: img_h, img_w = r.orig_shape |
| for box, sc, cl in zip(r.boxes.xyxy.cpu().numpy(), |
| r.boxes.conf.cpu().numpy(), |
| r.boxes.cls.cpu().numpy()): |
| x1, y1, x2, y2 = box |
| boxes.append([x1/img_w, y1/img_h, x2/img_w, y2/img_h]) |
| scores.append(float(sc)) |
| labels.append(int(cl)) |
| all_boxes.append(boxes if boxes else [[0,0,0,0]]) |
| all_scores.append(scores if scores else [0]) |
| all_labels.append(labels if labels else [0]) |
|
|
| if img_w is None: continue |
| fb, fs, fl = weighted_boxes_fusion(all_boxes, all_scores, all_labels, |
| iou_thr=wbf_iou, skip_box_thr=skip_thr) |
| for box, sc, lb in zip(fb, fs, fl): |
| x1, y1 = box[0]*img_w, box[1]*img_h |
| x2, y2 = box[2]*img_w, box[3]*img_h |
| w, h = x2-x1, y2-y1 |
| if w > 0 and h > 0: |
| preds.append({ |
| "image_id": img_info["id"], "category_id": int(lb), |
| "bbox": [float(x1), float(y1), float(w), float(h)], |
| "score": float(sc) |
| }) |
| return preds |
|
|
| def compute_score(preds): |
| """Compute 0.7*det_mAP + 0.3*cls_mAP.""" |
| if not preds: |
| return 0, 0, 0 |
|
|
| |
| if len(preds) > 49000: |
| preds.sort(key=lambda x: x["score"], reverse=True) |
| preds = preds[:49000] |
|
|
| gt_data = json.load(open(ANN)) |
|
|
| |
| det_preds = [dict(p, category_id=1) for p in preds] |
| det_gt = dict(gt_data) |
| det_gt["annotations"] = [dict(a, category_id=1) for a in gt_data["annotations"]] |
| det_gt["categories"] = [{"id": 1, "name": "product"}] |
| with open("/tmp/det_gt.json", "w") as f: json.dump(det_gt, f) |
| with open("/tmp/det_pr.json", "w") as f: json.dump(det_preds, f) |
|
|
| coco_d = COCO("/tmp/det_gt.json") |
| ev = COCOeval(coco_d, coco_d.loadRes("/tmp/det_pr.json"), "bbox") |
| ev.params.iouThrs = [0.5] |
| ev.evaluate(); ev.accumulate(); ev.summarize() |
| det_map = ev.stats[0] |
|
|
| |
| with open("/tmp/cls_pr.json", "w") as f: json.dump(preds, f) |
| coco_c = COCO(ANN) |
| ev2 = COCOeval(coco_c, coco_c.loadRes("/tmp/cls_pr.json"), "bbox") |
| ev2.params.iouThrs = [0.5] |
| ev2.evaluate(); ev2.accumulate(); ev2.summarize() |
| cls_map = ev2.stats[0] |
|
|
| combined = 0.7 * det_map + 0.3 * cls_map |
| return combined, det_map, cls_map |
|
|
| |
| print("Loading models...") |
| m1 = YOLO("bench/model1.onnx", task="detect") |
| m2 = YOLO("bench/model2.onnx", task="detect") |
| m3 = YOLO("bench/model3.onnx", task="detect") |
|
|
| |
| pt_models = {} |
| for pt in Path(".").glob("train_*/run/weights/best.pt"): |
| name = pt.parts[0] |
| pt_models[name] = YOLO(str(pt), task="detect") |
| print(f" Loaded {name}") |
|
|
| print(f"\nLoaded 3 ONNX + {len(pt_models)} PT models") |
| print("=" * 60) |
|
|
| results = [] |
|
|
| |
| print("\n--- SINGLE MODELS ---") |
| for name, model in [("model1(s123_1280_SGD)", m1), ("model2(s42_1024_AdamW)", m2), ("model3(s42_1280_AdamW)", m3)]: |
| t = time.time() |
| preds = get_predictions_single(model) |
| score, det, cls = compute_score(preds) |
| elapsed = time.time() - t |
| print(f"{name}: combined={score:.4f} det={det:.4f} cls={cls:.4f} preds={len(preds)} time={elapsed:.0f}s") |
| results.append((name, score, det, cls)) |
|
|
| |
| print("\n--- ENSEMBLE WBF IOU SWEEP ---") |
| for wbf_iou in [0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7]: |
| t = time.time() |
| preds = get_predictions_ensemble([m1, m2, m3], wbf_iou=wbf_iou) |
| score, det, cls = compute_score(preds) |
| elapsed = time.time() - t |
| print(f"wbf_iou={wbf_iou}: combined={score:.4f} det={det:.4f} cls={cls:.4f} preds={len(preds)} time={elapsed:.0f}s") |
| results.append((f"ensemble_wbf{wbf_iou}", score, det, cls)) |
|
|
| |
| print("\n--- MAX_DET SWEEP ---") |
| for max_det in [300, 500, 800, 1000]: |
| t = time.time() |
| preds = get_predictions_ensemble([m1, m2, m3], max_det=max_det, wbf_iou=0.55) |
| score, det, cls = compute_score(preds) |
| elapsed = time.time() - t |
| print(f"max_det={max_det}: combined={score:.4f} det={det:.4f} cls={cls:.4f} preds={len(preds)} time={elapsed:.0f}s") |
| results.append((f"maxdet_{max_det}", score, det, cls)) |
|
|
| |
| print("\n--- CONF SWEEP ---") |
| for conf in [0.0001, 0.001, 0.005, 0.01]: |
| t = time.time() |
| preds = get_predictions_ensemble([m1, m2, m3], conf=conf, wbf_iou=0.55) |
| score, det, cls = compute_score(preds) |
| elapsed = time.time() - t |
| print(f"conf={conf}: combined={score:.4f} det={det:.4f} cls={cls:.4f} preds={len(preds)} time={elapsed:.0f}s") |
| results.append((f"conf_{conf}", score, det, cls)) |
|
|
| |
| print("\n" + "=" * 60) |
| print("RANKED RESULTS:") |
| results.sort(key=lambda x: x[1], reverse=True) |
| for i, (name, score, det, cls) in enumerate(results): |
| marker = " <-- BEST" if i == 0 else "" |
| print(f" {i+1}. {name}: {score:.4f} (det={det:.4f} cls={cls:.4f}){marker}") |
|
|