SabaPivot's picture
download
raw
8.54 kB
#!/usr/bin/env python3
"""Independent finite-sample checks for Theorems 3 and 5 of arXiv:2606.01292.
Unlike the first logbook pass, this script does not evaluate the theorem's
closed-form risk proxy. It draws Gaussian training examples, runs minibatch
SGD for every sample size and seed, and estimates population risks directly
from the learned coefficient vectors.
"""
from __future__ import annotations
import csv
import json
from pathlib import Path
import numpy as np
OUT = Path("independent_results")
SEED = 260601292
REPS = 64
def write_csv(name: str, rows: list[dict]) -> None:
OUT.mkdir(parents=True, exist_ok=True)
with (OUT / name).open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
def train_semantic_regressor(
*,
alpha: float,
target: np.ndarray,
samples: int,
reps: int,
noise_sd: float,
seed: int,
batch: int = 32,
eta: float = 0.12,
paper_schedule: bool = False,
) -> tuple[np.ndarray, np.ndarray]:
"""Run fresh finite-sample SGD and return semantic prediction vectors."""
rng = np.random.default_rng(seed)
d = len(target)
lam = np.arange(1, d + 1, dtype=np.float64) ** (-alpha)
roots = np.sqrt(lam)
# Model is <u, sqrt(Lambda) z>; target is <target, z>.
weights = np.zeros((reps, d), dtype=np.float64)
updates = (samples + batch - 1) // batch
if paper_schedule:
# Algorithm 1 uses K=N/log2(N) samples between halvings. For
# sample-wise SGD this is exactly the schedule below.
decay_every = max(1, int(samples / np.log2(samples) / batch))
decay = 0.5
else:
decay_every = max(1, updates // 5)
decay = 0.55
for update in range(updates):
size = min(batch, samples - update * batch)
if size <= 0:
break
z = rng.normal(size=(reps, size, d))
features = z * roots
labels = np.einsum("rbd,d->rb", z, target)
if noise_sd:
labels += noise_sd * rng.normal(size=(reps, size))
prediction = np.einsum("rbd,rd->rb", features, weights)
gradient = np.mean(features * (prediction - labels)[..., None], axis=1)
learning_rate = eta * (decay ** (update // decay_every))
weights -= learning_rate * gradient
semantic = weights * roots
risk = 0.5 * np.sum((semantic - target[None, :]) ** 2, axis=1)
return semantic, risk
def claim2_der() -> dict:
"""Estimate DER from independently trained teacher/student SGD models."""
d = 256
target = np.ones(d, dtype=np.float64) / np.sqrt(d)
sample_grid = np.array([256, 512, 1024, 2048, 4096, 8192, 16384])
alpha_teacher, alpha_student, beta = 1.5, 3.0, 0.0
expected = (alpha_teacher - 1 - beta) * (
1 / alpha_teacher - 1 / alpha_student
)
rows: list[dict] = []
ders = []
for idx, n in enumerate(sample_grid):
teacher, teacher_risk = train_semantic_regressor(
alpha=alpha_teacher,
target=target,
samples=int(n),
reps=REPS,
noise_sd=0.15,
seed=SEED + 1000 * idx,
)
_, student_risk = train_semantic_regressor(
alpha=alpha_student,
target=target,
samples=int(n),
reps=REPS,
noise_sd=0.15,
seed=SEED + 1000 * idx + 1,
)
# With sufficient unlabeled transfer samples, a full-rank compatible
# student copies the teacher's semantic predictor. Its population
# risk is therefore measured from the fitted teacher, not a formula.
transfer_risk = 0.5 * np.sum((teacher - target[None, :]) ** 2, axis=1)
der_seed = student_risk / transfer_risk
ders.append(float(np.mean(der_seed)))
rows.append(
{
"N": int(n),
"repetitions": REPS,
"direct_student_risk_mean": float(np.mean(student_risk)),
"direct_student_risk_se": float(np.std(student_risk, ddof=1) / np.sqrt(REPS)),
"teacher_asymptotic_transfer_risk_mean": float(np.mean(transfer_risk)),
"teacher_asymptotic_transfer_risk_se": float(np.std(transfer_risk, ddof=1) / np.sqrt(REPS)),
"DER_mean_of_seed_ratios": float(np.mean(der_seed)),
"DER_median": float(np.median(der_seed)),
"fraction_DER_above_one": float(np.mean(der_seed > 1)),
}
)
slope = float(np.polyfit(np.log(sample_grid[-4:]), np.log(ders[-4:]), 1)[0])
write_csv("claim2_independent_sgd.csv", rows)
return {
"expected_asymptotic_kappa": expected,
"finite_sample_tail_slope": slope,
"DER_first": ders[0],
"DER_last": ders[-1],
"all_grid_mean_DER_above_one": bool(all(value > 1 for value in ders)),
"last_grid_fraction_seed_DER_above_one": rows[-1]["fraction_DER_above_one"],
"note": "Finite d=256 is expected to flatten before the infinite-dimensional asymptote.",
}
def claim4_pgr() -> dict:
"""Estimate W2S PGR by SGD teacher fitting plus student early stopping."""
d, k_dagger = 100, 10
alpha_teacher, alpha_student = 1.5, 2.0
target = np.zeros(d, dtype=np.float64)
target[:k_dagger] = 0.25
sample_grid = np.array([250, 500, 1000, 2000, 4000, 8000, 16000])
student_lam = np.arange(1, d + 1, dtype=np.float64) ** (-alpha_student)
# Integrated learning-rate grid controls the student's spectral cutoff.
times = np.geomspace(0.05, 2.0e5, 500)
filters = 1.0 - np.exp(-times[:, None] * student_lam[None, :])
rows: list[dict] = []
gaps = []
for idx, n in enumerate(sample_grid):
teacher, teacher_risk = train_semantic_regressor(
alpha=alpha_teacher,
target=target,
samples=int(n),
reps=REPS,
noise_sd=1.0,
seed=SEED + 50000 + 1000 * idx,
batch=1,
eta=0.08,
paper_schedule=True,
)
# Evaluate every early-stopping filter on each independently fitted
# teacher and select one global checkpoint by mean population risk.
error = filters[:, None, :] * teacher[None, :, :] - target[None, None, :]
transfer_risk_by_time_seed = 0.5 * np.sum(error**2, axis=2)
mean_curve = np.mean(transfer_risk_by_time_seed, axis=1)
best_idx = int(np.argmin(mean_curve))
transfer_seed = transfer_risk_by_time_seed[best_idx]
# Fully ground-truth-trained expressive student has zero population
# approximation error in this synthetic model, so 1-PGR=R_T2S/R_T.
gap_seed = transfer_seed / teacher_risk
gaps.append(float(np.mean(gap_seed)))
rows.append(
{
"N": int(n),
"repetitions": REPS,
"teacher_risk_mean": float(np.mean(teacher_risk)),
"optimal_student_time": float(times[best_idx]),
"transfer_risk_mean": float(np.mean(transfer_seed)),
"one_minus_PGR_mean_of_seed_ratios": float(np.mean(gap_seed)),
"PGR_mean": float(1 - np.mean(gap_seed)),
"fraction_student_beats_teacher": float(np.mean(transfer_seed < teacher_risk)),
}
)
empirical_delta = float(-np.polyfit(np.log(sample_grid[-4:]), np.log(gaps[-4:]), 1)[0])
expected_delta = 2 * alpha_student / (
alpha_teacher * (2 * alpha_student + 1)
)
write_csv("claim4_independent_pgr.csv", rows)
return {
"expected_asymptotic_delta": expected_delta,
"finite_sample_tail_delta": empirical_delta,
"PGR_first": 1 - gaps[0],
"PGR_last": 1 - gaps[-1],
"last_grid_fraction_student_beats_teacher": rows[-1]["fraction_student_beats_teacher"],
"all_grid_positive_PGR": bool(all(value < 1 for value in gaps)),
}
def main() -> None:
metrics = {
"design": {
"repetitions": REPS,
"seed": SEED,
"fresh_examples_for_every_N_and_repetition": True,
"risk": "exact population risk from fitted coefficient vectors",
},
"claim2": claim2_der(),
"claim4": claim4_pgr(),
}
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "metrics.json").write_text(json.dumps(metrics, indent=2), encoding="utf-8")
print(json.dumps(metrics, indent=2))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
8.54 kB
·
Xet hash:
778ff150985cd2deee2f1fae7b82aaeb8b8c7250575ce8e12ebb8909978a95ac

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.