Karar-git's picture
Add FINAL-2 pipeline, overnight search scripts and results (11 Nov 2026)
2c2dd6c verified
Raw History Blame Contribute Delete
4.38 kB
"""
modelCreate for the round-2 pick (FINAL-2): two models and one fixed rule.
1. zero-shot anchor teammate's XGBoost + DANN, trained on Madrid labels and
UNLABELLED Amsterdam features (cached probabilities,
data/processed/zeroshot_xgb_dann_ams.npz)
2. lgbm_feda LightGBM on Madrid + the labelled shots (weight 30, classes
balanced, each city robust-z scaled with its own median/IQR)
with Daume's feature augmentation: the top 120 features get
a Madrid-only copy and an Amsterdam-only copy
3. ensemble log p = w0 * log p_zero_shot + (1 - w0) * log p_feda, w0 = 10 / (n + 10)
4. decision neighbour smoothing (r = 6 px, alpha 0.5, shots excluded), then
tau-0.5 logit adjustment + macro-F1 plug-in rule
(spatial=False skips the smoothing: no coordinates needed)
from model_create2 import model_create
m = model_create(shot_idx, shot_labels) # rows of the Amsterdam feature table, labels 1..4
labels = m.predict() # labels 1..4 for m.keep (every non-shot row)
Needs at fit time: the Madrid table, ALL unlabelled Amsterdam features (per-city
scaling, the DANN inside the anchor), pixel coordinates for the smoothing. The
decision is batch-level: it labels the whole city at once.
CLI (scores one protocol draw; the only place non-shot labels are read):
python tools/nnlab/model_create2.py --n 25 --rep 0 [--no-spatial]
"""
from __future__ import annotations
import argparse, sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import lab
import numpy as np
import pandas as pd
W = lab.W
K = 10.0
_S = {}
def _state():
if not _S:
import lgbm_var as V
d = lab.load(official60=False)
feats = W.feature_names()
Xm = pd.read_parquet(W.DATA / "madrid_features.parquet", columns=feats)[feats].to_numpy(float)
Xa = pd.read_parquet(W.DATA / "amsterdam_features.parquet", columns=feats)[feats].to_numpy(float)
z = np.load(lab.PROC / "zeroshot_xgb_dann_ams.npz")
assert (z["pa"] == d["pa"]).all()
_S.update(V=V, d=d, Zm=W.robustz(Xm, W.robustz_fit(Xm)), Za=W.robustz(Xa, W.robustz_fit(Xa)),
ctx={"top": V.top_gain_idx()},
P_zs=((z["P_xgb"] + z["P_dann_seeds"].mean(0)) / 2).astype(np.float32).astype(np.float64),
params={**W.LGBM_PARAMS, **W.RETRAIN_OVERRIDES, "n_jobs": lab.THREADS})
return _S
class Model:
def __init__(self, proba, keep, spatial, members):
self.proba, self.keep, self.spatial, self.members = proba, keep, spatial, members
def predict(self):
S = _state()
P = self.proba[self.keep]
if self.spatial:
P = lab.smooth(P, S["d"]["pa"], keep=self.keep)
return W.decide(P, S["d"]["prior"])
def model_create(shot_idx, shot_labels, n=None, spatial=True):
S = _state()
shot = np.asarray(shot_idx)
ys = np.asarray(shot_labels)
n = len(shot) / 4 if n is None else n
P_feda = S["V"].fit_predict("lgbm_feda", S["Zm"], S["d"]["ym"], S["Za"], shot, ys, S["ctx"], S["params"])
P_feda = P_feda.astype(np.float32).astype(np.float64) # as stored by the lab
w0 = K / (n + K)
L = w0 * np.log(np.clip(S["P_zs"], 1e-6, 1)) + (1 - w0) * np.log(np.clip(P_feda, 1e-6, 1))
keep = np.setdiff1d(np.arange(len(S["Za"])), shot)
return Model(lab.softmax(L), keep, spatial, {"zs_xgb_dann": S["P_zs"], "lgbm_feda": P_feda})
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=25)
ap.add_argument("--rep", type=int, default=0)
ap.add_argument("--no-spatial", action="store_true")
a = ap.parse_args()
S = _state()
d = S["d"]
shot = lab.shots_for(d, a.n, a.rep)
m = model_create(shot, lab.shot_labels(d, shot), n=a.n, spatial=not a.no_spatial)
f1 = W.macro_f1(d["_ya"][m.keep], m.predict()) # scoring only
print(f"FINAL-2 n={a.n} rep={a.rep} spatial={m.spatial}: macro F1 {f1:.6f}")
ref = lab.pred_path("ens_e12", a.n, a.rep)
if ref.exists():
print(f"max |proba - stored ens_e12| = {np.abs(m.proba - np.load(ref)).max():.2e}")