ProCreations's picture
Fix the Figure-1 comparison (previous run compared unequal total sample budgets and reported an inversion); add the dimension-exponent arm for Theorem 2's rate
cc68c45 verified
Raw
History Blame Contribute Delete
5.28 kB
"""Claim 5 (Figure 1): subspace reconstruction error is LOWER when training on a
balanced, screened subset of sources than on the full heterogeneous population.
The previous run of this logbook found the opposite. The comparison there gave
the full population MORE TOTAL DATA (99 sources x n) than the screened subset
(36 sources x n), so it conflated "screening helps" with "more data helps".
Screening is a sample-ALLOCATION statement: at a fixed total budget, is it better
to spend it on a balanced admissible subset or spread over everything?
Both readings are measured below.
"""
import json, numpy as np
RES = {}
def population(d=30, k=6, N=99, seed=0):
"""Heterogeneous sources: a minority are well-conditioned in the shared
subspace, the majority are degenerate (covariates collapse onto 1-2
directions), which is the regime screening is meant to fix."""
rng = np.random.default_rng(seed)
B, _ = np.linalg.qr(rng.normal(size=(d, k))) # true shared subspace
srcs = []
for i in range(N):
if i % 3 == 0: # balanced source
A = rng.normal(size=(d, d))*0.5+np.eye(d)
Sig = A @ A.T/d+np.eye(d)*0.5
else: # degenerate source
u = rng.normal(size=(d, 2))
Sig = u @ u.T+np.eye(d)*0.02
alpha = rng.normal(size=k)
srcs.append({"Sigma": Sig, "theta": B @ alpha, "balanced": i % 3 == 0})
return B, srcs
def draw(src, n, rng, noise=0.5):
L = np.linalg.cholesky(src["Sigma"]+1e-8*np.eye(src["Sigma"].shape[0]))
X = rng.normal(size=(n, L.shape[0])) @ L.T
y = X @ src["theta"]+rng.normal(0, noise, size=n)
return X, y
def estimate_subspace(data, k, ridge=1e-3):
"""Per-source ridge estimates, then top-k principal subspace of their
second-moment matrix -- the standard shared-subspace estimator."""
M = np.zeros((data[0][0].shape[1],)*2)
for X, y in data:
d = X.shape[1]
th = np.linalg.solve(X.T @ X/len(X)+ridge*np.eye(d), X.T @ y/len(X))
M += np.outer(th, th)
w, V = np.linalg.eigh(M)
return V[:, -k:]
def subspace_error(Bhat, B):
"""sin of the largest principal angle."""
s = np.linalg.svd(Bhat.T @ B, compute_uv=False)
return float(np.sqrt(max(0.0, 1-min(s)**2)))
def screen(srcs, m):
"""Select m sources greedily maximising lambda_min of the aggregate design
-- the admissibility/spectral-norm criterion the theory uses."""
d = srcs[0]["Sigma"].shape[0]
chosen, S = [], np.zeros((d, d))
cand = list(range(len(srcs)))
for _ in range(m):
best, bi = -1e18, None
for i in cand:
lam = np.linalg.eigvalsh(S+srcs[i]["Sigma"])[0]
if lam > best: best, bi = lam, i
chosen.append(bi); cand.remove(bi); S = S+srcs[bi]["Sigma"]
return chosen
def run():
d, k, N = 30, 6, 99
B, srcs = population(d, k, N)
m = 33
sel = screen(srcs, m)
frac_bal = float(np.mean([srcs[i]["balanced"] for i in sel]))
print(" screened %d/%d sources; fraction balanced = %.2f (population %.2f)"
% (m, N, frac_bal, np.mean([s["balanced"] for s in srcs])), flush=True)
rows = []
for total in (1980, 3960, 7920, 15840): # TOTAL sample budget
e_full, e_scr, e_naive = [], [], []
for rep in range(8):
rng = np.random.default_rng(500+rep)
n_full = total//N # spread over all sources
n_scr = total//m # concentrated on screened
df = [draw(srcs[i], n_full, rng) for i in range(N)]
ds = [draw(srcs[i], n_scr, rng) for i in sel]
dn = [draw(srcs[i], n_full, rng) for i in sel] # unequal-budget control
e_full.append(subspace_error(estimate_subspace(df, k), B))
e_scr.append(subspace_error(estimate_subspace(ds, k), B))
e_naive.append(subspace_error(estimate_subspace(dn, k), B))
rows.append({"total_budget": total, "n_per_source_full": total//N,
"n_per_source_screened": total//m,
"err_full_population": round(float(np.mean(e_full)), 5),
"err_screened_matched_budget": round(float(np.mean(e_scr)), 5),
"err_screened_unmatched": round(float(np.mean(e_naive)), 5),
"screening_helps_matched": bool(np.mean(e_scr) < np.mean(e_full)),
"sd_full": round(float(np.std(e_full)), 5),
"sd_screened": round(float(np.std(e_scr)), 5)})
print(" budget %-6d full=%.5f screened(matched)=%.5f screened(same n/src)=%.5f %s"
% (total, np.mean(e_full), np.mean(e_scr), np.mean(e_naive),
"SCREENING WINS" if np.mean(e_scr) < np.mean(e_full) else "full wins"), flush=True)
RES["claim5_screening"] = {
"d": d, "k": k, "N_sources": N, "m_screened": m, "reps": 8,
"fraction_balanced_in_screened": frac_bal,
"rows": rows,
"screening_wins_at_matched_budget": sum(r["screening_helps_matched"] for r in rows),
"cells": len(rows)}
json.dump(RES, open("screen_results.json", "w"), indent=1)
if __name__ == "__main__":
run()
print("DONE")