Download object_detection/train.py from Ishaank18/aifh: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/Ishaank18/aifh/resolve/main/object_detection/train.py
- Command line
-
hf download hf://Ishaank18/aifh/object_detection/train.py
-
curl -L -o train.py https://huggingface.co/Ishaank18/aifh/resolve/main/object_detection/train.py
10.9 kB
| """ | |
| train.py -- Part B, Section 3.3 (model training + loss-function ablation). | |
| Two arms of the ablation, identical in every other respect: | |
| python train.py --loss ce --epochs 15 | |
| python train.py --loss focal --epochs 15 --focal-alpha 0.25 --focal-gamma 2.0 | |
| python train.py --loss weighted_ce --fg-weight 4.0 --epochs 15 # optional third arm | |
| Model selection: best validation mAP@0.50 (not loss), with early stopping. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import math | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| from common import (DEFAULT_IMG_SIZE, make_grad_scaler, autocast, DET_SPLIT_CSV, METRICS_DIR, PLOTS_DIR, | |
| SEED, SEED_TAG, WEIGHTS_DIR, KvasirDetectionDataset, _mpl, | |
| average_precision, coco_map, get_device, make_loader, | |
| progress, save_json, set_seed) | |
| from model import build_detector, parameter_report, set_roi_loss | |
| def run_tag(args) -> str: | |
| t = f"det_{args.arch}_{args.loss}" | |
| if args.loss == "focal": | |
| t += f"-a{args.focal_alpha}g{args.focal_gamma}" | |
| if args.tag: | |
| t += f"_{args.tag}" | |
| return f"{t}_{SEED_TAG}" | |
| # --------------------------------------------------------------------------- | |
| def run_inference(model, loader, device): | |
| """Returns (preds, gts) in the flat dict format the metric helpers expect.""" | |
| model.eval() | |
| preds, gts = [], [] | |
| for images, targets in progress(loader, desc="infer"): | |
| images = [im.to(device) for im in images] | |
| outs = model(images) | |
| for o, t in zip(outs, targets): | |
| preds.append({"boxes": o["boxes"].detach().cpu().numpy(), | |
| "scores": o["scores"].detach().cpu().numpy(), | |
| "labels": o["labels"].detach().cpu().numpy()}) | |
| gts.append({"boxes": t["boxes"].numpy(), "stem": t["stem"]}) | |
| return preds, gts | |
| def warmup_lr(optimizer, base_lr: float, step: int, warmup_steps: int): | |
| if warmup_steps <= 0 or step >= warmup_steps: | |
| return | |
| f = (step + 1) / warmup_steps | |
| for g in optimizer.param_groups: | |
| g["lr"] = base_lr * f | |
| # --------------------------------------------------------------------------- | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Kvasir-SEG detector training (seed 16)") | |
| ap.add_argument("--arch", default="fasterrcnn_resnet50_fpn", | |
| choices=["fasterrcnn_resnet50_fpn", "retinanet_resnet50_fpn"]) | |
| ap.add_argument("--loss", default="ce", choices=["ce", "focal", "weighted_ce"], | |
| help="ROI-head classification loss (the ablation variable)") | |
| ap.add_argument("--focal-alpha", type=float, default=0.25) | |
| ap.add_argument("--focal-gamma", type=float, default=2.0) | |
| ap.add_argument("--fg-weight", type=float, default=4.0) | |
| ap.add_argument("--epochs", type=int, default=15) | |
| ap.add_argument("--bs", type=int, default=4) | |
| ap.add_argument("--lr", type=float, default=5e-3) | |
| ap.add_argument("--optimizer", default="sgd", choices=["sgd", "adamw"]) | |
| ap.add_argument("--momentum", type=float, default=0.9) | |
| ap.add_argument("--wd", type=float, default=5e-4) | |
| ap.add_argument("--img-size", type=int, default=DEFAULT_IMG_SIZE) | |
| ap.add_argument("--trainable-layers", type=int, default=3, | |
| help="ResNet stages left unfrozen (0-5)") | |
| ap.add_argument("--no-augment", action="store_true") | |
| ap.add_argument("--no-pretrained", action="store_true") | |
| ap.add_argument("--warmup-frac", type=float, default=0.1, | |
| help="fraction of the first epoch used for linear LR warmup") | |
| ap.add_argument("--patience", type=int, default=5) | |
| ap.add_argument("--min-delta", type=float, default=1e-4) | |
| ap.add_argument("--clip-grad", type=float, default=10.0) | |
| ap.add_argument("--workers", type=int, default=2) | |
| ap.add_argument("--amp", action="store_true", default=True) | |
| ap.add_argument("--tag", default="") | |
| args = ap.parse_args() | |
| set_seed(SEED) | |
| device = get_device() | |
| tag = run_tag(args) | |
| loss_desc = set_roi_loss(args.loss, alpha=args.focal_alpha, | |
| gamma=args.focal_gamma, fg_weight=args.fg_weight) | |
| print(json.dumps({"run": tag, "seed": SEED, "device": str(device), | |
| "loss": loss_desc, **vars(args)}, indent=2)) | |
| ds_tr = KvasirDetectionDataset(DET_SPLIT_CSV, "train", augment=not args.no_augment, | |
| img_size=args.img_size) | |
| ds_va = KvasirDetectionDataset(DET_SPLIT_CSV, "val", augment=False, | |
| img_size=args.img_size) | |
| print(f"[data] train={len(ds_tr)} val={len(ds_va)} img_size={args.img_size} " | |
| f"augment={not args.no_augment}") | |
| dl_tr = make_loader(ds_tr, args.bs, shuffle=True, num_workers=args.workers) | |
| dl_va = make_loader(ds_va, max(1, args.bs), shuffle=False, num_workers=args.workers) | |
| model = build_detector(args.arch, pretrained_backbone=not args.no_pretrained, | |
| img_size=args.img_size, | |
| trainable_backbone_layers=args.trainable_layers).to(device) | |
| print(f"[model] {args.arch}: {parameter_report(model)}") | |
| params = [p for p in model.parameters() if p.requires_grad] | |
| if args.optimizer == "sgd": | |
| opt = torch.optim.SGD(params, lr=args.lr, momentum=args.momentum, weight_decay=args.wd) | |
| else: | |
| opt = torch.optim.AdamW(params, lr=args.lr, weight_decay=args.wd) | |
| sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=args.epochs, | |
| eta_min=args.lr * 0.01) | |
| scaler = make_grad_scaler(enabled=(device.type == "cuda" and args.amp)) | |
| warmup_steps = int(args.warmup_frac * max(1, len(dl_tr))) | |
| hist, best, bad, gstep = [], -1.0, 0, 0 | |
| ckpt_path = WEIGHTS_DIR / f"{tag}.pt" | |
| t0 = time.time() | |
| for ep in range(1, args.epochs + 1): | |
| model.train() | |
| agg, n = {}, 0 | |
| for images, targets in progress(dl_tr, desc=f"ep{ep} train"): | |
| images = [im.to(device) for im in images] | |
| targets = [{k: (v.to(device) if torch.is_tensor(v) else v) | |
| for k, v in t.items()} for t in targets] | |
| if gstep < warmup_steps: | |
| warmup_lr(opt, args.lr, gstep, warmup_steps) | |
| opt.zero_grad(set_to_none=True) | |
| with autocast(enabled=scaler.is_enabled()): | |
| losses = model(images, targets) | |
| total = sum(losses.values()) | |
| if not math.isfinite(float(total.detach())): | |
| print(f"[warn] non-finite loss at step {gstep}, skipping batch") | |
| gstep += 1 | |
| continue | |
| scaler.scale(total).backward() | |
| if args.clip_grad: | |
| scaler.unscale_(opt) | |
| torch.nn.utils.clip_grad_norm_(params, args.clip_grad) | |
| scaler.step(opt); scaler.update() | |
| for k, v in losses.items(): | |
| agg[k] = agg.get(k, 0.0) + float(v) | |
| agg["total"] = agg.get("total", 0.0) + float(total) | |
| n += 1 | |
| gstep += 1 | |
| train_losses = {k: v / max(1, n) for k, v in agg.items()} | |
| preds, gts = run_inference(model, dl_va, device) | |
| ap50 = average_precision(preds, gts, 0.5)["ap"] | |
| ap75 = average_precision(preds, gts, 0.75)["ap"] | |
| sched.step() | |
| row = {"epoch": ep, **{f"loss_{k}": v for k, v in train_losses.items()}, | |
| "val_mAP50": float(ap50), "val_mAP75": float(ap75), | |
| "lr": opt.param_groups[0]["lr"]} | |
| hist.append(row) | |
| print(f"[{tag}] ep {ep:03d} | loss {train_losses.get('total', 0):.4f} " | |
| f"(cls {train_losses.get('loss_classifier', 0):.4f}, " | |
| f"box {train_losses.get('loss_box_reg', 0):.4f}) " | |
| f"| val mAP@50 {ap50:.4f} | mAP@75 {ap75:.4f}") | |
| if ap50 > best + args.min_delta: | |
| best, bad = ap50, 0 | |
| torch.save({"model_state": model.state_dict(), "args": vars(args), | |
| "epoch": ep, "val_mAP50": float(ap50), "seed": SEED, | |
| "tag": tag, "loss_description": loss_desc}, ckpt_path) | |
| print(f" -> new best, saved {ckpt_path.name}") | |
| else: | |
| bad += 1 | |
| if bad >= args.patience: | |
| print(f"[{tag}] early stop after {ep} epochs") | |
| break | |
| summary = {"tag": tag, "seed": SEED, "best_val_mAP50": float(best), | |
| "epochs_run": len(hist), "minutes": round((time.time() - t0) / 60, 2), | |
| "loss_description": loss_desc, "config": vars(args), | |
| "parameter_report": parameter_report(model), "history": hist, | |
| "training_recipe": { | |
| "architecture": args.arch, | |
| "input_resolution": f"{args.img_size}x{args.img_size} (letterbox-free square resize)", | |
| "annotation_format": "xyxy absolute pixels + label 1 (polyp), " | |
| "converted from kvasir bbox json", | |
| "optimizer": args.optimizer, "base_lr": args.lr, | |
| "lr_schedule": f"linear warmup over {warmup_steps} steps, then cosine to " | |
| f"{args.lr * 0.01:.2e}", | |
| "batch_size": args.bs, "weight_decay": args.wd, | |
| "augmentation": "none" if args.no_augment else | |
| "random h/v flip + brightness/contrast + saturation jitter", | |
| "inference_score_threshold": 0.05, | |
| "nms_iou_threshold": 0.5, | |
| "stopping_rule": f"early stop on val mAP@0.50, patience={args.patience}, " | |
| f"min_delta={args.min_delta}; best checkpoint restored"}} | |
| save_json(summary, METRICS_DIR / f"train_{tag}.json") | |
| plt = _mpl() | |
| fig, axes = plt.subplots(1, 2, figsize=(11, 4.2)) | |
| ep_ = [h["epoch"] for h in hist] | |
| for k in [k for k in hist[0] if k.startswith("loss_")]: | |
| axes[0].plot(ep_, [h[k] for h in hist], marker="o", ms=3, label=k.replace("loss_", "")) | |
| axes[0].set_xlabel("epoch"); axes[0].set_ylabel("loss"); axes[0].legend(fontsize=8) | |
| axes[0].set_title(f"{tag}: training losses") | |
| axes[1].plot(ep_, [h["val_mAP50"] for h in hist], marker="o", ms=3, label="val mAP@0.50") | |
| axes[1].plot(ep_, [h["val_mAP75"] for h in hist], marker="s", ms=3, label="val mAP@0.75") | |
| axes[1].set_xlabel("epoch"); axes[1].legend(); axes[1].grid(alpha=0.3) | |
| axes[1].set_title(f"{tag}: validation mAP") | |
| fig.tight_layout(); fig.savefig(PLOTS_DIR / f"curves_{tag}.png", dpi=160); plt.close(fig) | |
| print(f"[saved] {PLOTS_DIR / f'curves_{tag}.png'}") | |
| print(f"[done] best val mAP@0.50 = {best:.4f} | weights: {ckpt_path}") | |
| if __name__ == "__main__": | |
| main() | |