Download tools/full_score_combo.py from Karar-git/hack4dev-building-age-winners: direct link, hf CLI and curl.
- Browser
- Download file 7.71 kB
-
https://huggingface.co/Karar-git/hack4dev-building-age-winners/resolve/main/tools/full_score_combo.py
- Command line
-
hf download hf://Karar-git/hack4dev-building-age-winners/tools/full_score_combo.py
-
curl -L -o full_score_combo.py https://huggingface.co/Karar-git/hack4dev-building-age-winners/resolve/main/tools/full_score_combo.py
7.71 kB
| """ | |
| NOTE FOR THIS BUNDLE. Only one mode of this script is used here: | |
| python tools/full_score_combo.py zeroshot | |
| It rebuilds data/processed/zeroshot_xgb_dann_ams.npz, the zero-shot anchor of the | |
| FINAL-2 pipeline (tools/nnlab/model_create2.py): a teammate's XGBoost and DANN on | |
| the official 60 features, from tools/compare_teammate.py. It needs xgboost and | |
| torch and takes six to thirteen minutes on a 12-thread CPU. The combination described | |
| below is NOT FINAL-2, and its numbers are not quoted anywhere in this bundle. | |
| Our few-shot winners combined with the teammate's DANN and neighbour smoothing, | |
| scored on ALL of Amsterdam under the official protocol. | |
| Intended as the primary model when this script was written (2026-11-10). This | |
| bundle holds no time-stamped record of that, and Amsterdam scores of its | |
| ingredients (the upweighted LightGBM, the teammate's zero-shot pipeline) had | |
| already been seen. The primary model is | |
| "upweighted + DANN + smooth": | |
| p = (P_upweighted_lgbm + P_dann) / 2 -> neighbour smoothing (6 px) -> our rule | |
| Every ingredient was chosen on Madrid, not Amsterdam: | |
| * upweighted retrained LightGBM - notebook 3's design, 393 features; | |
| * DANN (60 official features, adversary on UNLABELLED Amsterdam) and the | |
| 50/50 probability average with a tree model - teammate's design, +0.014 on | |
| their Madrid lockbox; | |
| * neighbour smoothing r = 6 px, alpha 0.5 - teammate's choice on Madrid | |
| (+0.025 on their CV); ours (r = 2) also gained on Madrid; | |
| * decision rule tau 0.5 then the macro-F1 plug-in with the Madrid prior - | |
| chosen on Madrid by the shift harness. | |
| The other rows are ablations that show what each ingredient contributes. Some | |
| were added after Amsterdam results had been seen. | |
| One deliberate change from the teammate: the 3 DANN seeds are averaged into one | |
| probability (their lockbox reported seeds separately), so a protocol draw does not | |
| depend on which seed was used. | |
| Protocol: identical to tools/full_score.py. Seed 42, shots drawn with | |
| rng(42 + 1000 n + rep), shots removed from the scored set, and every draw scored on | |
| all remaining Amsterdam pixels. Smoothing uses only the scored pixels as | |
| neighbours (no shot, no label), as in the teammate's code. Amsterdam labels are | |
| read only to draw the shots and to score. | |
| python -I tools/full_score_combo.py zeroshot # cache XGB + DANN | |
| python -I tools/full_score_combo.py upweighted reports/combo_upweighted.json | |
| python -I tools/full_score_combo.py prototype reports/combo_prototype.json | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| import compare_teammate as CT # noqa: E402 (also puts src/ on the path, chdirs to root) | |
| import full_score as FS # noqa: E402 | |
| import numpy as np # noqa: E402 | |
| import pandas as pd # noqa: E402 | |
| import config as C # noqa: E402 | |
| import evaluation as E # noqa: E402 | |
| SEED, N_LIST, R_PX = 42, (5, 25, 50, 100, 200), 6 | |
| CACHE = C.PROC_DIR / "zeroshot_xgb_dann_ams.npz" | |
| def build_cache(d): | |
| """Zero-shot parts: they never see a shot, so they are fitted once.""" | |
| t0 = time.time() | |
| sw = CT.compute_sample_weight("balanced", d["ym"]) | |
| xgb = CT.XGBClassifier(objective="multi:softprob", tree_method="hist", device="cpu", | |
| eval_metric="mlogloss", random_state=42, **CT.XGB_PARAMS) | |
| xgb.fit(d["Xm60"], d["ym"] - 1, sample_weight=sw) | |
| P_xgb = xgb.predict_proba(d["Xa60"]) | |
| Xm_n, Xa_n = CT.region_norm(d["Xm60"]), CT.region_norm(d["Xa60"]) | |
| P_d = [] | |
| for s in CT.DANN_SEEDS: | |
| net = CT.train_dann(Xm_n, d["ym"], Xa_n, CT.DANN_EPOCHS, adversarial=True, seed=s) | |
| P_d.append(CT.dann_proba(net, Xa_n)) | |
| print(f"DANN seed {s} done {time.time() - t0:.0f}s", flush=True) | |
| np.savez(CACHE, P_xgb=P_xgb, P_dann_seeds=np.stack(P_d), pa=d["pa"]) | |
| print(f"cached -> {CACHE}", flush=True) | |
| def main(): | |
| mode = sys.argv[1] | |
| d = CT.load("first") | |
| if mode == "zeroshot": | |
| build_cache(d) | |
| return | |
| out_path = Path(sys.argv[2]) | |
| z = np.load(CACHE) | |
| assert (z["pa"] == d["pa"]).all() | |
| P_dann = z["P_dann_seeds"].mean(0) # seed-averaged | |
| P_final = (z["P_xgb"] + P_dann) / 2 # teammate's FINAL, zero-shot | |
| name, make, n_repeats = FS.VARIANTS[mode] | |
| X_mad, y_mad, X_ams, y_ams = d["Xm"], d["ym"], d["Xa"], d["ya"] | |
| src_prior = np.array([(y_mad == c).mean() for c in E.CLASSES]) | |
| coords = d["pa"].astype(float) | |
| base = "upweighted" if mode == "upweighted" else "prototype" | |
| def rules(P, y): | |
| out = {} | |
| out["argmax"] = E.macro_f1(y, np.array(E.CLASSES)[P.argmax(1)]) | |
| out["tau+plugin"] = E.macro_f1(y, E.adapt_pipeline(P, src_prior, tau=0.5)[0]) | |
| return out | |
| draws = [] | |
| if out_path.exists(): | |
| prev = json.loads(out_path.read_text()) | |
| if prev.get("key") == mode: | |
| draws = prev["draws"] | |
| print(f"resuming: {len(draws)} draws already saved", flush=True) | |
| done = {(r["n"], r["rep"]) for r in draws} | |
| mc = make() | |
| t_all = time.time() | |
| for n in N_LIST: | |
| for r in range(n_repeats): | |
| if (n, r) in done: | |
| continue | |
| t0 = time.time() | |
| rng = np.random.default_rng(SEED + 1000 * n + r) | |
| shot_idx = E.draw_shots(y_ams, n, rng) | |
| keep = np.setdiff1d(np.arange(len(y_ams)), shot_idx) | |
| fn = mc(X_mad, y_mad, X_ams, X_ams[shot_idx], y_ams[shot_idx]) | |
| Pb = np.asarray(fn(X_ams[keep]), dtype=float) | |
| ck, yk = coords[keep], y_ams[keep] | |
| combos = { | |
| f"{base}": Pb, | |
| f"{base} + smooth": CT.smooth(Pb, ck, R_PX), | |
| f"{base} + DANN": (Pb + P_dann[keep]) / 2, | |
| f"{base} + DANN + smooth": CT.smooth((Pb + P_dann[keep]) / 2, ck, R_PX), | |
| } | |
| if mode == "upweighted": # zero-shot references, cheap, scored per draw | |
| combos["their FINAL (zero-shot)"] = P_final[keep] | |
| combos["their FINAL + smooth (zero-shot)"] = CT.smooth(P_final[keep], ck, R_PX) | |
| rec = dict(n=n, rep=r, n_scored=int(len(keep))) | |
| for k, P in combos.items(): | |
| for rule, v in rules(P, yk).items(): | |
| rec[f"{k} | {rule}"] = float(v) | |
| rec["seconds"] = round(time.time() - t0, 1) | |
| draws.append(rec) | |
| print(f"n={n:<4d} rep={r:<2d} " + " ".join( | |
| f"{k}={rec[k + ' | tau+plugin']:.4f}" for k in combos) + | |
| f" ({rec['seconds']}s)", flush=True) | |
| out_path.write_text(json.dumps(dict(key=mode, variant=name, seed=SEED, | |
| complete=False, draws=draws), indent=1)) | |
| df = pd.DataFrame(draws) | |
| cols = [c for c in df.columns if " | " in c] | |
| g = df.groupby("n")[cols] | |
| summ = {c: {**{int(n): dict(mean=float(g.mean().loc[n, c]), std=float(g.std(ddof=1).loc[n, c])) | |
| for n in N_LIST}, | |
| "average": float(g.mean()[c].mean()), "peak": float(g.mean()[c].max())} | |
| for c in cols} | |
| out_path.write_text(json.dumps(dict(key=mode, variant=name, seed=SEED, complete=True, | |
| minutes=round((time.time() - t_all) / 60, 1), | |
| summary=summ, draws=draws), indent=1)) | |
| tbl = pd.DataFrame({c: {**{n: summ[c][n]["mean"] for n in N_LIST}, | |
| "avg": summ[c]["average"]} for c in cols}).T | |
| print("\n" + tbl.round(4).to_string(), flush=True) | |
| if __name__ == "__main__": | |
| main() | |