Download code/src/enhancements/mega_ensemble.py from savastakan/hitit-cuneiform-ocr: direct link, hf CLI and curl.
- Browser
- Download file 4.41 kB
-
https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/src/enhancements/mega_ensemble.py
- Command line
-
hf download hf://savastakan/hitit-cuneiform-ocr/code/src/enhancements/mega_ensemble.py
-
curl -L -o mega_ensemble.py https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/src/enhancements/mega_ensemble.py
4.41 kB
| #!/usr/bin/env python3 | |
| """Mega ensemble — union all trained heads by probabilistic averaging. | |
| Inputs: list of pt files, each containing {probs, targets, top1}. | |
| Optimizes weights via coordinate descent on val top1. | |
| """ | |
| import json, argparse, itertools | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| import numpy as np | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--probs', nargs='+', required=True, help='name:path pt files') | |
| ap.add_argument('--optimize', action='store_true') | |
| ap.add_argument('--output', required=True) | |
| args = ap.parse_args() | |
| names, probs, targets = [], [], None | |
| own_acc = [] | |
| for spec in args.probs: | |
| if ':' in spec: | |
| nm, pth = spec.split(':', 1) | |
| else: | |
| nm = Path(spec).stem; pth = spec | |
| if not Path(pth).exists(): | |
| print(f"skip missing {pth}"); continue | |
| d = torch.load(pth, map_location='cpu', weights_only=False) | |
| p = d.get('probs') | |
| if p is None: p = d.get('refined_probs') | |
| t = d.get('targets') | |
| if p is None or t is None: | |
| print(f"skip {pth}: no probs/targets"); continue | |
| if targets is None: | |
| targets = t | |
| elif not torch.equal(t, targets): | |
| print(f"{nm}: target mismatch (size {t.size(0)} vs {targets.size(0)}); skip") | |
| continue | |
| probs.append(p) | |
| names.append(nm) | |
| acc = (p.argmax(-1) == targets).float().mean().item() | |
| own_acc.append(acc) | |
| print(f"{nm}: top1={acc:.4f}") | |
| if not probs: | |
| print("No probs to ensemble"); return | |
| stacked = torch.stack(probs).double() # (M, N, C) | |
| M = stacked.size(0) | |
| def topk(w, k=1): | |
| w = torch.tensor(w, dtype=stacked.dtype).view(-1, 1, 1) | |
| p = (stacked * w).sum(0) | |
| if k == 1: return (p.argmax(-1) == targets).float().mean().item() | |
| _, tk = p.topk(k, -1) | |
| return sum(targets[i].item() in tk[i].tolist() for i in range(len(targets))) / len(targets) | |
| # Seed: own-acc proportional | |
| own = np.array(own_acc, dtype=np.float64) | |
| w_uni = np.ones(M) / M | |
| w_own = own / own.sum() | |
| best_w = w_own if topk(w_own) > topk(w_uni) else w_uni | |
| best_s = max(topk(w_own), topk(w_uni)) | |
| print(f"Seed top1: uniform={topk(w_uni):.4f}, own-acc={topk(w_own):.4f}") | |
| if args.optimize: | |
| step = 0.1 | |
| for it in range(100): | |
| improved = False | |
| for i, j in itertools.combinations(range(M), 2): | |
| for d in (step, -step): | |
| w = best_w.copy() | |
| w[i] += d; w[j] -= d | |
| if w.min() < 0: continue | |
| w = w / w.sum() | |
| a = topk(w) | |
| if a > best_s + 1e-6: | |
| best_w, best_s = w, a; improved = True | |
| if not improved: step *= 0.5 | |
| if step < 1e-4: break | |
| print(f"Optimized top1: {best_s:.4f}") | |
| # Final | |
| w_t = torch.tensor(best_w, dtype=stacked.dtype).view(-1, 1, 1) | |
| final_probs = (stacked * w_t).sum(0) | |
| top1 = (final_probs.argmax(-1) == targets).float().mean().item() | |
| _, top5 = final_probs.topk(5, -1) | |
| top5_acc = sum(targets[i].item() in top5[i].tolist() for i in range(len(targets))) / len(targets) | |
| # Selective metrics | |
| max_p = final_probs.max(-1).values | |
| pred = final_probs.argmax(-1) | |
| sel = {} | |
| for thr in (0.5, 0.6, 0.7, 0.8, 0.9): | |
| keep = max_p >= thr | |
| cov = keep.float().mean().item() | |
| acc = (pred[keep] == targets[keep]).float().mean().item() if keep.sum() else 0.0 | |
| sel[str(thr)] = {'selective_acc': acc, 'coverage': cov} | |
| print(f"=== MEGA ENSEMBLE ===") | |
| print(f"Top-1: {top1:.4f}, Top-5: {top5_acc:.4f}") | |
| for t, m in sel.items(): | |
| print(f" τ={t}: sel_acc={m['selective_acc']:.4f} cov={m['coverage']:.4f}") | |
| out = { | |
| 'weights': dict(zip(names, best_w.tolist())), | |
| 'top1': top1, 'top5': top5_acc, | |
| 'selective_metrics': sel, | |
| 'per_model_top1': dict(zip(names, own_acc)), | |
| 'n_val': len(targets), | |
| } | |
| Path(args.output).parent.mkdir(parents=True, exist_ok=True) | |
| json.dump(out, open(args.output, 'w'), indent=2) | |
| torch.save({'probs': final_probs, 'targets': targets}, | |
| args.output.replace('.json', '_probs.pt')) | |
| print(f"Saved → {args.output}") | |
| if __name__ == '__main__': | |
| main() | |