Download training/scripts/semantic_overlap.py from SinhalaMMLU-SharedTask/team-nexus: direct link, hf CLI and curl.
- Browser
- Download file 3.24 kB
-
https://huggingface.co/SinhalaMMLU-SharedTask/team-nexus/resolve/main/training/scripts/semantic_overlap.py
- Command line
-
hf download hf://SinhalaMMLU-SharedTask/team-nexus/training/scripts/semantic_overlap.py
-
curl -L -o semantic_overlap.py https://huggingface.co/SinhalaMMLU-SharedTask/team-nexus/resolve/main/training/scripts/semantic_overlap.py
3.24 kB
| """Meaning-based overlap check between two MCQ sets (catches the same question in different wording, which the | |
| character n-gram check in scripts/overlap.py misses). | |
| python scripts/semantic_overlap.py --a sinhalammlu_dev --b data/sft/gmmlu_si_train.jsonl [--show 15] | |
| Embeds "question + options" with intfloat/multilingual-e5-large (covers Sinhala), cosine similarity, and reports for | |
| each A item its nearest B item. A reference scale is printed from pairs that are known copies (Global-MMLU questions | |
| repeated across MMLU subjects) and from random unrelated pairs. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from eval.common import cap_gpu_memory, load_env, read_jsonl # noqa: E402 | |
| from eval.run_eval import resolve_data # noqa: E402 | |
| MODEL = "intfloat/multilingual-e5-large" | |
| def text(r: dict) -> str: | |
| return "query: " + r["question"] + " " + " | ".join(r["options"]) | |
| def embed(texts: list[str], tok, model, bs: int = 64) -> np.ndarray: | |
| import torch | |
| out = [] | |
| for i in range(0, len(texts), bs): | |
| enc = tok(texts[i:i + bs], padding=True, truncation=True, max_length=512, return_tensors="pt").to("cuda") | |
| with torch.no_grad(): | |
| h = model(**enc).last_hidden_state | |
| m = enc["attention_mask"].unsqueeze(-1).to(h.dtype) | |
| v = (h * m).sum(1) / m.sum(1) | |
| out.append(torch.nn.functional.normalize(v.float(), dim=-1).cpu().numpy()) | |
| return np.concatenate(out) | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--a", required=True) | |
| ap.add_argument("--b", required=True) | |
| ap.add_argument("--show", type=int, default=15) | |
| args = ap.parse_args() | |
| load_env() | |
| cap_gpu_memory(20) | |
| import torch | |
| from transformers import AutoModel, AutoTokenizer | |
| tok = AutoTokenizer.from_pretrained(MODEL) | |
| model = AutoModel.from_pretrained(MODEL, dtype=torch.float16).cuda().eval() | |
| A, B = read_jsonl(resolve_data(args.a)), read_jsonl(resolve_data(args.b)) | |
| ea, eb = embed([text(r) for r in A], tok, model), embed([text(r) for r in B], tok, model) | |
| sims = ea @ eb.T | |
| best, idx = sims.max(1), sims.argmax(1) | |
| # Reference scale: known copies (validation items with a character-level copy in B) and random pairs | |
| rng = np.random.default_rng(0) | |
| rand = sims[rng.integers(0, len(A), 5000), rng.integers(0, len(B), 5000)] | |
| print(f"A={args.a} ({len(A)}) B={args.b} ({len(B)}) model={MODEL}") | |
| print(f"reference: random unrelated pairs median {np.median(rand):.3f}, 99th pct {np.percentile(rand, 99):.3f}") | |
| for q in (50, 90, 99, 100): | |
| print(f"nearest-neighbour similarity, {q:>3}th percentile: {np.percentile(best, q):.3f}") | |
| for t in (0.85, 0.90, 0.95): | |
| print(f"A items with a B neighbour >= {t}: {(best >= t).sum()}") | |
| for i in np.argsort(-best)[: args.show]: | |
| print(f"\nsim={best[i]:.3f}\n A {A[i]['uid']}: {A[i]['question'][:150]}\n options: {' | '.join(A[i]['options'])[:150]}" | |
| f"\n B {B[idx[i]]['uid']}: {B[idx[i]]['question'][:150]}\n options: {' | '.join(B[idx[i]]['options'])[:150]}") | |
| if __name__ == "__main__": | |
| main() | |