Tabular Classification
Transformers
Safetensors
felatab
feature-extraction
fela
tabular
in-context-learning
prior-fitted-network
foundation-model
delta-rule
cpu
on-device
custom_code
Eval Results (legacy)
Instructions to use lowdown-labs/fela-tab with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lowdown-labs/fela-tab with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("lowdown-labs/fela-tab", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
| import argparse | |
| import math | |
| import os | |
| import time | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from modeling import FelaTab, FelaTabConfig, count_params | |
| from prior import PriorConfig, gen_pool | |
| def collate(tasks, task_type, tmax, rng, maxF, s_frac=(0.5, 0.9)): | |
| B = len(tasks) | |
| used, sup = ([], []) | |
| for t in tasks: | |
| N = t["X"].shape[0] | |
| n = min(N, tmax) | |
| frac = rng.uniform(*s_frac) | |
| s = int(np.clip(int(frac * n), 16, n - 8)) | |
| used.append(n) | |
| sup.append(s) | |
| T = max(used) | |
| X = np.zeros((B, T, maxF), np.float32) | |
| y = np.zeros((B, T), np.int64 if task_type == "cls" else np.float32) | |
| lm = np.zeros((B, T), np.float32) | |
| ncl = np.zeros(B, np.int64) | |
| ns = np.zeros(B, np.int64) | |
| for b, t in enumerate(tasks): | |
| N = t["X"].shape[0] | |
| n = used[b] | |
| s = sup[b] | |
| Ft = min(t["X"].shape[1], maxF) | |
| perm = rng.permutation(N)[:n] | |
| X[b, :n, :Ft] = t["X"][perm][:, :Ft] | |
| y[b, :n] = t["y"][perm] | |
| lm[b, s:n] = 1.0 | |
| ns[b] = s | |
| ncl[b] = t["n_classes"] if task_type == "cls" else 0 | |
| return ( | |
| torch.from_numpy(X), | |
| torch.from_numpy(y), | |
| torch.from_numpy(ns), | |
| torch.from_numpy(lm), | |
| torch.from_numpy(ncl), | |
| ) | |
| def cls_loss_acc(logits, y, lm, ncl): | |
| B, T, C = logits.shape | |
| cmask = torch.arange(C, device=logits.device).view(1, C) < ncl.view(B, 1) | |
| logits = logits.masked_fill(~cmask.unsqueeze(1), -1000000000.0) | |
| lf = logits.reshape(B * T, C) | |
| yf = y.reshape(B * T).clamp(min=0) | |
| mf = lm.reshape(B * T).bool() | |
| if mf.sum() == 0: | |
| return (logits.sum() * 0.0, 0.0) | |
| loss = F.cross_entropy(lf[mf], yf[mf]) | |
| acc = (lf[mf].argmax(-1) == yf[mf]).float().mean().item() | |
| return (loss, acc) | |
| def reg_loss_r2(out, y, lm): | |
| mean = out[..., 0] | |
| logvar = out[..., 1].clamp(-8, 8) | |
| mf = lm.bool() | |
| if mf.sum() == 0: | |
| return (out.sum() * 0.0, 0.0) | |
| m, lv, t = (mean[mf], logvar[mf], y[mf]) | |
| nll = 0.5 * (lv + (t - m) ** 2 / torch.exp(lv) + math.log(2 * math.pi)) | |
| ss_res = ((t - m) ** 2).sum().item() | |
| ss_tot = ((t - t.mean()) ** 2).sum().item() + 1e-08 | |
| return (nll.mean(), 1 - ss_res / ss_tot) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--steps", type=int, default=20000) | |
| ap.add_argument("--bs", type=int, default=16) | |
| ap.add_argument("--tmax", type=int, default=1024) | |
| ap.add_argument("--lr", type=float, default=0.0003) | |
| ap.add_argument("--dim", type=int, default=512) | |
| ap.add_argument("--layers", type=int, default=14) | |
| ap.add_argument("--heads", type=int, default=8) | |
| ap.add_argument("--head_dim", type=int, default=64) | |
| ap.add_argument("--pool", type=int, default=1000) | |
| ap.add_argument("--refresh", type=int, default=400) | |
| ap.add_argument("--ckpt", type=str, default="ckpt/felatab.pt") | |
| ap.add_argument( | |
| "--smoke", action="store_true", help="tiny fast loop to prove it trains" | |
| ) | |
| args = ap.parse_args() | |
| if args.smoke: | |
| args.steps, args.dim, args.layers, args.heads = (200, 128, 4, 4) | |
| args.pool, args.bs, args.tmax = (128, 8, 256) | |
| dev = "cuda" if torch.cuda.is_available() else "cpu" | |
| prior_cfg = PriorConfig(max_classes=10, max_rows=6000) | |
| cfg = FelaTabConfig( | |
| dim=args.dim, | |
| n_layers=args.layers, | |
| n_heads=args.heads, | |
| head_dim=args.head_dim, | |
| max_features=prior_cfg.max_features, | |
| max_classes=prior_cfg.max_classes, | |
| ) | |
| model = FelaTab(cfg).to(dev) | |
| print( | |
| f"params {count_params(model) / 1000000.0:.2f}M dim={cfg.dim} L={cfg.n_layers} H={cfg.n_heads} dev={dev}" | |
| ) | |
| opt = torch.optim.AdamW( | |
| model.parameters(), lr=args.lr, weight_decay=0.01, betas=(0.9, 0.95) | |
| ) | |
| sched = torch.optim.lr_scheduler.OneCycleLR( | |
| opt, | |
| max_lr=args.lr, | |
| total_steps=args.steps, | |
| pct_start=0.03, | |
| anneal_strategy="cos", | |
| ) | |
| rng = np.random.default_rng(1234) | |
| def refill(seed): | |
| pool = gen_pool(prior_cfg, args.pool, seed=seed, workers=None) | |
| return ( | |
| [t for t in pool if t["task_type"] == "cls"], | |
| [t for t in pool if t["task_type"] == "reg"], | |
| ) | |
| print("generating initial synthetic pool...") | |
| t0 = time.time() | |
| cls_pool, reg_pool = refill(int(rng.integers(1000000000.0))) | |
| print( | |
| f"pool ready {time.time() - t0:.1f}s (cls {len(cls_pool)} reg {len(reg_pool)})" | |
| ) | |
| model.train() | |
| run_loss = run_metric = 0.0 | |
| for step in range(1, args.steps + 1): | |
| typ = "cls" if step % 2 == 0 else "reg" | |
| src = cls_pool if typ == "cls" else reg_pool | |
| if not src: | |
| continue | |
| T_target = int(rng.integers(128, args.tmax + 1)) | |
| idx = rng.integers(0, len(src), size=args.bs) | |
| batch = [src[i] for i in idx] | |
| X, y, ns, lm, ncl = collate(batch, typ, T_target, rng, cfg.max_features) | |
| X, y, ns, lm, ncl = [z.to(dev) for z in (X, y, ns, lm, ncl)] | |
| out = model(X, y, ns, torch.tensor(0 if typ == "cls" else 1, device=dev)) | |
| if typ == "cls": | |
| loss, metric = cls_loss_acc(out.float(), y, lm, ncl) | |
| else: | |
| loss, metric = reg_loss_r2(out.float(), y, lm) | |
| opt.zero_grad() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| opt.step() | |
| sched.step() | |
| run_loss += loss.item() | |
| run_metric += metric | |
| if step % 50 == 0: | |
| print( | |
| f"step {step:6d} {typ} loss {run_loss / 50:.4f} metric {run_metric / 50:.4f} lr {sched.get_last_lr()[0]:.2e}", | |
| flush=True, | |
| ) | |
| run_loss = run_metric = 0.0 | |
| if step % args.refresh == 0: | |
| cls_pool, reg_pool = refill(int(rng.integers(1000000000.0))) | |
| os.makedirs(os.path.dirname(args.ckpt) or ".", exist_ok=True) | |
| torch.save(model.state_dict(), args.ckpt) | |
| print(f"done. saved {args.ckpt}") | |
| if __name__ == "__main__": | |
| main() | |