Download ALSA.py from sparsetrace/dima: direct link, hf CLI and curl.
- Browser
- Download file 14.9 kB
-
https://huggingface.co/sparsetrace/dima/resolve/main/ALSA.py
- Command line
-
hf download hf://sparsetrace/dima/ALSA.py
-
curl -L -o ALSA.py https://huggingface.co/sparsetrace/dima/resolve/main/ALSA.py
14.9 kB
| # ALSA.py | |
| # ============================================================ | |
| # ALSA = NLSAEncoder + GPLM baseline + in-context attention residual mixing | |
| # | |
| # Key idea: | |
| # ALSA = global predictor + analog/attention-like correction in diffusion coords ψ | |
| # | |
| # Behavior: | |
| # - if prefix length ell == L: baseline-only AR rollout (NLSA+GPLM) | |
| # - if ell > L: ALSA correction enabled: | |
| # E_ctx = Y_true - Y_glob | |
| # p(q,b) ∝ exp(-||ψ_q-ψ_b||^2 / (2 ell_attn^2)) | |
| # y = y_glob + gamma_ctx * Σ_b p(q,b) E_ctx[b] | |
| # | |
| # This is a "Markovian" IC method because p(q,·) is a probability distribution. | |
| # | |
| # Repo expectations: | |
| # - NLSAEncoder provides: | |
| # .L, .K_, .psi_, .mu_ and .encode_window(window_LD)->(r,) | |
| # - GPLM provides: | |
| # GPLM(X_latent, Y_ambient, center_X=False, ...) and callable predict | |
| # | |
| # Dependencies: | |
| # numpy (scipy optional for pdist if you want it, but not required) | |
| # ============================================================ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Any, Dict, Optional, Tuple, Union | |
| import numpy as np | |
| from numpy.lib.stride_tricks import sliding_window_view | |
| # ------------------------------------------------------------ | |
| # Imports from your repo (robust fallbacks) | |
| # ------------------------------------------------------------ | |
| # NLSA encoder | |
| try: | |
| from .nlsa_encoder import NLSA # type: ignore | |
| except Exception: | |
| try: | |
| from nlsa_encoder import NLSA # type: ignore | |
| except Exception: | |
| # if you named the file NLSA.py and class NLSAEncoder lives there | |
| from NLSA import NLSA # type: ignore | |
| # GPLM decoder | |
| try: | |
| from .gplm import GPLM # type: ignore | |
| except Exception: | |
| try: | |
| from gplm import GPLM # type: ignore | |
| except Exception: | |
| from GPLM import GPLM # type: ignore | |
| Array = np.ndarray | |
| # ============================================================ | |
| # Small utilities | |
| # ============================================================ | |
| def _as_2d(X: Array) -> Array: | |
| X = np.asarray(X, dtype=float) | |
| if X.ndim == 1: | |
| X = X[:, None] | |
| return X | |
| def _sliding_windows(F_tD: Array, L: int) -> Array: | |
| """ | |
| Return windows W (K,L,D) from F (N,D) with K=N-L+1. | |
| Handles numpy stride ordering differences. | |
| """ | |
| F = _as_2d(F_tD) | |
| W = sliding_window_view(F, window_shape=int(L), axis=0) | |
| # W can be (K,L,D) or (K,D,L) | |
| a, b = W.shape[1], W.shape[2] | |
| if (a, b) == (L, F.shape[1]): | |
| return np.ascontiguousarray(W) | |
| if (a, b) == (F.shape[1], L): | |
| return np.ascontiguousarray(np.transpose(W, (0, 2, 1))) | |
| raise ValueError(f"Unexpected window shape {W.shape} for L={L}, D={F.shape[1]}.") | |
| def _sample_pairwise_d2(X: Array, m: int = 1024, seed: int = 0) -> Array: | |
| """ | |
| Sample pairwise squared distances for bandwidth estimation. | |
| Returns 1D array of sampled d^2 values. | |
| """ | |
| X = np.asarray(X, dtype=np.float64) | |
| n = X.shape[0] | |
| if n <= 1: | |
| return np.array([1.0], dtype=np.float64) | |
| rng = np.random.default_rng(int(seed)) | |
| idx = rng.choice(n, size=min(int(m), n), replace=False) | |
| Xs = X[idx] | |
| # Sample random pairs among Xs | |
| ns = Xs.shape[0] | |
| M = min(20000, ns * (ns - 1) // 2) | |
| ii = rng.integers(0, ns, size=M) | |
| jj = rng.integers(0, ns, size=M) | |
| mask = ii != jj | |
| ii, jj = ii[mask], jj[mask] | |
| if ii.size == 0: | |
| return np.array([1.0], dtype=np.float64) | |
| diff = Xs[ii] - Xs[jj] | |
| return np.sum(diff * diff, axis=1) | |
| def _median_ell_from_d2(d2: Array, q: float = 0.5, eps: float = 1e-12) -> float: | |
| """ | |
| If kernel is exp(-||x-y||^2/(2 ell^2)), heuristic: | |
| ell^2 ≈ quantile(d^2)/2 | |
| """ | |
| d2 = np.asarray(d2, dtype=np.float64) | |
| if d2.size == 0: | |
| return 1.0 | |
| v = float(np.quantile(d2, float(q))) | |
| return float(np.sqrt(max(v / 2.0, eps))) | |
| def _softmax_weights_from_d2(d2: Array, ell: float, eps: float = 1e-18) -> Array: | |
| """ | |
| p_i ∝ exp(-0.5 * d2_i / ell^2) | |
| """ | |
| d2 = np.asarray(d2, dtype=np.float64) | |
| ell2 = float(ell) * float(ell) + 1e-18 | |
| logits = -0.5 * d2 / ell2 | |
| logits -= float(np.max(logits)) | |
| w = np.exp(logits) | |
| s = float(np.sum(w)) | |
| return w / (s + eps) | |
| # ============================================================ | |
| # ALSA config (sister to LISAConfig) | |
| # ============================================================ | |
| class ALSAConfig: | |
| # Encoder (NLSA) | |
| L: int = 128 | |
| rank: int = 32 | |
| beta: Optional[float] = None | |
| alpha: float = 1.0 | |
| center: bool = True | |
| drop_first: bool = True | |
| max_K_dense: int = 6000 | |
| seed: int = 0 | |
| # Baseline GPLM decoder psi->next sample | |
| gplm_kwargs: Optional[Dict[str, Any]] = None | |
| # psi preprocessing | |
| whiten_psi: bool = True | |
| # In-context attention correction | |
| min_ctx_windows: Optional[int] = None # default r+1 | |
| ctx_k0: float = 10.0 # gamma_ctx = K_ctx/(K_ctx + ctx_k0) | |
| attn_topk: Optional[int] = 64 # None = dense over all context | |
| attn_ell: Optional[float] = None # if None -> estimate from training ψ geometry | |
| attn_ell_q: float = 0.5 # quantile used for attn_ell estimation | |
| # ============================================================ | |
| # ALSA main class | |
| # ============================================================ | |
| class ALSA: | |
| """ | |
| ALSA = NLSAEncoder + GPLM baseline + attention-like (Markov) IC correction. | |
| Training: | |
| 1) Fit NLSAEncoder on training series | |
| 2) Fit GPLM baseline on diffusion coords: | |
| ψ_train(window) -> next sample | |
| Inference: | |
| - If prefix length ell == L: baseline-only AR rollout | |
| - If ell > L: | |
| build context windows within prefix | |
| residual table E_ctx = Y_true - Y_glob | |
| attention weights p(q,ctx) from ψ-distances | |
| correction = Σ p * E_ctx | |
| y = y_glob + gamma_ctx * correction | |
| """ | |
| def __init__(self, F_train: Array, *, config: Optional[ALSAConfig] = None, **kwargs: Any): | |
| if config is None: | |
| config = ALSAConfig(**kwargs) | |
| self.cfg = config | |
| F_train = _as_2d(F_train) | |
| self.D = int(F_train.shape[1]) | |
| self.L = int(self.cfg.L) | |
| # ---- 1) Encoder ---- | |
| self.enc = NLSA( | |
| F_train, | |
| L=int(self.cfg.L), | |
| rank=int(self.cfg.rank), | |
| beta=self.cfg.beta, | |
| alpha=float(self.cfg.alpha), | |
| center=bool(self.cfg.center), | |
| drop_first=bool(self.cfg.drop_first), | |
| max_K_dense=int(self.cfg.max_K_dense), | |
| seed=int(self.cfg.seed), | |
| ) | |
| self.r = 0 if self.enc.psi_ is None else int(self.enc.psi_.shape[1]) | |
| if self.r <= 0: | |
| raise ValueError("NLSA produced r=0 diffusion dims. Increase rank or set drop_first=False.") | |
| # Output mean from encoder centering | |
| self.center = bool(self.cfg.center) | |
| self.mu_X = self.enc.mu_.reshape(-1).astype(np.float64) if self.center else np.zeros((self.D,), dtype=np.float64) | |
| # ---- 2) Baseline GPLM: psi -> next sample (centered space) ---- | |
| K = int(self.enc.K_) | |
| n_pairs = K - 1 | |
| Psi_tr = np.asarray(self.enc.psi_[:n_pairs, :], dtype=np.float64) # (K-1, r) | |
| # Targets in centered space | |
| F_centered = F_train - self.mu_X[None, :] if self.center else F_train | |
| Y_tr = np.asarray(F_centered[self.L : self.L + n_pairs, :], dtype=np.float64) # (K-1, D) | |
| # Optional whitening of psi for both: | |
| # - baseline decoder geometry | |
| # - attention geometry | |
| self.whiten_psi = bool(self.cfg.whiten_psi) | |
| if self.whiten_psi: | |
| self.psi_mu = Psi_tr.mean(axis=0, keepdims=True) | |
| self.psi_sd = Psi_tr.std(axis=0, keepdims=True) + 1e-12 | |
| Psi_tr_w = (Psi_tr - self.psi_mu) / self.psi_sd | |
| else: | |
| self.psi_mu = np.zeros((1, self.r), dtype=np.float64) | |
| self.psi_sd = np.ones((1, self.r), dtype=np.float64) | |
| Psi_tr_w = Psi_tr | |
| gkw = {} if self.cfg.gplm_kwargs is None else dict(self.cfg.gplm_kwargs) | |
| # Critical: outputs already centered -> don't recenter in GPLM | |
| gkw.setdefault("center_X", False) | |
| gkw.setdefault("seed", int(self.cfg.seed)) | |
| gkw.setdefault("sigma2", 1e-4) | |
| gkw.setdefault("jitter", 1e-8) | |
| gkw.setdefault("m", min(1024, n_pairs)) | |
| gkw.setdefault("inducing", "kmeans_medoids") | |
| self.baseline = GPLM(Psi_tr_w, Y_tr, **gkw) | |
| # Attention hyperparams | |
| self.attn_topk = None if self.cfg.attn_topk is None else int(self.cfg.attn_topk) | |
| if self.cfg.min_ctx_windows is None: | |
| self.min_ctx_windows = max(1, self.r + 1) | |
| else: | |
| self.min_ctx_windows = int(self.cfg.min_ctx_windows) | |
| self.ctx_k0 = float(self.cfg.ctx_k0) | |
| # Pick attention ell if not provided (based on training psi geometry) | |
| if self.cfg.attn_ell is None: | |
| d2 = _sample_pairwise_d2(Psi_tr_w, m=1024, seed=int(self.cfg.seed) + 123) | |
| self.attn_ell = _median_ell_from_d2(d2, q=float(self.cfg.attn_ell_q)) | |
| else: | |
| self.attn_ell = float(self.cfg.attn_ell) | |
| # ------------------------- | |
| # psi processing | |
| # ------------------------- | |
| def _psi_whiten(self, psi: Array) -> Array: | |
| psi = np.asarray(psi, dtype=np.float64) | |
| if psi.ndim == 1: | |
| psi = psi[None, :] | |
| if self.whiten_psi: | |
| return (psi - self.psi_mu) / self.psi_sd | |
| return psi | |
| # ------------------------- | |
| # baseline step | |
| # ------------------------- | |
| def predict_one_step_baseline(self, W_LD: Array) -> Array: | |
| """ | |
| Baseline one-step prediction: | |
| window (L,D) -> psi -> GPLM -> next sample (D) | |
| """ | |
| W = np.asarray(W_LD, dtype=float) | |
| if W.ndim == 1: | |
| W = W[:, None] | |
| if W.shape != (self.L, self.D): | |
| raise ValueError(f"Expected window shape {(self.L, self.D)}, got {W.shape}") | |
| psi = self.enc.encode_window(W) # (r,) | |
| psi_w = self._psi_whiten(psi)[0] # (r,) | |
| y_c = np.asarray(self.baseline(psi_w), dtype=np.float64).reshape(-1) # centered | |
| return (y_c + self.mu_X) if self.center else y_c | |
| # ------------------------- | |
| # main IC rollout | |
| # ------------------------- | |
| def __call__(self, prefix: Array, steps: int = 1) -> Array: | |
| """ | |
| Forecast from prefix. | |
| prefix: (ell,D), ell >= L | |
| steps: horizon H | |
| Returns: | |
| (H,D) (or (D,) if H==1) | |
| """ | |
| prefix = _as_2d(prefix) | |
| ell, D = prefix.shape | |
| if D != self.D: | |
| raise ValueError(f"ALSA trained with D={self.D}, got prefix D={D}.") | |
| if ell < self.L: | |
| raise ValueError(f"Need prefix length ell >= L={self.L}.") | |
| H = int(steps) | |
| if H <= 0: | |
| return np.zeros((0, self.D), dtype=np.float64) | |
| # Seed window is always last L | |
| cur = prefix[-self.L :, :].copy() | |
| # ---------------------------------------------------- | |
| # If no context (ell == L), run baseline-only rollout | |
| # ---------------------------------------------------- | |
| if ell == self.L: | |
| out = self._rollout_baseline(cur, H) | |
| return out[0] if H == 1 else out | |
| # ---------------------------------------------------- | |
| # Build context windows and residual table from prefix | |
| # ---------------------------------------------------- | |
| W_all = _sliding_windows(prefix, self.L) # (K_n, L, D) | |
| K_n = int(W_all.shape[0]) | |
| K_ctx = K_n - 1 | |
| # If too little context, baseline-only | |
| if K_ctx < self.min_ctx_windows: | |
| out = self._rollout_baseline(cur, H) | |
| return out[0] if H == 1 else out | |
| W_ctx = np.ascontiguousarray(W_all[:K_ctx, :, :]) # (K_ctx,L,D) | |
| Y_true = np.asarray(prefix[self.L : self.L + K_ctx, :], float) # (K_ctx,D) | |
| # Center targets | |
| Y_true_c = (Y_true - self.mu_X[None, :]) if self.center else Y_true | |
| # Encode context psi | |
| Psi_ctx = np.zeros((K_ctx, self.r), dtype=np.float64) | |
| for i in range(K_ctx): | |
| Psi_ctx[i] = self.enc.encode_window(W_ctx[i]) | |
| Psi_ctx_w = self._psi_whiten(Psi_ctx) # (K_ctx,r) | |
| # Baseline on context | |
| Y_glob_ctx_c = np.asarray(self.baseline(Psi_ctx_w), dtype=np.float64) # (K_ctx,D) | |
| # Residual table | |
| E_ctx = Y_true_c - Y_glob_ctx_c # (K_ctx,D) | |
| # Context gate gamma in [0,1] | |
| gamma_ctx = float(K_ctx) / float(K_ctx + self.ctx_k0) if self.ctx_k0 > 0 else 1.0 | |
| # ---------------------------------------------------- | |
| # Rollout with attention residual correction | |
| # ---------------------------------------------------- | |
| out = np.zeros((H, self.D), dtype=np.float64) | |
| for h in range(H): | |
| psi_q = self.enc.encode_window(cur) # (r,) | |
| psi_q_w = self._psi_whiten(psi_q)[0] # (r,) | |
| # Baseline prediction | |
| y_glob_c = np.asarray(self.baseline(psi_q_w), dtype=np.float64).reshape(-1) # (D,) | |
| # Attention weights on psi distances (Markov weights) | |
| diff = Psi_ctx_w - psi_q_w[None, :] # (K_ctx,r) | |
| d2 = np.einsum("kr,kr->k", diff, diff, optimize=True) | |
| if self.attn_topk is not None and self.attn_topk < K_ctx: | |
| k = int(self.attn_topk) | |
| idx = np.argpartition(d2, kth=k - 1)[:k] | |
| w = _softmax_weights_from_d2(d2[idx], self.attn_ell) | |
| e_hat = w @ E_ctx[idx] # (D,) | |
| else: | |
| w = _softmax_weights_from_d2(d2, self.attn_ell) | |
| e_hat = w @ E_ctx # (D,) | |
| # Combine | |
| y_c = y_glob_c + gamma_ctx * e_hat | |
| y = (y_c + self.mu_X) if self.center else y_c | |
| out[h] = y | |
| # Advance window | |
| if self.L > 1: | |
| cur[:-1] = cur[1:] | |
| cur[-1] = y | |
| return out[0] if H == 1 else out | |
| def _rollout_baseline(self, seed_LD: Array, H: int) -> Array: | |
| """ | |
| Baseline-only AR rollout (no IC correction). | |
| """ | |
| cur = np.asarray(seed_LD, dtype=float).copy() | |
| out = np.zeros((int(H), self.D), dtype=np.float64) | |
| for h in range(int(H)): | |
| y = self.predict_one_step_baseline(cur) | |
| out[h] = y | |
| if self.L > 1: | |
| cur[:-1] = cur[1:] | |
| cur[-1] = y | |
| return out | |
| def __repr__(self) -> str: | |
| return ( | |
| f"ALSA(L={self.L}, D={self.D}, r={self.r}, " | |
| f"center={self.center}, whiten_psi={self.whiten_psi}, " | |
| f"attn_topk={self.attn_topk}, attn_ell={self.attn_ell:.4g})" | |
| ) | |
| __all__ = ["ALSA", "ALSAConfig"] | |