| """
|
| Evaluate a BOTH-source (diverse) overlap model on each source separately.
|
|
|
| If training on both sources closes the shortcut, this model should score high on
|
| BOTH the wild and the studio test sets (gap ~ 0) — unlike the single-source model
|
| that collapses. GPU eval-only.
|
|
|
| python eval_diversity.py --ckpt outputs/checkpoints/overlap_both_s42/p1_best.pth --out_suffix s42
|
| """
|
| import sys, os, argparse, json
|
| from pathlib import Path
|
|
|
| _base = "/mnt/d/SpiceNet" if os.path.exists("/mnt/d/SpiceNet") else "D:/SpiceNet"
|
| sys.path.insert(0, _base)
|
|
|
| import numpy as np
|
| import torch
|
| from torch.utils.data import DataLoader
|
| from sklearn.metrics import f1_score
|
|
|
| from src.model import SpiceFusionNet
|
| from src.dataset import SpiceDataset, get_val_transform, load_manifest_splits
|
|
|
| ROOT = Path(_base)
|
|
|
|
|
| def _loader(paths, labels):
|
| return DataLoader(SpiceDataset(paths, labels, get_val_transform(), multimodal=False),
|
| batch_size=64, num_workers=2, pin_memory=True)
|
|
|
|
|
| @torch.no_grad()
|
| def _acc(model, loader, device, n):
|
| yt, yp = [], []
|
| for imgs, tex, col, labels in loader:
|
| yp.extend(model.forward_image(imgs.to(device)).argmax(1).cpu().tolist())
|
| yt.extend(labels.tolist())
|
| yt, yp = np.array(yt), np.array(yp)
|
| return float((yt == yp).mean()), float(f1_score(yt, yp, average="macro",
|
| labels=list(range(n)), zero_division=0))
|
|
|
|
|
| def main():
|
| ap = argparse.ArgumentParser()
|
| ap.add_argument("--ckpt", required=True)
|
| ap.add_argument("--out_suffix", required=True)
|
| args = ap.parse_args()
|
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| ss = load_manifest_splits(str(ROOT / "outputs/manifest_overlap_ss.json"))
|
| ins = load_manifest_splits(str(ROOT / "outputs/manifest_overlap_indian.json"))
|
| n = len(ss[1])
|
| model = SpiceFusionNet(num_classes=n).to(device)
|
| ck = torch.load(args.ckpt, map_location=device, weights_only=False)
|
| model.load_state_dict(ck["model_state"]); model.eval()
|
|
|
| acc_w, f1_w = _acc(model, _loader(*ss[0]["test"]), device, n)
|
| acc_s, f1_s = _acc(model, _loader(*ins[0]["test"]), device, n)
|
| out = {"acc_wild": acc_w, "acc_studio": acc_s, "gap_pp": round(abs(acc_s - acc_w) * 100, 2),
|
| "macro_f1_wild": f1_w, "macro_f1_studio": f1_s}
|
| json.dump(out, open(ROOT / "outputs" / f"diversity_{args.out_suffix}.json", "w"), indent=2)
|
| print(f"both-source: wild={acc_w*100:.1f}% studio={acc_s*100:.1f}% gap={out['gap_pp']} pp")
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|