| """ |
| MMLU data construction for Claim 5 (paper Section 5.2 / Appendix B.2). |
| |
| Builds, deterministically (seed 0): |
| * a 1,000-example Humanities fine-tuning set, |
| * a 4x250 = 1,000-example evaluation set (Humanities / Social Sciences / |
| STEM / Other) that is DISJOINT from the fine-tuning set, |
| * 7 few-shot exemplars per subject drawn from the MMLU `validation` split |
| (topped up from `dev` when a subject has fewer than 7 validation rows), |
| so no exemplar can ever appear in the fine-tuning set or the eval set. |
| |
| The paper fine-tunes Qwen2.5-3B-Instruct on "the Humanities subset of MMLU, |
| which contains 1,000 training examples" and evaluates on "the MMLU test set |
| across four categories ... consisting of 1,000 examples in total". MMLU ships |
| no dedicated Humanities train split, so we carve the 1,000 fine-tuning rows out |
| of the Humanities *test* pool and hold the 250 evaluation rows out of it. |
| """ |
| import random |
|
|
| HUMANITIES = [ |
| "formal_logic", "high_school_european_history", "high_school_us_history", |
| "high_school_world_history", "international_law", "jurisprudence", |
| "logical_fallacies", "moral_disputes", "moral_scenarios", "philosophy", |
| "prehistory", "professional_law", "world_religions", |
| ] |
| STEM = [ |
| "abstract_algebra", "astronomy", "college_biology", "college_chemistry", |
| "college_computer_science", "college_mathematics", "college_physics", |
| "computer_security", "conceptual_physics", "electrical_engineering", |
| "elementary_mathematics", "high_school_biology", "high_school_chemistry", |
| "high_school_computer_science", "high_school_mathematics", |
| "high_school_physics", "high_school_statistics", "machine_learning", |
| ] |
| SOCIAL = [ |
| "econometrics", "high_school_geography", |
| "high_school_government_and_politics", "high_school_macroeconomics", |
| "high_school_microeconomics", "high_school_psychology", "human_sexuality", |
| "professional_psychology", "public_relations", "security_studies", |
| "sociology", "us_foreign_policy", |
| ] |
| OTHER = [ |
| "anatomy", "business_ethics", "clinical_knowledge", "college_medicine", |
| "global_facts", "human_aging", "management", "marketing", |
| "medical_genetics", "miscellaneous", "nutrition", "professional_accounting", |
| "professional_medicine", "virology", |
| ] |
| CATEGORIES = { |
| "Humanities": HUMANITIES, |
| "Social Sciences": SOCIAL, |
| "STEM": STEM, |
| "Other": OTHER, |
| } |
| SUBJ2CAT = {s: c for c, ss in CATEGORIES.items() for s in ss} |
| LETTERS = ["A", "B", "C", "D"] |
|
|
| N_TRAIN = 1000 |
| N_EVAL_PER_CAT = 250 |
| N_SHOT = 7 |
| DATA_SEED = 0 |
|
|
|
|
| def _pretty(subject): |
| return subject.replace("_", " ") |
|
|
|
|
| def question_text(row): |
| """Zero-shot user turn for one MMLU row.""" |
| lines = [ |
| f"The following is a multiple choice question about {_pretty(row['subject'])}.", |
| "", |
| row["question"].strip(), |
| ] |
| for letter, choice in zip(LETTERS, row["choices"]): |
| lines.append(f"{letter}. {str(choice).strip()}") |
| lines.append("") |
| lines.append("Answer with the letter of the correct option (A, B, C, or D).") |
| return "\n".join(lines) |
|
|
|
|
| def answer_text(row): |
| return LETTERS[int(row["answer"])] |
|
|
|
|
| def _rows(ds_split, subjects): |
| out = [] |
| for r in ds_split: |
| if r["subject"] in subjects: |
| out.append({ |
| "subject": r["subject"], |
| "question": r["question"], |
| "choices": list(r["choices"]), |
| "answer": int(r["answer"]), |
| }) |
| return out |
|
|
|
|
| def build(cache_dir=None): |
| from datasets import load_dataset |
| dd = load_dataset("cais/mmlu", "all", cache_dir=cache_dir) |
| test, val, dev = dd["test"], dd["validation"], dd["dev"] |
|
|
| all_subjects = sorted(SUBJ2CAT) |
|
|
| |
| val_rows, dev_rows = {}, {} |
| for r in _rows(val, set(all_subjects)): |
| val_rows.setdefault(r["subject"], []).append(r) |
| for r in _rows(dev, set(all_subjects)): |
| dev_rows.setdefault(r["subject"], []).append(r) |
| shots = {} |
| for s in all_subjects: |
| pool = list(val_rows.get(s, [])) |
| random.Random(DATA_SEED).shuffle(pool) |
| picked = pool[:N_SHOT] |
| if len(picked) < N_SHOT: |
| picked = picked + dev_rows.get(s, [])[: N_SHOT - len(picked)] |
| assert len(picked) == N_SHOT, (s, len(picked)) |
| shots[s] = picked |
|
|
| |
| eval_sets, used_hum = {}, set() |
| for ci, (cat, subjects) in enumerate(CATEGORIES.items()): |
| pool = _rows(test, set(subjects)) |
| idx = list(range(len(pool))) |
| random.Random(DATA_SEED + 100 * (ci + 1)).shuffle(idx) |
| chosen = idx[:N_EVAL_PER_CAT] |
| eval_sets[cat] = [pool[i] for i in chosen] |
| if cat == "Humanities": |
| hum_pool = pool |
| used_hum = set(chosen) |
|
|
| |
| remaining = [i for i in range(len(hum_pool)) if i not in used_hum] |
| random.Random(DATA_SEED + 7).shuffle(remaining) |
| train_rows = [hum_pool[i] for i in remaining[:N_TRAIN]] |
| assert len(train_rows) == N_TRAIN |
|
|
| return {"train": train_rows, "eval": eval_sets, "shots": shots} |
|
|
|
|
| def chat_messages(row, shots=None): |
| """Multi-turn chat prompt. `shots` = list of exemplar rows (same subject).""" |
| msgs = [] |
| for ex in (shots or []): |
| msgs.append({"role": "user", "content": question_text(ex)}) |
| msgs.append({"role": "assistant", "content": answer_text(ex)}) |
| msgs.append({"role": "user", "content": question_text(row)}) |
| return msgs |
|
|