Buckets:
| """ | |
| Core independent re-implementation of the definitions and constructions of | |
| "The Interplay Between Interpolation and Aggregation in Regression: | |
| Optimal Sample Complexity" (arXiv 2605.29819, OpenReview qXlovWytwg) | |
| Everything here is written from the *statements* of Definitions 3.1, 3.2, 3.3, | |
| 3.4, 3.6, 3.7, 3.9 and the *constructions* sketched in the proof overviews of | |
| Theorems 3.5, 3.8, 3.10, 3.11, 3.12. No code from the authors was used | |
| (none is released). | |
| CPU only. numpy + itertools. | |
| """ | |
| from __future__ import annotations | |
| import itertools | |
| import math | |
| from dataclasses import dataclass | |
| from typing import Callable, Iterable, Sequence | |
| import numpy as np | |
| # ---------------------------------------------------------------------------- | |
| # Finite hypothesis classes: a class is an (|H| x |X|) matrix of values in [0,1] | |
| # ---------------------------------------------------------------------------- | |
| class FiniteClass: | |
| """H subset of [0,1]^X for a finite domain X = {0,...,m-1}.""" | |
| values: np.ndarray # shape (|H|, |X|), entries in [0,1] | |
| name: str = "" | |
| def n_hyp(self) -> int: | |
| return self.values.shape[0] | |
| def n_pts(self) -> int: | |
| return self.values.shape[1] | |
| # ---------------------------------------------------------------------------- | |
| # Definition 3.1 (gamma-graph shattering) and Definition 3.2 (gamma-graph dim) | |
| # ---------------------------------------------------------------------------- | |
| def _relation_matrix(values: np.ndarray, witness: int, gamma: float) -> np.ndarray: | |
| """R[h, j] = 0 if h(x_j) == witness(x_j) | |
| 1 if |h(x_j) - witness(x_j)| > gamma | |
| 2 otherwise (h unusable at x_j for shattering with this witness) | |
| This is a *verbatim* transcription of Definition 3.1's two bullet points. | |
| """ | |
| w = values[witness] | |
| diff = np.abs(values - w[None, :]) | |
| R = np.full(values.shape, 2, dtype=np.int8) | |
| R[diff == 0.0] = 0 | |
| R[diff > gamma] = 1 | |
| return R | |
| def is_gamma_graph_shattered( | |
| cls: FiniteClass, cols: Sequence[int], gamma: float, witness: int | |
| ) -> bool: | |
| """Definition 3.1: is the sequence (x_j)_{j in cols} gamma-graph shattered | |
| by H with witness h_{witness}?""" | |
| R = _relation_matrix(cls.values, witness, gamma)[:, list(cols)] | |
| d = len(cols) | |
| usable = ~(R == 2).any(axis=1) | |
| if not usable.any(): | |
| return False | |
| pats = R[usable].astype(np.int64) | |
| weights = (1 << np.arange(d)).astype(np.int64) | |
| codes = pats @ weights | |
| return len(np.unique(codes)) == (1 << d) | |
| def gamma_graph_dim( | |
| cls: FiniteClass, | |
| gamma: float, | |
| max_d: int | None = None, | |
| return_witness: bool = False, | |
| witnesses=None, | |
| ): | |
| """Definition 3.2, computed EXHAUSTIVELY. | |
| gamma-graph shattering is downward closed in the point set (drop a | |
| coordinate from every b), so we enumerate level-wise: only supersets of an | |
| already-shattered set can be shattered. For each witness the shattered | |
| families are represented with bitmasks, so a single shattering test is one | |
| vectorised numpy pass over H. | |
| """ | |
| m = cls.n_pts | |
| cap = m if max_d is None else min(m, max_d) | |
| wit_iter = range(cls.n_hyp) if witnesses is None else list(witnesses) | |
| best, best_cert = 0, None | |
| for w in wit_iter: | |
| R = _relation_matrix(cls.values, w, gamma) | |
| bits = (1 << np.arange(m)).astype(np.int64) | |
| vmask = ((R != 2).astype(np.int64) * bits).sum(axis=1) # usable columns | |
| fmask = ((R == 1).astype(np.int64) * bits).sum(axis=1) # 'gamma-far' columns | |
| def shattered(cmask: int, d: int) -> bool: | |
| sel = (vmask & cmask) == cmask | |
| if not sel.any(): | |
| return False | |
| codes = np.unique(fmask[sel] & cmask) | |
| return len(codes) == (1 << d) | |
| level = [(int(bits[j]), (j,)) for j in range(m) if shattered(int(bits[j]), 1)] | |
| if level and best < 1: | |
| best, best_cert = 1, (w, list(level[0][1])) | |
| d = 1 | |
| while level and d < cap: | |
| nxt = [] | |
| for cmask, cols in level: | |
| for j in range(cols[-1] + 1, m): | |
| nm = cmask | int(bits[j]) | |
| if shattered(nm, d + 1): | |
| nxt.append((nm, cols + (j,))) | |
| if not nxt: | |
| break | |
| d += 1 | |
| level = nxt | |
| if d > best: | |
| best, best_cert = d, (w, list(level[0][1])) | |
| if best >= cap: | |
| break | |
| if return_witness: | |
| return best, best_cert | |
| return best | |
| # ---------------------------------------------------------------------------- | |
| # Definition 3.9 (gamma-OIG dimension) -- one-inclusion (hyper)graph machinery | |
| # ---------------------------------------------------------------------------- | |
| def one_inclusion_graph(cls: FiniteClass, cols: Sequence[int]): | |
| """Vertices V = H|_S (distinct restrictions). Hyperedge (f, i) collects all | |
| v in V agreeing with f on every coordinate except i. Returns | |
| (vertices ndarray, list of (direction i, tuple of vertex indices)).""" | |
| sub = cls.values[:, list(cols)] | |
| verts = np.unique(sub, axis=0) | |
| n = len(cols) | |
| edges = [] | |
| for i in range(n): | |
| keys = {} | |
| for vi, v in enumerate(verts): | |
| key = tuple(np.delete(v, i)) | |
| keys.setdefault(key, []).append(vi) | |
| for _, group in keys.items(): | |
| edges.append((i, tuple(group))) | |
| return verts, edges | |
| def min_max_outdegree( | |
| verts: np.ndarray, edges, gamma: float, brute_cap: int = 2_000_000 | |
| ): | |
| """min over orientations sigma of max_v |{i : |sigma(e_{v,i})_i - v_i| > gamma}|. | |
| Singleton edges are self-oriented and contribute 0. For the remaining | |
| (multi-vertex) edges we brute force all orientations when the search space | |
| is small, otherwise we return the value achieved by the paper's explicit | |
| 'orient towards the smallest value in the direction of the edge' rule and | |
| flag it as an upper bound.""" | |
| multi = [(i, g) for (i, g) in edges if len(g) > 1] | |
| nv = len(verts) | |
| if not multi: | |
| return 0, True | |
| space = 1 | |
| for _, g in multi: | |
| space *= len(g) | |
| if space > brute_cap: | |
| break | |
| def outdeg_for(choice): | |
| deg = np.zeros(nv, dtype=int) | |
| for (i, g), tgt in zip(multi, choice): | |
| tv = verts[tgt][i] | |
| for v in g: | |
| if abs(tv - verts[v][i]) > gamma: | |
| deg[v] += 1 | |
| return int(deg.max()) if nv else 0 | |
| if space <= brute_cap: | |
| best = None | |
| for choice in itertools.product(*[g for _, g in multi]): | |
| val = outdeg_for(choice) | |
| if best is None or val < best: | |
| best = val | |
| if best == 0: | |
| break | |
| return best, True | |
| # paper's rule: orient each edge to the vertex with the smallest value in | |
| # the direction of the edge | |
| choice = [min(g, key=lambda v: verts[v][i]) for (i, g) in multi] | |
| return outdeg_for(choice), False | |
| def gamma_oig_dim(cls: FiniteClass, gamma: float, max_k: int = 5, exact: bool = True): | |
| """Definition 3.9. Returns (dimension estimate, per-k detail). | |
| k counts iff there EXISTS a point set S of size k and a finite subgraph for | |
| which EVERY orientation has max out-degree strictly greater than k/3. | |
| We evaluate the full induced one-inclusion graph and (for small instances) | |
| every vertex-induced subgraph, which is the family of subgraphs the paper's | |
| argument ranges over.""" | |
| detail = {} | |
| dim = 0 | |
| m = cls.n_pts | |
| for k in range(1, min(max_k, m) + 1): | |
| worst = 0 | |
| exact_all = True | |
| for cols in itertools.combinations(range(m), k): | |
| verts, edges = one_inclusion_graph(cls, cols) | |
| val, is_exact = min_max_outdegree(verts, edges, gamma) | |
| exact_all = exact_all and is_exact | |
| worst = max(worst, val) | |
| if exact and len(verts) <= 9: | |
| # also scan vertex-induced subgraphs | |
| idx = list(range(len(verts))) | |
| for r in range(2, len(verts) + 1): | |
| for keep in itertools.combinations(idx, r): | |
| sub = FiniteClass(verts[list(keep)][:, :], "sub") | |
| v2, e2 = one_inclusion_graph(sub, range(k)) | |
| val2, ex2 = min_max_outdegree(v2, e2, gamma) | |
| exact_all = exact_all and ex2 | |
| worst = max(worst, val2) | |
| counts = worst > k / 3.0 | |
| detail[k] = { | |
| "min_max_outdegree": worst, | |
| "threshold_k_over_3": k / 3.0, | |
| "counts": bool(counts), | |
| "exact": bool(exact_all), | |
| } | |
| if counts: | |
| dim = k | |
| return dim, detail | |
| # ---------------------------------------------------------------------------- | |
| # Aggregation rules (Definitions 3.4 and 3.7) | |
| # ---------------------------------------------------------------------------- | |
| def rule_median(z: np.ndarray) -> float: | |
| return float(np.median(z)) | |
| def rule_min(z: np.ndarray) -> float: | |
| return float(np.min(z)) | |
| def rule_max(z: np.ndarray) -> float: | |
| return float(np.max(z)) | |
| def rule_mean(z: np.ndarray) -> float: | |
| return float(np.mean(z)) | |
| def rule_midrange(z: np.ndarray) -> float: | |
| return 0.5 * (float(np.min(z)) + float(np.max(z))) | |
| def make_order_statistic(q: float) -> Callable[[np.ndarray], float]: | |
| def r(z: np.ndarray) -> float: | |
| s = np.sort(z) | |
| return float(s[min(len(s) - 1, int(q * len(s)))]) | |
| return r | |
| def is_proper_rule(rule, rng, trials=2000, m_max=7, odd_only=False) -> bool: | |
| """Definition 3.4 checked empirically. `odd_only` restricts the arity to odd | |
| m, which is the regime in which the paper calls the median proper.""" | |
| for _ in range(trials): | |
| m = int(rng.integers(1, m_max + 1)) | |
| if odd_only and m % 2 == 0: | |
| m += 1 | |
| z = rng.random(m) | |
| out = rule(z) | |
| if not np.any(np.isclose(out, z)): | |
| return False | |
| return True | |
| def is_interpolating_rule(rule, rng, trials=2000, m_max=7) -> bool: | |
| """Definition 3.7 checked empirically.""" | |
| for _ in range(trials): | |
| m = int(rng.integers(1, m_max + 1)) | |
| z = rng.random(m) | |
| out = rule(z) | |
| if out < z.min() - 1e-12 or out > z.max() + 1e-12: | |
| return False | |
| return True | |
| # ---------------------------------------------------------------------------- | |
| # Cutoff loss (Eq. 1) | |
| # ---------------------------------------------------------------------------- | |
| def cutoff_loss( | |
| pred: np.ndarray, labels: np.ndarray, masses: np.ndarray, gamma: float | |
| ) -> float: | |
| """L^gamma_D(h) = P_{(x,y)~D}[|h(x)-y| > gamma] for a finitely supported D.""" | |
| return float(masses[np.abs(pred - labels) > gamma].sum()) | |
| def _unique_values(n: int, gamma: float) -> np.ndarray: | |
| """n distinct values gamma_A in (gamma, 1], all within a window of width | |
| < gamma of each other. | |
| The paper only requires gamma_{A} in (gamma, 1] and *unique*. Keeping the | |
| window narrower than gamma additionally guarantees that two distinct unique | |
| values are never gamma-far from each other, which is the regime the proofs | |
| of Theorems 3.8 / 3.10 / 3.12 implicitly work in (there, agreement at a | |
| unique value is the ONLY way another hypothesis can match the witness off | |
| its zero set). Widening the window is exercised as a sensitivity probe. | |
| """ | |
| lo = gamma + 0.05 * gamma | |
| hi = min(1.0, lo + 0.5 * gamma) | |
| if n == 1: | |
| return np.array([lo]) | |
| return np.linspace(lo, hi, n) | |
| # ---------------------------------------------------------------------------- | |
| # Construction A -- Theorem 3.5 hard instance | |
| # shattered set x_1..x_d with witness h; mass 1 - a*eps on x_1, | |
| # a*eps/(d-1) on each of x_2..x_d. Worst-case interpolator returns the | |
| # hypothesis that is gamma-far from the witness on every unobserved point. | |
| # ---------------------------------------------------------------------------- | |
| def thm35_class(d: int, gamma: float) -> FiniteClass: | |
| """Smallest class realising gamma-graph dimension exactly d: the witness is | |
| all-zero on d points and for each b in {0,1}^d there is h_b that is 0 where | |
| b_i = 0 and (gamma+delta) where b_i = 1.""" | |
| hi = min(1.0, gamma + (1.0 - gamma) / 2.0) | |
| rows = [] | |
| for b in itertools.product([0, 1], repeat=d): | |
| rows.append([hi if bi else 0.0 for bi in b]) | |
| return FiniteClass(np.array(rows, dtype=float), f"thm35_d{d}") | |
| def thm35_masses(d: int, eps: float, a: float = 2.0) -> np.ndarray: | |
| """1 - a*eps on the heavy point, a*eps/(d-1) on each of the other d-1.""" | |
| p = np.empty(d) | |
| p[0] = 1.0 - a * eps | |
| p[1:] = a * eps / (d - 1) | |
| return p | |
| def thm35_expected_loss_exact(d: int, eps: float, n: int, a: float = 2.0) -> float: | |
| """Exact E_S[L^gamma_D(A'(S))] lower bound for ANY interpolator-based | |
| aggregation with a proper rule: the predictor is gamma-far from the witness | |
| on every point missing from S, so the loss is at least the mass of the | |
| missing light points. (The heavy point is essentially always observed; we | |
| include it exactly anyway.)""" | |
| p = thm35_masses(d, eps, a) | |
| return float(np.sum(p * (1.0 - p) ** n)) | |
| # ---------------------------------------------------------------------------- | |
| # Construction B -- Theorem 3.8 "Cantor-like" class | |
| # H = {h_A : A subset of [ku], |A| = d}, h_A = 0 on A, unique gamma_A > gamma | |
| # elsewhere. | |
| # ---------------------------------------------------------------------------- | |
| def thm38_class( | |
| ku: int, d: int, gamma: float | |
| ) -> tuple[FiniteClass, list[tuple[int, ...]]]: | |
| subsets = list(itertools.combinations(range(ku), d)) | |
| n_sub = len(subsets) | |
| rows = np.zeros((n_sub, ku)) | |
| # unique value gamma_A in (gamma, 1] for each A | |
| uniq = _unique_values(n_sub, gamma) | |
| for i, A in enumerate(subsets): | |
| rows[i, :] = uniq[i] | |
| for x in A: | |
| rows[i, x] = 0.0 | |
| return FiniteClass(rows, f"thm38_ku{ku}_d{d}"), subsets | |
| # ---------------------------------------------------------------------------- | |
| # Construction C -- Theorem 3.10 split-space class (one block k = i^2) | |
| # H_k = {h_{k,A} : A subset [k], |A| = sqrt(k)}, 0 on A, unique value elsewhere | |
| # ---------------------------------------------------------------------------- | |
| def thm310_block_class( | |
| k: int, gamma: float | |
| ) -> tuple[FiniteClass, list[tuple[int, ...]]]: | |
| s = int(round(math.isqrt(k))) | |
| assert s * s == k, "k must be a perfect square" | |
| subsets = list(itertools.combinations(range(k), s)) | |
| rows = np.zeros((len(subsets), k)) | |
| uniq = _unique_values(len(subsets), gamma) | |
| for i, A in enumerate(subsets): | |
| rows[i, :] = uniq[i] | |
| for x in A: | |
| rows[i, x] = 0.0 | |
| return FiniteClass(rows, f"thm310_k{k}"), subsets | |
| # ---------------------------------------------------------------------------- | |
| # Construction D -- Theorem 3.12 class | |
| # block k: H_k = {h_{k,A}: A subset [k], |A| = d-1}, h_{k,A} = 0 on [k]\A, | |
| # unique value gamma_{k,A} > gamma on A (and on every other block). | |
| # ---------------------------------------------------------------------------- | |
| def thm312_block_class( | |
| k: int, d: int, gamma: float | |
| ) -> tuple[FiniteClass, list[tuple[int, ...]]]: | |
| subsets = list(itertools.combinations(range(k), d - 1)) | |
| rows = np.zeros((len(subsets), k)) | |
| uniq = _unique_values(len(subsets), gamma) | |
| for i, A in enumerate(subsets): | |
| rows[i, :] = 0.0 | |
| for x in A: | |
| rows[i, x] = uniq[i] | |
| return FiniteClass(rows, f"thm312_k{k}_d{d}"), subsets | |
| def thm312_class(ks, d: int, gamma: float): | |
| """The FULL Theorem 3.12 class over a split input space X = union_k X_k. | |
| h_{k,A} (A subset of [k], |A| = d-1) is 0 on {(k,x) : x in [k]\\A}, equal to | |
| the unique value gamma_{k,A} in (gamma,1] on {(k,x) : x in A}, and equal to | |
| that same unique value on EVERY point of every other block. The extra | |
| blocks push the gamma-graph dimension from d-1 up to exactly d.""" | |
| ks = list(ks) | |
| offsets, tot = {}, 0 | |
| for k in ks: | |
| offsets[k] = tot | |
| tot += k | |
| specs = [] | |
| for k in ks: | |
| for A in itertools.combinations(range(k), d - 1): | |
| specs.append((k, A)) | |
| rows = np.zeros((len(specs), tot)) | |
| uniq = _unique_values(len(specs), gamma) | |
| for i, (k, A) in enumerate(specs): | |
| rows[i, :] = uniq[i] # unique value everywhere ... | |
| off = offsets[k] | |
| for x in range(k): # ... except zeros on [k]\A inside block k | |
| if x not in A: | |
| rows[i, off + x] = 0.0 | |
| return FiniteClass(rows, f"thm312_full_d{d}"), specs, offsets | |
| def thm310_class(ks, gamma: float): | |
| """The FULL Theorem 3.10 class over the split space X = union_{k=i^2} X_k: | |
| h_{k,A} (A subset of [k], |A| = sqrt(k)) is 0 on {(k,x): x in A} and equal | |
| to a unique value gamma_{k,A} in (gamma,1] on every other point of X.""" | |
| ks = list(ks) | |
| offsets, tot = {}, 0 | |
| for k in ks: | |
| offsets[k] = tot | |
| tot += k | |
| specs = [] | |
| for k in ks: | |
| s = math.isqrt(k) | |
| assert s * s == k | |
| for A in itertools.combinations(range(k), s): | |
| specs.append((k, A)) | |
| rows = np.zeros((len(specs), tot)) | |
| uniq = _unique_values(len(specs), gamma) | |
| for i, (k, A) in enumerate(specs): | |
| rows[i, :] = uniq[i] | |
| off = offsets[k] | |
| for x in A: | |
| rows[i, off + x] = 0.0 | |
| return FiniteClass(rows, "thm310_full"), specs, offsets | |
| # ---------------------------------------------------------------------------- | |
| # helpers | |
| # ---------------------------------------------------------------------------- | |
| def fit_loglog_slope(xs: Iterable[float], ys: Iterable[float]) -> tuple[float, float]: | |
| x = np.log(np.asarray(list(xs), dtype=float)) | |
| y = np.log(np.asarray(list(ys), dtype=float)) | |
| A = np.vstack([x, np.ones_like(x)]).T | |
| coef, *_ = np.linalg.lstsq(A, y, rcond=None) | |
| resid = y - A @ coef | |
| ss_res = float((resid**2).sum()) | |
| ss_tot = float(((y - y.mean()) ** 2).sum()) | |
| r2 = 1.0 - ss_res / ss_tot if ss_tot > 0 else float("nan") | |
| return float(coef[0]), r2 | |
| def dump_json(path, obj): | |
| import json | |
| def default(o): | |
| if isinstance(o, (np.integer,)): | |
| return int(o) | |
| if isinstance(o, (np.floating,)): | |
| return float(o) | |
| if isinstance(o, (np.ndarray,)): | |
| return o.tolist() | |
| if isinstance(o, (np.bool_,)): | |
| return bool(o) | |
| raise TypeError(str(type(o))) | |
| with open(path, "w") as f: | |
| json.dump(obj, f, indent=2, default=default) | |
| return path | |
Xet Storage Details
- Size:
- 18.7 kB
- Xet hash:
- 9057b9aef7d0448b4e6a176261f875b486baebe76c9435c85b528166a2f1bbdb
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.