rudeparis commited on
Commit
0538485
·
verified ·
1 Parent(s): eaa16cb

Upload eval_holdout.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. eval_holdout.py +88 -0
eval_holdout.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Eval H1 adapters on the canonical 929-row held-out eval_set.jsonl.
2
+
3
+ This is the FROZEN production target — never touched during training.
4
+ """
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+ from mlx_lm import load, generate
9
+
10
+ INTENTS = sorted(json.loads(open("/Users/jean-patricksmith/digital/kingly/apps/production/naac/poc/deberta_intent/checkpoints-base/label_mapping.json").read())["intent2id"].keys())
11
+ SLOTS = ["ALTIMETER_SETTING", "ALTITUDE", "APPROACH_TYPE", "CALL_SIGN", "CLOCK_POSITION",
12
+ "DIRECTION", "DISTANCE", "FACILITY", "FIX", "FREQUENCY", "HEADING", "PATTERN_LEG",
13
+ "ROUTE", "RUNWAY", "SPEED", "TAXIWAY", "TIME", "TRANSPONDER_CODE", "TURN_DIRECTION"]
14
+
15
+ SYSTEM_PROMPT = (
16
+ "You are an ATC parser. Parse the air traffic control transmission into a JSON object.\n"
17
+ "OUTPUT FORMAT: {\"intent\": <one-of-enum>, \"slots\": {<SLOT_TYPE>: <value>}}\n"
18
+ f"\nINTENT MUST BE ONE OF: {', '.join(INTENTS)}\n"
19
+ f"\nSLOT TYPES (UPPERCASE): {', '.join(SLOTS)}\n"
20
+ "\nOutput ONLY the JSON object. No prose, no code fences."
21
+ )
22
+
23
+ import re
24
+
25
+ ADAPTER = sys.argv[1] if len(sys.argv) > 1 else "poc/llm-finetune/training/h1_adapter_v3"
26
+ SRC = Path(sys.argv[2] if len(sys.argv) > 2 else "poc/deberta_intent/data/eval_set.jsonl")
27
+ BASE = sys.argv[3] if len(sys.argv) > 3 else (
28
+ "mlx-community/Qwen3-4B-Instruct-2507-4bit" if "q3b" in ADAPTER.lower()
29
+ else "mlx-community/Qwen2.5-32B-Instruct-4bit"
30
+ )
31
+
32
+
33
+ def parse(text):
34
+ text = text.strip()
35
+ text = re.sub(r'<think>.*?</think>\s*', '', text, flags=re.DOTALL).strip()
36
+ if text.startswith("```"):
37
+ text = text.split("```")[1]
38
+ if text.startswith("json"):
39
+ text = text[4:]
40
+ try:
41
+ return json.loads(text.strip())
42
+ except Exception:
43
+ return None
44
+
45
+
46
+ def main():
47
+ print(f"loading {BASE} + {ADAPTER}")
48
+ model, tok = load(BASE, adapter_path=ADAPTER)
49
+ rows = [json.loads(l) for l in SRC.read_text().splitlines() if l.strip()]
50
+ print(f"held-out rows: {len(rows)}")
51
+
52
+ schema_ok = intent_em = slots_em = 0
53
+ by_intent = {}
54
+ for i, row in enumerate(rows):
55
+ text = row.get("text", "")
56
+ gold_intent = row.get("intent", "")
57
+ gold_slots = {k: v.get("value", "") for k, v in (row.get("slots") or {}).items() if isinstance(v, dict)}
58
+ msgs = [
59
+ {"role": "system", "content": SYSTEM_PROMPT},
60
+ {"role": "user", "content": text},
61
+ ]
62
+ prompt = tok.apply_chat_template(msgs, add_generation_prompt=True, tokenize=False)
63
+ out = generate(model, tok, prompt=prompt, max_tokens=200, verbose=False)
64
+ parsed = parse(out)
65
+ is_schema = bool(parsed and "intent" in parsed and "slots" in parsed)
66
+ is_intent = bool(parsed and parsed.get("intent") == gold_intent)
67
+ is_slots = bool(parsed and parsed.get("slots") == gold_slots)
68
+ schema_ok += int(is_schema); intent_em += int(is_intent); slots_em += int(is_slots)
69
+ by_intent.setdefault(gold_intent, [0, 0, 0]) # n, intent, slots
70
+ by_intent[gold_intent][0] += 1
71
+ by_intent[gold_intent][1] += int(is_intent)
72
+ by_intent[gold_intent][2] += int(is_slots)
73
+ if (i + 1) % 100 == 0:
74
+ print(f" ...{i+1}/{len(rows)}: intent={intent_em/(i+1):.1%}, slots={slots_em/(i+1):.1%}")
75
+
76
+ n = len(rows)
77
+ print(f"\n=== HOLD-OUT (eval_set.jsonl, 929 rows, NEVER trained on) ===")
78
+ print(f"adapter: {ADAPTER}")
79
+ print(f"schema_valid: {schema_ok}/{n} = {schema_ok/n:.1%}")
80
+ print(f"intent_em: {intent_em}/{n} = {intent_em/n:.1%}")
81
+ print(f"slots_em: {slots_em}/{n} = {slots_em/n:.1%}")
82
+ print(f"\n=== per-intent (top 15 by count) ===")
83
+ for intent, (n_i, i_em, s_em) in sorted(by_intent.items(), key=lambda kv: -kv[1][0])[:15]:
84
+ print(f" {intent:30s} n={n_i:4d} intent={i_em}/{n_i} ({i_em/n_i:.0%}) slots={s_em}/{n_i} ({s_em/n_i:.0%})")
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()