Sasha Malahov
example-repro: spec-faithful Qwen3-Emb+LoRA+prototype-head training on synthetic data
68a82f9 Download evaluate.py from Kodep/jev-topic-head-example: direct link, hf CLI and curl.
- Browser
- Download file 3.47 kB
-
https://huggingface.co/Kodep/jev-topic-head-example/resolve/main/evaluate.py
- Command line
-
hf download hf://Kodep/jev-topic-head-example/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/Kodep/jev-topic-head-example/resolve/main/evaluate.py
3.47 kB
| """Evaluate a trained run: top-1 vs jev_topic, macro-F1, per-item dump, confidence buckets. | |
| Mirrors the eval/ schema promised in jev-topic-head (per_item jsonl + headline numbers). | |
| No rules_table.json exists in this example, so show/hide is skipped (it's a fixed | |
| lookup table downstream of the topic, not part of the head). | |
| """ | |
| import argparse, json, os | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| import yaml | |
| from sklearn.metrics import f1_score | |
| from transformers import AutoModel, AutoTokenizer | |
| from peft import PeftModel | |
| from train_head import PrototypeHead, encode, read_jsonl | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--config", default="config.yaml") | |
| ap.add_argument("--run", required=True, help="e.g. runs/seed0") | |
| args = ap.parse_args() | |
| cfg = yaml.safe_load(open(args.config)) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| dtype = torch.bfloat16 if cfg["train"]["dtype"] == "bf16" else torch.float32 | |
| hs = torch.load(os.path.join(args.run, "head", "head.pt"), map_location="cpu") | |
| topics = hs["topics"] | |
| topic2id = {t: i for i, t in enumerate(topics)} | |
| tok = AutoTokenizer.from_pretrained(cfg["base_model"]) | |
| tok.padding_side = "right" | |
| base = AutoModel.from_pretrained(hs.get("base_model", cfg["base_model"]), | |
| torch_dtype=dtype).to(device) | |
| base.config.use_cache = False | |
| model = PeftModel.from_pretrained(base, os.path.join(args.run, "lora")) | |
| head = PrototypeHead(hs["protos"], float(hs["scale"])) | |
| head.load_state_dict({"protos": hs["protos"], "scale": hs["scale"]}) | |
| head = head.to(device).eval() | |
| rows = read_jsonl(os.path.join(cfg["data"]["dir"], cfg["data"]["test"])) | |
| texts = [r["text"] for r in rows] | |
| logits, BATCH = [], 32 | |
| with torch.no_grad(): | |
| for i in range(0, len(texts), BATCH): | |
| v = encode(model, tok, texts[i:i + BATCH], cfg["max_length"], device) | |
| logits.append(head.logits(v).float().cpu()) | |
| logits = torch.cat(logits) | |
| probs = F.softmax(logits, dim=-1) | |
| conf, pred = probs.max(-1) | |
| pred = pred.numpy() | |
| gold = np.array([topic2id[r["jev_topic"]] for r in rows]) | |
| per_item = [] | |
| for r, p, c in zip(rows, pred, conf.numpy()): | |
| per_item.append({"id": r["id"], "model_topic": topics[p], | |
| "jev_topic": r["jev_topic"], "confidence": round(float(c), 4), | |
| "agree": bool(p == topic2id[r["jev_topic"]])}) | |
| os.makedirs(os.path.join(args.run, "eval"), exist_ok=True) | |
| with open(os.path.join(args.run, "eval", "per_item_test.jsonl"), "w") as f: | |
| for r in per_item: | |
| f.write(json.dumps(r) + "\n") | |
| top1 = float((pred == gold).mean()) | |
| mf1 = float(f1_score(gold, pred, average="macro", zero_division=0, | |
| labels=list(range(len(topics))))) | |
| conf_all = conf.numpy() | |
| summary = {"n": len(rows), "top1_vs_jev": round(top1, 4), | |
| "macro_f1_vs_jev": round(mf1, 4), | |
| "conf_lt_05": int((conf_all < 0.5).sum()), | |
| "conf_gte_09": int((conf_all >= 0.9).sum()), | |
| "errors_lt_05": int(((pred != gold) & (conf_all < 0.5)).sum()), | |
| "errors_gte_09": int(((pred != gold) & (conf_all >= 0.9)).sum())} | |
| json.dump(summary, open(os.path.join(args.run, "eval", "summary.json"), "w"), indent=2) | |
| print(json.dumps(summary, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |