Sharp JL Hidden-State Bottleneck / run_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import random
  4import time
  5import numpy as np
  6
  7SEED = 2261
  8np.random.seed(SEED)
  9random.seed(SEED)
 10
 11
 12def projection(d, r, rng):
 13    return rng.normal(0.0, 1.0 / math.sqrt(r), size=(r, d)).astype(np.float64)
 14
 15
 16def pairwise_ratios(X, P, max_pairs=200000, rng=None):
 17    n = len(X)
 18    rng = np.random.default_rng(SEED) if rng is None else rng
 19    total = n * (n - 1) // 2
 20    if total <= max_pairs:
 21        ii, jj = np.triu_indices(n, 1)
 22    else:
 23        ii = rng.integers(0, n, size=max_pairs)
 24        jj = rng.integers(0, n, size=max_pairs)
 25        keep = ii != jj
 26        ii, jj = ii[keep], jj[keep]
 27    dx = X[ii] - X[jj]
 28    dz = (X[ii] - X[jj]) @ P.T
 29    return np.linalg.norm(dz, axis=1) / np.maximum(np.linalg.norm(dx, axis=1), 1e-12)
 30
 31
 32def width_rule(d, n, eps, C=1.0):
 33    raw = min(d, n - 1, math.log(2.0 + eps * eps * n) / (eps * eps))
 34    return max(1, min(d, int(math.ceil(C * raw)))), raw
 35
 36
 37def toy_math_verification():
 38    rng = np.random.default_rng(SEED + 1)
 39    d, n = 128, 180
 40    X = rng.normal(size=(n, d))
 41    # Prediction 1: Gaussian scaling is unbiased: E[||Pv||^2/||v||^2] = 1.
 42    v = rng.normal(size=d)
 43    norm_trials = []
 44    for _ in range(300):
 45        P = projection(d, 32, rng)
 46        norm_trials.append(np.sum((P @ v) ** 2) / np.sum(v ** 2))
 47    norm_mean = float(np.mean(norm_trials))
 48    # Prediction 2: for a fixed pair, ratio has SD approximately 1/sqrt(2r).
 49    fixed_v = X[0] - X[1]
 50    fixed_ratios = []
 51    for _ in range(500):
 52        P = projection(d, 32, rng)
 53        fixed_ratios.append(np.linalg.norm(P @ fixed_v) / np.linalg.norm(fixed_v))
 54    fixed_sd = float(np.std(fixed_ratios, ddof=1))
 55    fixed_sd_pred = 1.0 / math.sqrt(2 * 32)
 56    # Prediction 3: maximum of many pair errors scales as sqrt(log(number pairs)/r).
 57    max_rows = []
 58    for r in [8, 16, 32, 64, 128]:
 59        P = projection(d, r, rng)
 60        ratios = pairwise_ratios(X, P, rng=rng)
 61        max_rows.append({
 62            "r": r,
 63            "max_abs_error": float(np.max(np.abs(ratios - 1))),
 64            "p95_abs_error": float(np.percentile(np.abs(ratios - 1), 95)),
 65            "predicted_max_scale": math.sqrt(2.0 * math.log(max(2, n * (n - 1) / 2)) / r),
 66            "predicted_fixed_sd": 1.0 / math.sqrt(2 * r),
 67        })
 68    # Prediction 4: sharp schedule transitions with n and saturates at d or n-1.
 69    schedule = []
 70    for eps in [0.1, 0.2, 0.3]:
 71        for nn in [8, 32, 128, 512]:
 72            rr, raw = width_rule(128, nn, eps, C=1)
 73            schedule.append({"epsilon": eps, "n": nn, "r": rr, "raw": raw})
 74    return {
 75        "unbiased_norm": {"observed_mean": norm_mean, "predicted": 1.0, "relative_error": abs(norm_mean - 1.0)},
 76        "fixed_pair_sd": {"observed": fixed_sd, "predicted": fixed_sd_pred, "ratio_observed_to_predicted": fixed_sd / fixed_sd_pred},
 77        "max_error_scaling": max_rows,
 78        "sharp_width_schedule": schedule,
 79    }
 80
 81
 82def softmax_loss(W, X, y):
 83    logits = X @ W
 84    logits -= logits.max(axis=1, keepdims=True)
 85    exp = np.exp(logits)
 86    probs = exp / exp.sum(axis=1, keepdims=True)
 87    loss = -np.log(np.maximum(probs[np.arange(len(y)), y], 1e-12)).mean()
 88    grad = X.T @ (probs - np.eye(W.shape[1])[y]) / len(y)
 89    return float(loss), grad
 90
 91
 92def train_linear(Xtr, ytr, Xva, yva, steps=250, lr=0.4):
 93    W = np.zeros((Xtr.shape[1], 2), dtype=np.float64)
 94    for _ in range(steps):
 95        _, g = softmax_loss(W, Xtr, ytr)
 96        W -= lr * g
 97    loss, _ = softmax_loss(W, Xva, yva)
 98    acc = float(np.mean(np.argmax(Xva @ W, axis=1) == yva))
 99    return loss, acc
100
101
102def downstream_experiment():
103    rng = np.random.default_rng(SEED + 2)
104    ntr, nva, d, n_tokens = 2400, 800, 96, 24
105    # Two classes differ in a low-dimensional token mean; unrelated token noise
106    # makes the full-width representation deliberately width-sensitive.
107    ytr = rng.integers(0, 2, ntr)
108    yva = rng.integers(0, 2, nva)
109    def make(y):
110        x = rng.normal(size=(len(y), n_tokens, d))
111        x[:, :, :8] += (2 * y[:, None, None] - 1) * 0.9
112        return x
113    Xtr0, Xva0 = make(ytr), make(yva)
114    full_tr, full_va = Xtr0.mean(axis=1), Xva0.mean(axis=1)
115    full_loss, full_acc = train_linear(full_tr, ytr, full_va, yva)
116    rows = []
117    for eps in [0.1, 0.2, 0.3]:
118        for C in [1, 2, 4]:
119            r, raw = width_rule(d, n_tokens, eps, C)
120            P = projection(d, r, rng)
121            ztr, zva = Xtr0 @ P.T, Xva0 @ P.T
122            loss, acc = train_linear(ztr.mean(axis=1), ytr, zva.mean(axis=1), yva)
123            ratios = pairwise_ratios(Xva0.reshape(-1, d)[:160], P, rng=rng)
124            rows.append({"epsilon": eps, "C": C, "r": r, "raw": raw, "val_loss": loss, "val_accuracy": acc,
125                         "relative_accuracy_loss": (full_acc - acc) / max(full_acc, 1e-12),
126                         "p95_abs_distortion": float(np.percentile(np.abs(ratios - 1), 95)),
127                         "activation_width_ratio": r / d})
128    return {"full_width": {"d": d, "val_loss": full_loss, "val_accuracy": full_acc, "activation_width": d}, "compressed": rows}
129
130
131def main():
132    t0 = time.time()
133    out = {"seed": SEED, "math": toy_math_verification(), "downstream": downstream_experiment()}
134    out["runtime_sec"] = time.time() - t0
135    with open("results.json", "w") as f:
136        json.dump(out, f, indent=2)
137    print(json.dumps(out, indent=2))
138
139
140if __name__ == "__main__":
141    main()