Download tools/nnlab/tab_nn.py from Karar-git/hack4dev-building-age-winners: direct link, hf CLI and curl.
- Browser
- Download file 22 kB
-
https://huggingface.co/Karar-git/hack4dev-building-age-winners/resolve/main/tools/nnlab/tab_nn.py
- Command line
-
hf download hf://Karar-git/hack4dev-building-age-winners/tools/nnlab/tab_nn.py
-
curl -L -o tab_nn.py https://huggingface.co/Karar-git/hack4dev-building-age-winners/resolve/main/tools/nnlab/tab_nn.py
22 kB
| """ | |
| tab_nn - tabular neural networks with a FROZEN backbone and last-layer adaptation. | |
| python tools/nnlab/tab_nn.py [--smoke] [--only <substring>] | |
| Backbones (trained ONCE on all Madrid labels, class-balanced CE, AdamW lr 1e-3, | |
| wd 1e-4, batch 512, seeds 0,1,2): | |
| mlp Linear-BN-ReLU-Drop(0.2) x2 (256) -> Linear(64)-ReLU -> Linear(64,64)-ReLU -> Linear(64,4) | |
| res ResNet-MLP (Gorishniy et al. 2021): Linear(256), 3 x [x + Lin(Drop(ReLU(Lin(BN(x)))))], | |
| BN-ReLU -> Linear(64)-ReLU -> Linear(64,4) | |
| dann mlp + domain head (Lin(64,64)-ReLU-Lin(64,1)) on the first 64-d features through | |
| gradient reversal, lambda = 2/(1+exp(-10p))-1, adversary vs UNLABELLED Amsterdam rows | |
| (as tools/compare_teammate.py train_dann; Madrid and Amsterdam batches go through | |
| the net separately) | |
| coral mlp + deep-CORAL loss (weight 1.0) between the penultimate features of a Madrid | |
| batch and an unlabelled Amsterdam batch | |
| Inputs: rz = 393 feats lab.robustz per city; rg = 393 feats lab.rankgauss per city; | |
| o60 = official 60 feats lab.standardise per city. | |
| The EMBEDDING is the input of the final Linear(.,4); (W0, b0) are that layer's weights. | |
| Heads per protocol draw (probabilities averaged over the 3 seeds before saving): | |
| zs source head unchanged (saved once, n=0 rep=0) | |
| ft lab.finetune_last_layer(W0, b0, Z_shot, y_shot, lam) lam chosen on Madrid | |
| joint the objective of lab.joint_head(Z_madrid, y_madrid, Z_shot, y_shot, shot_weight=30) | |
| (same weights, C=1) on embeddings standardised with the Madrid embedding mean/sd | |
| (same affine map for the shots), solved with newton-cholesky warm-started from | |
| the Madrid-only fit (see head_joint: lab.joint_head's lbfgs does not converge | |
| in 400 iterations and would cost ~2 h over the run) | |
| proto prototypes (n_c * shot mean + k * Madrid class mean)/(n_c + k), k=10, embeddings | |
| centred by their own city's mean and L2-normalised; P = softmax(-20 * sq. distance) | |
| Method names: tab_{backbone}_{input}_{head}. "rg" is run for mlp and dann only (time budget), | |
| so 10 backbone x input combos x 4 heads = 40 methods. | |
| Madrid-only choices (lab.madrid_split, pseudo-target = the 25 % of 2 km blocks furthest | |
| along axis 0; transforms computed separately on the source part and on the pseudo-target, | |
| exactly as they are per city; adversary / CORAL target = the unlabelled pseudo-target): | |
| * epochs per backbone type from {15, 30, 45}: input rz, seed 0, trained on the source | |
| part, scored by macro F1 of the decision rule (W.decide, source-part prior) on the | |
| pseudo-target. Non-adversarial nets: one 45-epoch run evaluated after 15/30/45 epochs | |
| (identical to separate runs: constant lr, same seed). dann: separate runs, since its | |
| lambda schedule depends on the total epoch count. | |
| * lam for ft, ONE value for all backbones and n, from {0.1, 1, 10, 100}: on each | |
| backbone's selected source-part net (rz, seed 0), draw n in {5, 50} per class from the | |
| pseudo-target (5 draws, rng(7000 + 100 n + rep)), fine-tune, score the remaining | |
| pseudo-target with the rule; pick the lam with the best mean over backbones x n x draws. | |
| Both are cached in CACHE/tab_nn/selection.json and copied into every meta.json. | |
| CACHE LAYOUT (lab.CACHE / "tab_nn", or "tab_nn_SMOKE" for --smoke): | |
| selection.json epochs per backbone, lam, all pseudo-target scores | |
| sel_{backbone}.npz source-part (madrid_split) net at the chosen epochs: | |
| Zs, Zt (source / pseudo-target embeddings), W0, b0 | |
| emb_{backbone}_{input}_s{seed}.npz one file per (backbone, input, seed); inputs rz,o60 | |
| for all backbones, rg for mlp and dann; seeds 0,1,2: | |
| Zm (76263, 64) float32 Madrid embeddings (feature-table row order, eval mode) | |
| Za (25992, 64) float32 Amsterdam embeddings (feature-table row order) | |
| W0 (4, 64) float32 final layer weight; b0 (4,) final layer bias | |
| epochs () int epochs it was trained for | |
| Logits = Z @ W0.T + b0, columns = classes 1..4. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| import lab # noqa: E402 | |
| import numpy as np # noqa: E402 | |
| W = lab.W | |
| T0 = time.time() | |
| BACKBONES = ("mlp", "res", "dann", "coral") | |
| INPUTS = ("rz", "rg", "o60") | |
| # The full run on 3 shared CPU threads is far above 60 min, so per the brief the | |
| # "rg" input is dropped for "res" and "coral" (10 backbone x input combos, 40 methods). | |
| INPUTS_FOR = {"mlp": INPUTS, "dann": INPUTS, "res": ("rz", "o60"), "coral": ("rz", "o60")} | |
| HEADS = ("zs", "ft", "joint", "proto") | |
| SEEDS = (0, 1, 2) | |
| EPOCH_GRID = (15, 30, 45) | |
| LAM_GRID = (0.1, 1.0, 10.0, 100.0) | |
| LAM_NS = (5, 50) | |
| LAM_REPS = 5 | |
| SHOT_WEIGHT = 30.0 | |
| PROTO_K, PROTO_T = 10.0, 20.0 | |
| CORAL_W = 1.0 | |
| BATCH, LR, WD = 512, 1e-3, 1e-4 | |
| def log(msg): | |
| print(f"[{time.time() - T0:7.1f}s] {msg}", flush=True) | |
| # --------------------------------------------------------------------------- # | |
| # Networks | |
| # --------------------------------------------------------------------------- # | |
| def build_net(kind: str, d_in: int): | |
| import torch.nn as nn | |
| from torch.autograd import Function | |
| class GradReverse(Function): | |
| def forward(ctx, x, lam): | |
| ctx.lam = lam | |
| return x.view_as(x) | |
| def backward(ctx, g): | |
| return -ctx.lam * g, None | |
| class ResBlock(nn.Module): | |
| def __init__(self, h, p=0.2): | |
| super().__init__() | |
| self.f = nn.Sequential(nn.BatchNorm1d(h), nn.Linear(h, h), nn.ReLU(), nn.Dropout(p), nn.Linear(h, h)) | |
| def forward(self, x): | |
| return x + self.f(x) | |
| class Net(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| if kind == "res": | |
| self.f1 = nn.Sequential(nn.Linear(d_in, 256), ResBlock(256), ResBlock(256), ResBlock(256), | |
| nn.BatchNorm1d(256), nn.ReLU(), nn.Linear(256, 64), nn.ReLU()) | |
| self.f2 = nn.Identity() | |
| else: | |
| self.f1 = nn.Sequential(nn.Linear(d_in, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Dropout(0.2), | |
| nn.Linear(256, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Dropout(0.2), | |
| nn.Linear(256, 64), nn.ReLU()) | |
| self.f2 = nn.Sequential(nn.Linear(64, 64), nn.ReLU()) | |
| self.head = nn.Linear(64, 4) | |
| self.dom = nn.Sequential(nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 1)) if kind == "dann" else None | |
| def forward(self, x, lam=0.0): | |
| f = self.f1(x) | |
| z = self.f2(f) | |
| dl = self.dom(GradReverse.apply(f, lam)) if self.dom is not None else None | |
| return self.head(z), z, dl | |
| return Net() | |
| def coral_loss(a, b): | |
| import torch | |
| d = a.shape[1] | |
| def cov(x): | |
| x = x - x.mean(0, keepdim=True) | |
| return x.T @ x / max(len(x) - 1, 1) | |
| return ((cov(a) - cov(b)) ** 2).sum() / (4 * d * d) | |
| SMOKE_ROWS = None # --smoke: train on a random subset of this many rows | |
| def train(kind, Xs, ys, Xt, epochs, seed, eval_at=(), eval_fn=None): | |
| """Train a backbone. Xt = unlabelled target rows (dann / coral). If eval_at is | |
| given, eval_fn(net) is called after those epochs; returns (net, {ep: result}).""" | |
| torch = lab.torch_setup(seed) | |
| if SMOKE_ROWS and len(Xs) > SMOKE_ROWS: | |
| sub = np.random.default_rng(seed).choice(len(Xs), SMOKE_ROWS, replace=False) | |
| Xs, ys = Xs[sub], np.asarray(ys)[sub] | |
| import torch.nn as nn | |
| net = build_net(kind, Xs.shape[1]) | |
| opt = torch.optim.AdamW(net.parameters(), lr=LR, weight_decay=WD) | |
| cw = torch.tensor(lab.balanced_class_weights(ys), dtype=torch.float32) | |
| ce, bce = nn.CrossEntropyLoss(weight=cw), nn.BCEWithLogitsLoss() | |
| S, Y = torch.from_numpy(np.ascontiguousarray(Xs, np.float32)), torch.from_numpy(np.asarray(ys) - 1).long() | |
| T = torch.from_numpy(np.ascontiguousarray(Xt, np.float32)) | |
| g = torch.Generator().manual_seed(1000 + seed) | |
| steps = int(np.ceil(len(S) / BATCH)) | |
| res = {} | |
| for ep in range(epochs): | |
| net.train() | |
| perm = torch.randperm(len(S), generator=g) | |
| for k in range(steps): | |
| i = perm[k * BATCH:(k + 1) * BATCH] | |
| if len(i) < 2: | |
| continue | |
| if kind == "dann": | |
| p = (ep * steps + k) / (epochs * steps) | |
| lam = 2 / (1 + np.exp(-10 * p)) - 1 | |
| j = torch.randint(0, len(T), (len(i),), generator=g) | |
| ls, _, ds = net(S[i], lam) | |
| _, _, dt = net(T[j], lam) | |
| loss = ce(ls, Y[i]) + bce(ds.squeeze(1), torch.zeros(len(i))) + bce(dt.squeeze(1), torch.ones(len(j))) | |
| elif kind == "coral": | |
| j = torch.randint(0, len(T), (len(i),), generator=g) | |
| ls, zs, _ = net(S[i]) | |
| _, zt, _ = net(T[j]) | |
| loss = ce(ls, Y[i]) + CORAL_W * coral_loss(zs, zt) | |
| else: | |
| ls, _, _ = net(S[i]) | |
| loss = ce(ls, Y[i]) | |
| opt.zero_grad() | |
| loss.backward() | |
| opt.step() | |
| if ep + 1 in eval_at: | |
| res[ep + 1] = eval_fn(net) | |
| net.eval() | |
| return net, res | |
| def embed(net, X): | |
| import torch | |
| net.eval() | |
| out = [] | |
| with torch.no_grad(): | |
| for k in range(0, len(X), 8192): | |
| out.append(net(torch.from_numpy(np.ascontiguousarray(X[k:k + 8192], np.float32)))[1].numpy()) | |
| return np.vstack(out).astype(np.float32) | |
| def last_layer(net): | |
| return (net.head.weight.detach().numpy().astype(np.float32).copy(), | |
| net.head.bias.detach().numpy().astype(np.float32).copy()) | |
| # --------------------------------------------------------------------------- # | |
| # Inputs | |
| # --------------------------------------------------------------------------- # | |
| def _by_columns(f, chunk=48): | |
| """Apply a per-column lab transform in column blocks: identical output (every lab | |
| transform is column-wise), but bounds the float64 / rank temporaries - lab.rankgauss on | |
| the full 76k x 393 Madrid matrix alone peaks at ~1.5 GB extra (reviewer fix, RAM cap).""" | |
| def g(X): | |
| return np.hstack([f(X[:, k:k + chunk]) for k in range(0, X.shape[1], chunk)]) | |
| return g | |
| TRANSFORM = {"rz": _by_columns(lab.robustz), "rg": _by_columns(lab.rankgauss), "o60": lab.standardise} | |
| def raw_inputs(d, inp): | |
| return (d["Xm60"], d["Xa60"]) if inp == "o60" else (d["Xm"], d["Xa"]) | |
| _INPUT_CACHE = {} | |
| def city_inputs(d, inp): | |
| """Per-city transformed inputs, kept in memory for the run (~150 MB per 393-feat input).""" | |
| if inp not in _INPUT_CACHE: | |
| Xm, Xa = raw_inputs(d, inp) | |
| f = TRANSFORM[inp] | |
| _INPUT_CACHE[inp] = (f(Xm), f(Xa)) | |
| return _INPUT_CACHE[inp] | |
| # --------------------------------------------------------------------------- # | |
| # Heads | |
| # --------------------------------------------------------------------------- # | |
| def head_ft(e, Zs, ys, lam): | |
| Wf, bf = lab.finetune_last_layer(e["W0"], e["b0"], Zs, ys, lam) | |
| return lab.softmax(e["Za"].astype(np.float64) @ Wf.T + bf) | |
| def joint_init(e): | |
| """Standardise embeddings with the Madrid mean/sd and fit the Madrid-only | |
| weighted LR once per seed (warm start for every draw's joint head).""" | |
| from sklearn.linear_model import LogisticRegression | |
| if "_Zm_std" not in e: | |
| mu, sd = e["Zm"].mean(0), e["Zm"].std(0) | |
| sd = np.where(sd > 1e-8, sd, 1.0) | |
| e["_Zm_std"] = ((e["Zm"] - mu) / sd).astype(np.float64) | |
| e["_Za_std"] = ((e["Za"] - mu) / sd).astype(np.float64) | |
| w = lab.balanced_class_weights(e["ym"])[e["ym"] - 1] | |
| e["_m0"] = LogisticRegression(C=1.0, solver="newton-cholesky", max_iter=100).fit( | |
| e["_Zm_std"], e["ym"], sample_weight=w) | |
| return e | |
| def head_joint(e, shot, ys): | |
| """Same objective as lab.joint_head(Zm, ym, Zs, ys, shot_weight=30, C=1) | |
| (identical sample weights), solved with sklearn's newton-cholesky solver | |
| warm-started from the Madrid-only fit: lab.joint_head's lbfgs stops at | |
| max_iter=400 unconverged on these embeddings and takes ~9 s per fit; this | |
| converges in 2-4 Newton steps (~2 s) and lands closer to the true optimum.""" | |
| from sklearn.linear_model import LogisticRegression | |
| e = joint_init(e) | |
| Zm, Za = e["_Zm_std"], e["_Za_std"] | |
| X = np.vstack([Zm, Za[shot]]) | |
| y = np.r_[np.asarray(e["ym"]), np.asarray(ys)] | |
| w = np.r_[np.ones(len(e["ym"])), np.full(len(ys), SHOT_WEIGHT)] | |
| w = w * lab.balanced_class_weights(y)[y - 1] | |
| m = LogisticRegression(C=1.0, solver="newton-cholesky", max_iter=100, warm_start=True) | |
| m.coef_, m.intercept_ = e["_m0"].coef_.copy(), e["_m0"].intercept_.copy() | |
| m.fit(X, y, sample_weight=w) | |
| return m.predict_proba(Za) | |
| def _unit(Z): | |
| Z = Z - Z.mean(0) | |
| return Z / np.clip(np.linalg.norm(Z, axis=1, keepdims=True), 1e-12, None) | |
| def head_proto(e, shot, ys): | |
| if "_Um" not in e: # computed once per seed (setdefault would recompute every call) | |
| e["_Um"], e["_Ua"] = _unit(e["Zm"].astype(np.float64)), _unit(e["Za"].astype(np.float64)) | |
| Um, Ua = e["_Um"], e["_Ua"] | |
| protos = [] | |
| for c in lab.CLASSES: | |
| mm = Um[e["ym"] == c].mean(0) | |
| sc = Ua[shot[ys == c]] | |
| nc = len(sc) | |
| protos.append((nc * sc.mean(0) + PROTO_K * mm) / (nc + PROTO_K) if nc else mm) | |
| Pr = np.stack(protos) | |
| d2 = (Ua ** 2).sum(1)[:, None] - 2 * Ua @ Pr.T + (Pr ** 2).sum(1)[None] | |
| return lab.softmax(-PROTO_T * d2) | |
| # --------------------------------------------------------------------------- # | |
| # Madrid-only selection | |
| # --------------------------------------------------------------------------- # | |
| def selection(d, cache, smoke): | |
| path = cache / "selection.json" | |
| sel = json.loads(path.read_text()) if path.exists() else {"epochs": {}, "epoch_scores": {}} | |
| grid = (1, 2) if smoke else EPOCH_GRID | |
| src, tgt = lab.madrid_split(d["pm"]) | |
| Xs, Xt = TRANSFORM["rz"](d["Xm"][src]), TRANSFORM["rz"](d["Xm"][tgt]) | |
| ys, yt = d["ym"][src], d["ym"][tgt] | |
| prior = np.array([(ys == c).mean() for c in lab.CLASSES]) | |
| def f1(net): | |
| P = lab.softmax(embed(net, Xt).astype(np.float64) @ last_layer(net)[0].T.astype(np.float64) | |
| + last_layer(net)[1]) | |
| return W.macro_f1(yt, W.decide(P, prior)) | |
| emb = {} | |
| for kind in BACKBONES: | |
| ep_path = cache / f"sel_{kind}.npz" | |
| if kind in sel["epochs"] and ep_path.exists(): | |
| z = np.load(ep_path) | |
| emb[kind] = dict(Zs=z["Zs"], Zt=z["Zt"], W0=z["W0"], b0=z["b0"]) | |
| continue | |
| t0 = time.time() | |
| nets = {} | |
| if kind == "dann": | |
| scores = {} | |
| for E in grid: | |
| net, _ = train(kind, Xs, ys, Xt, E, 0) | |
| scores[E], nets[E] = f1(net), (embed(net, Xs), embed(net, Xt), *last_layer(net)) | |
| else: | |
| snap = {} | |
| def ev(net): | |
| r = f1(net) | |
| snap[len(snap)] = (embed(net, Xs), embed(net, Xt), *last_layer(net)) | |
| net.train() | |
| return r | |
| _, scores = train(kind, Xs, ys, Xt, max(grid), 0, eval_at=grid, eval_fn=ev) | |
| for k, E in enumerate(grid): | |
| nets[E] = snap[k] | |
| best = max(grid, key=lambda E: (scores[E], -E)) | |
| sel["epochs"][kind] = best | |
| sel["epoch_scores"][kind] = {str(k): v for k, v in scores.items()} | |
| Zs_, Zt_, W0, b0 = nets[best] | |
| np.savez(ep_path, Zs=Zs_, Zt=Zt_, W0=W0, b0=b0) | |
| emb[kind] = dict(Zs=Zs_, Zt=Zt_, W0=W0, b0=b0) | |
| path.write_text(json.dumps(sel, indent=1)) | |
| log(f"select epochs {kind}: pseudo-target rule F1 {scores} -> {best} ({time.time() - t0:.0f}s)") | |
| if "lam" not in sel: | |
| t0 = time.time() | |
| reps = 1 if smoke else LAM_REPS | |
| tab = {str(l): [] for l in LAM_GRID} | |
| per = {} | |
| for kind in BACKBONES: | |
| e = emb[kind] | |
| for n in LAM_NS: | |
| for rep in range(reps): | |
| rng = np.random.default_rng(7000 + 100 * n + rep) | |
| shot = np.concatenate([rng.choice(np.flatnonzero(yt == c), n, replace=False) for c in lab.CLASSES]) | |
| keep = np.setdiff1d(np.arange(len(yt)), shot) | |
| for l in LAM_GRID: | |
| Wf, bf = lab.finetune_last_layer(e["W0"], e["b0"], e["Zt"][shot], yt[shot], l) | |
| P = lab.softmax(e["Zt"][keep].astype(np.float64) @ Wf.T + bf) | |
| s = W.macro_f1(yt[keep], W.decide(P, prior)) | |
| tab[str(l)].append(s) | |
| per.setdefault(f"{kind}_n{n}_lam{l}", []).append(s) | |
| means = {l: float(np.mean(v)) for l, v in tab.items()} | |
| sel["lam"] = float(max(LAM_GRID, key=lambda l: (means[str(l)], l))) | |
| sel["lam_scores"] = means | |
| sel["lam_scores_detail"] = {k: float(np.mean(v)) for k, v in per.items()} | |
| path.write_text(json.dumps(sel, indent=1)) | |
| log(f"select lam: {means} -> {sel['lam']} ({time.time() - t0:.0f}s)") | |
| return sel | |
| # --------------------------------------------------------------------------- # | |
| # Main | |
| # --------------------------------------------------------------------------- # | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--smoke", action="store_true") | |
| ap.add_argument("--only", default="") | |
| ap.add_argument("--seeds", type=int, default=len(SEEDS), | |
| help="use only the first k of seeds 0,1,2 (default 3; method names unchanged, " | |
| "recorded in meta) - a time-saving fallback, not the planned run") | |
| a = ap.parse_args() | |
| smoke = a.smoke | |
| global SMOKE_ROWS | |
| SMOKE_ROWS = 3000 if smoke else None | |
| sfx = "_SMOKE" if smoke else "" | |
| cache = lab.CACHE / ("tab_nn" + sfx) | |
| cache.mkdir(parents=True, exist_ok=True) | |
| seeds = (0,) if smoke else SEEDS[:a.seeds] | |
| draws = [(5, 0), (200, 0)] if smoke else list(lab.draws()) | |
| d = lab.load(official60=True) | |
| log("data loaded") | |
| def mname(bb, inp, h): | |
| return f"tab_{bb}_{inp}_{h}{sfx}" | |
| combos = [(bb, inp) for bb in BACKBONES for inp in INPUTS_FOR[bb] | |
| if any(a.only in mname(bb, inp, h) for h in HEADS)] | |
| sel = selection(d, cache, smoke) | |
| lam = sel["lam"] | |
| for bb, inp in combos: | |
| heads = [h for h in HEADS if a.only in mname(bb, inp, h)] | |
| todo_draws = [(n, r) for n, r in draws | |
| if any(not lab.have_pred(mname(bb, inp, h), n, r) for h in heads if h != "zs")] | |
| need_zs = "zs" in heads and not lab.have_pred(mname(bb, inp, "zs"), 0, 0) | |
| if not todo_draws and not need_zs: | |
| log(f"{bb}/{inp}: all saved, skip") | |
| continue | |
| epochs = int(sel["epochs"][bb]) | |
| embs, Xin = [], None | |
| for s in seeds: | |
| f = cache / f"emb_{bb}_{inp}_s{s}.npz" | |
| if f.exists(): # reviewer: never reuse a net trained for another epoch count | |
| with np.load(f) as z0: | |
| stale = int(z0["epochs"]) != epochs | |
| if stale: | |
| log(f"cached {f.name} has a different epoch count than selection.json: retraining") | |
| f.unlink() | |
| if not f.exists(): | |
| if Xin is None: | |
| Xin = city_inputs(d, inp) | |
| t0 = time.time() | |
| net, _ = train(bb, Xin[0], d["ym"], Xin[1], epochs, s) | |
| W0, b0 = last_layer(net) | |
| np.savez(f, Zm=embed(net, Xin[0]), Za=embed(net, Xin[1]), W0=W0, b0=b0, epochs=epochs) | |
| log(f"train {bb}/{inp} seed {s}: {epochs} epochs {time.time() - t0:.0f}s") | |
| z = np.load(f) | |
| embs.append(dict(Zm=z["Zm"], Za=z["Za"], W0=z["W0"], b0=z["b0"], ym=d["ym"])) | |
| del Xin | |
| meta = dict(script="tools/nnlab/tab_nn.py", backbone=bb, input=inp, seeds=list(seeds), | |
| epochs=epochs, epoch_choice=sel["epoch_scores"].get(bb), ft_lam=lam, | |
| lam_scores=sel.get("lam_scores"), joint_shot_weight=SHOT_WEIGHT, | |
| proto_k=PROTO_K, proto_temp=PROTO_T, coral_weight=CORAL_W if bb == "coral" else None, | |
| optim=dict(opt="AdamW", lr=LR, wd=WD, batch=BATCH), | |
| joint_solver="newton-cholesky, warm start from Madrid-only fit, C=1", | |
| selection="lab.madrid_split pseudo-target, rule macro F1, input rz seed 0", | |
| smoke=smoke) | |
| if need_zs: | |
| P = np.mean([lab.softmax(e["Za"].astype(np.float64) @ e["W0"].T + e["b0"]) for e in embs], 0) | |
| lab.save_pred(mname(bb, inp, "zs"), 0, 0, P, {**meta, "head": "zs"}) | |
| log(f"{mname(bb, inp, 'zs')} n=0 saved") | |
| for n, rep in todo_draws: | |
| t0 = time.time() | |
| shot = lab.shots_for(d, n, rep) | |
| ys = lab.shot_labels(d, shot) | |
| for h in heads: | |
| if h == "zs" or lab.have_pred(mname(bb, inp, h), n, rep): | |
| continue | |
| th = time.time() | |
| if h == "ft": | |
| P = np.mean([head_ft(e, e["Za"][shot], ys, lam) for e in embs], 0) | |
| elif h == "joint": | |
| P = np.mean([head_joint(e, shot, ys) for e in embs], 0) | |
| else: | |
| P = np.mean([head_proto(e, shot, ys) for e in embs], 0) | |
| lab.save_pred(mname(bb, inp, h), n, rep, P, {**meta, "head": h}) | |
| log(f"{mname(bb, inp, h)} n={n} rep={rep} {time.time() - th:.1f}s") | |
| del embs | |
| log("done") | |
| if __name__ == "__main__": | |
| main() | |