Sasha Malahov
example-repro: spec-faithful Qwen3-Emb+LoRA+prototype-head training on synthetic data
68a82f9 Download make_toy_data.py from Kodep/jev-topic-head-example: direct link, hf CLI and curl.
- Browser
- Download file 9.96 kB
-
https://huggingface.co/Kodep/jev-topic-head-example/resolve/main/make_toy_data.py
- Command line
-
hf download hf://Kodep/jev-topic-head-example/make_toy_data.py
-
curl -L -o make_toy_data.py https://huggingface.co/Kodep/jev-topic-head-example/resolve/main/make_toy_data.py
9.96 kB
| """Synthetic stand-in for Kodep/jev-topic-data (which is private and not yet pushed). | |
| Same structure as the spec: 56 topics with descriptions, 279 train / 85 val / 105 test | |
| items, skewed class counts, 14 topics with no training items, hard pairs sharing | |
| vocabulary. The TOPICS are ours and the text is machine-generated — treat the metrics | |
| as pipeline validation, not as a benchmark. | |
| """ | |
| import json, os, random | |
| TOPICS = [ | |
| # (name, description, keywords) — pairs listed in config hard_pairs share keywords | |
| ("soccer", "soccer practice, games and team news", ["soccer", "practice", "field", "game"]), | |
| ("sports_general", "general sports and athletic program news", ["sports", "practice", "game", "athletic"]), | |
| ("swim_club", "swim club sessions, meets and pool rules", ["swim", "pool", "meet", "lap"]), | |
| ("basketball", "basketball schedule and gym bookings", ["basketball", "gym", "court", "tournament"]), | |
| ("athletics_office", "athletics office announcements", ["athletic", "office", "booster"]), | |
| ("field_trip", "field trip itineraries, buses and permission", ["field trip", "bus", "itinerary", "chaperone"]), | |
| ("permission_slip", "permission slips and signature reminders", ["permission", "slip", "signature", "consent"]), | |
| ("lunch_menu", "weekly lunch menu and meal options", ["lunch", "menu", "meal", "cafeteria"]), | |
| ("cafeteria_rules", "cafeteria expectations and seating", ["cafeteria", "seating", "rules", "lunch"]), | |
| ("bake_sale", "bake sales and baked goods fundraisers", ["bake sale", "cookies", "sale", "fundraiser"]), | |
| ("fundraiser", "fundraisers, drives and donation goals", ["fundraiser", "donation", "goal", "sale"]), | |
| ("art_club", "art club projects, medium and shows", ["art", "paint", "project", "exhibit"]), | |
| ("crafts_club", "crafts club projects and supplies", ["crafts", "glue", "project", "supplies"]), | |
| ("drama_club", "drama club rehearsals and tickets", ["drama", "rehearsal", "play", "tickets"]), | |
| ("music_band", "concert band rehearsals and concerts", ["band", "concert", "performance", "instrument"]), | |
| ("choir", "choir practices and winter concert", ["choir", "concert", "performance", "sing"]), | |
| ("orchestra", "orchestra sectionals and repertoire", ["orchestra", "sectional", "violin", "concert"]), | |
| ("reading_week", "reading week challenges and logs", ["reading", "challenge", "log", "books"]), | |
| ("library", "library hours, returns and fines", ["library", "return", "books", "due"]), | |
| ("spelling_bee", "spelling bee rounds and registration", ["spelling", "bee", "round", "word"]), | |
| ("math_club", "math club contests and puzzles", ["math", "contest", "puzzle", "problem"]), | |
| ("math_olympiad", "math olympiad training and teams", ["math", "olympiad", "team", "contest"]), | |
| ("science_fair", "science fair projects and judging", ["science", "fair", "project", "judging"]), | |
| ("robotics", "robotics builds and competition", ["robotics", "robot", "build", "competition"]), | |
| ("coding_club", "coding club sessions and projects", ["coding", "app", "scratch", "project"]), | |
| ("history_day", "history day topics and papers", ["history", "paper", "topic", "exhibit"]), | |
| ("debate_club", "debate rounds and motions", ["debate", "round", "motion", "argument"]), | |
| ("chess_club", "chess club ladder and matches", ["chess", "match", "ladder", "tournament"]), | |
| ("homework", "homework policies and support", ["homework", "assignment", "due", "tutoring"]), | |
| ("grades", "grade reporting and conferences", ["grades", "report card", "conference", "gpa"]), | |
| ("attendance", "attendance windows and absences", ["attendance", "absence", "tardy", "excused"]), | |
| ("late_pickup", "late pickup procedures and fees", ["pickup", "late", "fee", "after school"]), | |
| ("early_dismissal", "early dismissal and snow schedules", ["dismissal", "early", "schedule", "snow"]), | |
| ("weather_closing", "weather closings and delays", ["closing", "delay", "weather", "snow"]), | |
| ("bus_route", "bus route changes and stops", ["bus", "route", "stop", "ride"]), | |
| ("carpool", "carpool coordination boards", ["carpool", "drivers", "roster", "pickup"]), | |
| ("flu_report", "illness, flu and nurse notes", ["flu", "nurse", "illness", "outbreak"]), | |
| ("allergies", "allergy alerts and food policies", ["allergy", "food", "nurse", "epipen"]), | |
| ("photo_day", "school photos and portrait packages", ["photo", "portrait", "package", "picture"]), | |
| ("dentist_note", "dental screening and vision checks", ["dental", "vision", "screening", "note"]), | |
| ("volunteer", "volunteer hours and signups", ["volunteer", "hours", "signup", "helper"]), | |
| ("pta_meeting", "PTA meetings and officers", ["pta", "meeting", "officer", "dues"]), | |
| ("textbook", "textbook and supply lists", ["textbook", "supply", "list", "checkout"]), | |
| ("locker", "locker assignments and rules", ["locker", "combination", "assignment", "hall"]), | |
| ("dress_code", "dress code and spirit days", ["dress code", "spirit", "attire", "jeans"]), | |
| ("concert_tickets", "event ticket sales and seating", ["tickets", "seating", "presale", "entry"]), | |
| ("yearbook", "yearbook ads, photos and delivery", ["yearbook", "ad", "delivery", "super senior"]), | |
| ("litter_pickup", "litter and grounds volunteer days", ["litter", "grounds", "cleanup", "volunteer"]), | |
| ("garden", "school garden planting and care", ["garden", "plant", "soil", "harvest"]), | |
| ("astronomy_night", "stargazing and planetarium trips", ["astronomy", "stars", "planetarium", "telescope"]), | |
| ("museums_late", "museum nights and docent tours", ["museum", "docent", "tour", "gallery"]), | |
| ("wellness", "wellness checks and health screenings", ["wellness", "health", "screening", "check"]), | |
| ("tech_help", "device help and wifi support", ["device", "wifi", "chromebook", "support"]), | |
| ("hall_pass", "hall pass and study hall policies", ["hall pass", "study hall", "pass", "policy"]), | |
| ("lunch_duty", "duty rosters and coverage", ["duty", "lunch duty", "coverage", "roster"]), | |
| ("staff_appreciation", "staff thank-you and Appreciation Week", ["staff", "appreciation", "thank you", "week"]), | |
| ] | |
| HARD = {"soccer", "sports_general", "swim_club", "art_club", "crafts_club", "music_band", | |
| "choir", "flu_report", "allergies", "bake_sale", "fundraiser"} | |
| FILLERS = [ | |
| "Please reply-all if anything changed since last week.", | |
| "Mrs. Delgado left this note for the whole distribution list.", | |
| "Reminder forwarded from the office with no further edits.", | |
| "The link in the original message was left out of this copy.", | |
| "This item repeats in three separate newsletters this month.", | |
| "A follow-up message may or may not arrive later.", | |
| "Someone on the list has asked to be removed after the fact.", | |
| "The date appears twice and the two dates disagree.", | |
| ] | |
| EVENTS = ["reminder", "update", "signup now", "schedule change", "final call", "quick note"] | |
| def make_text(rng, topic): | |
| kws = TOPIC_KW[topic] | |
| bits = [rng.choice(kws), rng.choice(kws)] | |
| rng.shuffle(bits) | |
| text = f"{topic.replace('_', ' ').title()}: {rng.choice(EVENTS)} - {bits[0]} and {bits[1]} this week." | |
| if rng.random() < 0.35: | |
| text += f" Also mention {rng.choice(kws)} briefly near the end." | |
| if rng.random() < 0.30: | |
| text += " " + rng.choice(FILLERS) | |
| if rng.random() < 0.12: | |
| text += " " + " ".join([rng.choice(FILLERS), rng.choice(FILLERS)]) | |
| return text | |
| def main(out_dir): | |
| rng = random.Random(42) | |
| os.makedirs(out_dir, exist_ok=True) | |
| global TOPIC_KW | |
| TOPIC_KW = {t[0]: t[2] for t in TOPICS} | |
| names = [t[0] for t in TOPICS] | |
| assert len(names) == 56, len(names) | |
| taxonomy = {name: desc for name, desc, _ in TOPICS} | |
| with open(os.path.join(out_dir, "topic_taxonomy.json"), "w") as f: | |
| json.dump(taxonomy, f, indent=2) | |
| # skewed train counts: a few big topics, a long tail, 14 topics with no train items | |
| train_pool = [n for n in names if n not in rng.sample(names, 14)] | |
| counts = {} | |
| remaining = 279 | |
| for i, n in enumerate(train_pool): | |
| w = rng.choice([1, 1, 2, 2, 3, 5]) if n in HARD else rng.choice([1, 1, 1, 2, 2, 4, 7]) | |
| counts[n] = max(1, w) | |
| scale = remaining / sum(counts.values()) | |
| counts = {n: max(1, int(c * scale + 0.5)) for n, c in counts.items()} | |
| # land exactly on 279, keeping every kept topic >= 1 item | |
| order = [n for n in train_pool if counts[n] > 1] | |
| i = 0 | |
| while sum(counts.values()) != 279: | |
| n = order[i % len(order)] | |
| if sum(counts.values()) < 279: | |
| counts[n] += 1 | |
| else: | |
| counts[n] -= 1 | |
| i += 1 | |
| idx = 0 | |
| def emit(split, topic): | |
| nonlocal idx | |
| idx += 1 | |
| return {"id": f"toy-{idx:04d}", "email_id": f"email-{idx:03d}", | |
| "text": make_text(rng, topic), "jev_topic": topic, "human_topic": None} | |
| train = [emit("train", n) for n, c in counts.items() for _ in range(c)] | |
| assert len(train) == 279, len(train) | |
| rng.shuffle(train) | |
| # val + test: cover all 56 topics; unseen topics appear only here | |
| val, test = [], [] | |
| for n in names: | |
| for _ in range(1): | |
| val.append(emit("validation", n)) | |
| while len(val) < 85: | |
| val.append(emit("validation", rng.choice(names))) | |
| for n in names: | |
| test.append(emit("test", n)) | |
| while len(test) < 105: | |
| test.append(emit("test", rng.choice(names))) | |
| for split, rows in [("train", train), ("validation", val), ("test", test)]: | |
| with open(os.path.join(out_dir, f"{split}.jsonl"), "w") as f: | |
| for r in rows: | |
| f.write(json.dumps(r) + "\n") | |
| print(split, len(rows)) | |
| big = sorted(counts.items(), key=lambda kv: -kv[1])[:5] | |
| print("busiest topics:", big, "| train topics:", len(counts), "/56") | |
| if __name__ == "__main__": | |
| import argparse | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--out", default="data") | |
| main(ap.parse_args().out) | |