Download code/save_models.py from meet5568/meet_models: direct link, hf CLI and curl.
- Browser
- Download file 5.95 kB
-
https://huggingface.co/meet5568/meet_models/resolve/main/code/save_models.py
- Command line
-
hf download hf://meet5568/meet_models/code/save_models.py
-
curl -L -o save_models.py https://huggingface.co/meet5568/meet_models/resolve/main/code/save_models.py
5.95 kB
| #!/usr/bin/env python3 | |
| """ | |
| Retrain and save the chosen theme classifiers. | |
| classify.py runs the full grid and keeps only scores and predictions. This script takes, for each | |
| of the 15 modality combinations and each of the 5 folds, the configuration that validation picked | |
| (results/classification/phaseb/runs.csv), trains it again with exactly the same fold split, | |
| validation split, standardization, seed and settings, and saves it. | |
| Output: <out>/<combination>/fold_<k>/ | |
| model.pt state_dict (PyTorch); rebuild with classify.build_model(run, modalities, in_dims, model_cfg) | |
| config.json modalities, method and settings, input sizes, model config, validation / test | |
| macro-F1 (and the test macro-F1 of the original GPU run for comparison) | |
| norm.npz per-modality mean and std of this fold's training songs (inputs must be | |
| standardized with them: (x - mean) / std) | |
| <out>/index.csv one row per saved model | |
| Combinations whose chosen configuration is late fusion get only config.json (it averages the | |
| saved single-modality models of the same fold). | |
| Usage (from the repository root; GPU recommended): | |
| python classification/save_models.py --out meet_models/classification | |
| On Kaggle (no arguments): SET_ENV="SAVE_JOBS=1" EMBED_FILES="classification/classify.py | |
| classification/config.yaml results/classification/phaseb/runs.csv" push_to_kaggle.sh ... | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| from itertools import combinations | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| from joblib import Parallel, delayed | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| sys.path.insert(0, "classification") # Kaggle: classify.py is written there (EMBED_FILES) | |
| import classify as C # noqa: E402 | |
| def as_bool(v): | |
| return str(v).strip().lower() in ("true", "1", "1.0") | |
| def fold_job(k, chosen, out, config_path): | |
| if not torch.cuda.is_available(): | |
| torch.set_num_threads(2) | |
| cfg = C.load_config(config_path, []) | |
| meta, audio, lyrics, folds, groups, y, soft = C.load_data(cfg) | |
| test = np.flatnonzero(folds == k) | |
| tr, va = C.inner_split(np.flatnonzero(folds != k), y, groups, cfg) | |
| split = {"train": tr, "val": va, "test": test} | |
| T = C.fold_tensors(k, audio, lyrics, split) | |
| sources = {**audio, "lyrics": lyrics[k] if lyrics.ndim == 3 else lyrics} | |
| mods_all = cfg["modalities"] | |
| rows = [] | |
| for n in range(1, len(mods_all) + 1): | |
| for mods in combinations(mods_all, n): | |
| mods = list(mods) | |
| name = "+".join(mods) | |
| row = chosen[(chosen.combo == name) & (chosen.fold == k)].iloc[0] | |
| d = os.path.join(out, name, f"fold_{k}") | |
| os.makedirs(d, exist_ok=True) | |
| info = {"combination": name, "modalities": mods, "fold": k, "method": row["method"], | |
| "themes": C.THEMES, "lyrics_encoder": f"theme-tuned BhashaEmbed, fold {k}", | |
| "original_val_f1": float(row["val_f1"]), "original_test_f1": float(row["test_f1"])} | |
| if row["method"] == "late": | |
| info["note"] = "late fusion: average the class probabilities of the single-modality models " \ | |
| f"{', '.join(f'{m}/fold_{k}' for m in mods)}" | |
| json.dump(info, open(os.path.join(d, "config.json"), "w"), indent=2) | |
| rows.append({**info, "test_f1": np.nan}) | |
| continue | |
| run = {"method": row["method"], "lr": float(row["lr"]), "soft": as_bool(row["soft"]), | |
| "moddrop": float(row["moddrop"]) if pd.notna(row["moddrop"]) else 0.0, | |
| "aux": as_bool(row["aux"])} | |
| r = C.train_eval(run, mods, T, y, soft, split, cfg) | |
| tf1, tacc = C.scores(y[test], r["pt"]) | |
| torch.save(r["model"].state_dict(), os.path.join(d, "model.pt")) | |
| np.savez(os.path.join(d, "norm.npz"), | |
| **{f"{m}_mean": sources[m][tr].mean(0) for m in mods}, | |
| **{f"{m}_std": np.where(sources[m][tr].std(0) < 1e-8, 1.0, sources[m][tr].std(0)) for m in mods}) | |
| info.update({"run": run, "in_dims": [int(T[m]["train"].shape[1]) for m in mods], | |
| "model_config": cfg["model"], "training": cfg["training"], "seed": cfg["seed"], | |
| "val_f1": float(r["val_f1"]), "test_f1": float(tf1), "test_accuracy": float(tacc), | |
| "epochs": int(r["epochs"]), "n_train": len(tr), "n_val": len(va), "n_test": len(test)}) | |
| json.dump(info, open(os.path.join(d, "config.json"), "w"), indent=2) | |
| rows.append({k2: v for k2, v in info.items() if not isinstance(v, (dict, list))}) | |
| print(f"fold {k} {name:30s} {run['method']:5s} test {tf1:.4f} (original {row['test_f1']:.4f})", flush=True) | |
| return rows | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Retrain and save the chosen classifiers") | |
| ap.add_argument("--out", default=os.environ.get("SAVE_OUT", "meet_models/classification")) | |
| ap.add_argument("--config", default="classification/config.yaml") | |
| ap.add_argument("--runs", default="results/classification/phaseb/runs.csv") | |
| ap.add_argument("--jobs", type=int, default=int(os.environ.get("SAVE_JOBS", 5)), | |
| help="folds trained in parallel (1 on a single GPU)") | |
| args = ap.parse_args() | |
| chosen = C.pick_by_val(pd.read_csv(args.runs)) | |
| out = Parallel(n_jobs=args.jobs)(delayed(fold_job)(k, chosen, args.out, args.config) for k in range(5)) | |
| df = pd.DataFrame([r for rows in out for r in rows]).sort_values(["combination", "fold"]) | |
| df.to_csv(os.path.join(args.out, "index.csv"), index=False) | |
| s = df.groupby("combination")[["test_f1", "original_test_f1"]].mean().sort_values("test_f1", ascending=False) | |
| print("\nmean test macro-F1 per combination (retrained on this machine vs original run):") | |
| print(s.round(4).to_string()) | |
| if __name__ == "__main__": | |
| main() | |