Schur Interaction Monitor for Adaptive Hyperparameters / schur_monitor_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2from pathlib import Path
  3import numpy as np
  4
  5
  6def schur_monitor(H, G, lam=1e-3, beta=1.0):
  7    """Return R=G.T (H+lam I)^-1 G and the trust update operator."""
  8    H = np.asarray(H, dtype=float)
  9    G = np.asarray(G, dtype=float)
 10    A = H + lam * np.eye(H.shape[0])
 11    X = np.linalg.solve(A, G)
 12    R = 0.5 * (G.T @ X + X.T @ G)
 13    # eigh is stable and makes tiny numerical asymmetry harmless
 14    ev, V = np.linalg.eigh(R)
 15    ev = np.maximum(ev, 0.0)
 16    R_psd = (V * ev) @ V.T
 17    trust = np.linalg.inv(np.eye(G.shape[1]) + beta * R_psd)
 18    return R_psd, trust
 19
 20
 21def reduced_energy(sigma, u, H, G, a):
 22    # E is affine in u before relaxation: .5 sigma'Hsigma + sigma'Gu + a'u
 23    return .5 * sigma @ H @ sigma + sigma @ G @ u + a @ u
 24
 25
 26def verify_identity(rng):
 27    k, m = 4, 3
 28    A = rng.normal(size=(k, k))
 29    H = A.T @ A + 0.7 * np.eye(k)
 30    G = rng.normal(size=(k, m))
 31    a = rng.normal(size=m)
 32    HinvG = np.linalg.solve(H, G)
 33    R = G.T @ HinvG
 34    # Relax sigma*(u)=-H^-1 G u and finite-difference the reduced energy.
 35    def reduced(u):
 36        sigma = -np.linalg.solve(H, G @ u)
 37        return reduced_energy(sigma, u, H, G, a)
 38    u0 = rng.normal(size=m)
 39    eps = 2e-4
 40    numerical = np.zeros((m, m))
 41    for i in range(m):
 42        ei = np.eye(m)[i]
 43        for j in range(m):
 44            ej = np.eye(m)[j]
 45            numerical[i, j] = (reduced(u0+eps*ei+eps*ej)-reduced(u0+eps*ei-eps*ej)
 46                - reduced(u0-eps*ei+eps*ej)+reduced(u0-eps*ei-eps*ej))/(4*eps**2)
 47    target = -R
 48    eig_R = np.linalg.eigvalsh(R)
 49    return {
 50        "max_abs_hessian_error": float(np.max(np.abs(numerical-target))),
 51        "R_eigenvalues": eig_R.tolist(),
 52        "min_R_eigenvalue": float(eig_R.min()),
 53        "identity_pass": bool(np.max(np.abs(numerical-target)) < 2e-6 and eig_R.min() >= -1e-10),
 54    }
 55
 56
 57def run_control(rng, steps=80):
 58    # Two mechanism amplitudes. R has one very interactive direction.
 59    H = np.diag([0.25, 1.0])
 60    G = np.array([[2.7, 2.7], [0.15, -0.15]])
 61    R, trust = schur_monitor(H, G, lam=0.02, beta=1.0)
 62    # Smooth bounded target loss, with a deliberately aggressive hyper-step.
 63    target = np.array([1.0, -1.0])
 64    Q = np.diag([1.0, 1.0])
 65    eta = 0.72
 66    rows = {"baseline": [], "schur": []}
 67    noise = 0.015 * rng.normal(size=(steps, 2))
 68    for name, P in [("baseline", np.eye(2)), ("schur", trust)]:
 69        u = np.array([0.0, 0.0])
 70        for t in range(steps):
 71            # Hypergradient of a quadratic validation proxy, plus reproducible mild noise.
 72            h = Q @ (u-target) + noise[t]
 73            delta = -eta * P @ h
 74            u = np.clip(u + delta, -3.0, 3.0)
 75            loss = 0.5 * (u-target) @ Q @ (u-target)
 76            rows[name].append({"step": t+1, "loss": float(loss), "u_norm": float(np.linalg.norm(u)),
 77                               "delta_norm": float(np.linalg.norm(delta))})
 78    def summarize(x):
 79        losses = np.array([z["loss"] for z in x])
 80        return {"final_loss": float(losses[-1]), "best_loss": float(losses.min()),
 81                "loss_spikes": int(np.sum(losses[1:] > 1.2 * losses[:-1])),
 82                "mean_last10": float(losses[-10:].mean()),
 83                "max_u_norm": float(max(z["u_norm"] for z in x))}
 84    return {"H": H.tolist(), "G": G.tolist(), "R": R.tolist(),
 85            "R_eigenvalues": np.linalg.eigvalsh(R).tolist(),
 86            "trust_matrix": trust.tolist(), "eta": eta,
 87            "summary": {k: summarize(v) for k,v in rows.items()}, "trace": rows}
 88
 89
 90def main():
 91    rng = np.random.default_rng(491)
 92    verification = verify_identity(rng)
 93    experiment = run_control(rng)
 94    out = {"verification": verification, "experiment": experiment}
 95    Path("results.json").write_text(json.dumps(out, indent=2))
 96    print(json.dumps({"verification": verification, "summary": experiment["summary"],
 97                      "R_eigenvalues": experiment["R_eigenvalues"]}, indent=2))
 98
 99if __name__ == "__main__":
100    main()