ProCreations's picture
Publish validated ICML reproduction
4adaccc verified
Raw
History Blame Contribute Delete
5.79 kB
# perfo_runs.py
import os
import numpy as np
import itertools
import typer
from typer_config import use_yaml_config
def _toeplitz_rect(g, h, gamma):
i = np.arange(g)[:, None]
j = np.arange(h)[None, :]
return gamma ** np.abs(i - j)
def sample_X(rng, rows, rho, gamma,g,h):
if np.isclose(rho, 0) and np.isclose(gamma,0):
return rng.standard_normal((rows, g+h))
Tg = _toeplitz_rect(g, g, gamma)
Th = _toeplitz_rect(h, h, gamma)
Tgh = _toeplitz_rect(g, h, gamma)
S = np.block([[Tg, rho * Tgh], [rho * Tgh.T, Th]])
L = np.linalg.cholesky(S)
assert np.isfinite(L).all()
Z = rng.standard_normal((rows, g + h))
return Z @ L.T
app = typer.Typer()
@app.command()
@use_yaml_config()
def main(unbalanced:float,
n: int,
kappa: float,
lam_start: float,
lam_stop: float,
lam_step: float,
runs: int,
steps: int,
sigma: float,
gamma: float,
rho: float,
b: float,
c: float,
on_perfo: bool):
# To use with slurm
run_id = os.getenv("SLURM_ARRAY_TASK_ID", None)
outdir = "results_perfosame"
param = {
"sigma": sigma,
"kappa": kappa,
"rho": rho,
"gamma": gamma,
"unbalanced": unbalanced,
"b": b,
"c": c,
"onperfo": on_perfo
}
if run_id is not None:
outdir = f"out/results_{os.getenv('SLURM_JOB_NAME', 'unkown')}_{os.getenv('SLURM_ARRAY_JOB_ID', 'unkown')}_{os.getenv('SLURM_PROCID', 'unkown')}"
grid_parameters = {
"sigma": [.2, .5, 1],
"kappa": [1.1, 2],
"rho": [0, .5],
"gamma": [0, .5, .9],
"unbalanced": [0.5, .8],
"b": [0, 0.2],
"c": [0, 0.2]
}
keys, values = zip(*sorted(grid_parameters.items()))
params = []
for point_values in itertools.product(*values):
params.append({k: v for k, v in zip(keys, point_values)})
run_id = int(run_id)
param = params[run_id]
sigma = param["sigma"]
kappa = param["kappa"]
rho = param["rho"]
gamma = param["gamma"]
unbalanced = param["unbalanced"]
b = param["b"]
c = param["c"]
print(param)
p = int(n * kappa)
assert p % 2 == 0
h = int(p * unbalanced)
g = p - h
dvec = np.zeros(p, dtype=np.float64)
dvec[: g] = b
dvec[g :] = c
# performative diagonal D: first half 0.2, rest 0.0 (apply with elementwise multiply)
print(f"Hello sigma_{sigma}_kappa_{kappa}_rho_{rho}_gamma_{gamma}_u_{unbalanced}_b_{b}_c_{c}_perfo_{on_perfo}")
sigma2 = sigma**2
use_test = False # True -> empirical test MSE; False -> analytic ||θ̂-θ*||^2 + σ^2
n_test = 10**5
test_chunk = 20000
os.makedirs(outdir, exist_ok=True)
I_n = np.eye(n)
lams = np.arange(lam_start, lam_stop, lam_step, dtype=np.float64)
def empirical_mse(theta_hat, seed):
rng = np.random.default_rng(seed) # same test set for every λ
total = 0.0
seen = 0
for start in range(0, n_test, test_chunk):
m = min(test_chunk, n_test - start)
Xb = sample_X(rng, m, p, rho) # <-- correlated test features
yb = Xb @ theta_hat_star + sigma * rng.standard_normal(m) # untouched test y
err = (Xb @ theta_hat) - yb
total += float(err @ err)
seen += m
return total / seen
def one_run(seed: int, rid: int, param):
rng = np.random.default_rng(seed)
# theta*
v = rng.standard_normal(g); v /= np.linalg.norm(v)
theta_star = np.zeros(p); theta_star[: g] = v
# per-λ estimator carried across steps
theta_prev = np.zeros((p, lams.size), dtype=np.float64)
theta_prevprev = np.zeros((p, lams.size), dtype=np.float64)
for s in range(1, steps + 1):
# fresh correlated X, eps each step
X = sample_X(rng, n, rho, gamma, g, h)
eps = sigma * rng.standard_normal(n)
y_base = X @ theta_star + eps
G = (X @ X.T) / float(p)
theta_new = np.empty_like(theta_prev)
for i, lam in enumerate(lams):
y = y_base if s == 1 else y_base + X @ (dvec * theta_prev[:, i])
w = np.linalg.solve(G + lam * I_n, y / float(p))
theta_new[:, i] = X.T @ w
theta_prevprev = theta_prev
theta_prev = theta_new
# final-step risks
R = np.empty(lams.shape, dtype=np.float64)
if use_test:
seed_test = seed + 10**6
# cache θ* for empirical_mse
global theta_hat_star
theta_hat_star = theta_star
for i in range(lams.size):
theta_hat = theta_prev[:, i]
R[i] = empirical_mse(theta_hat, seed_test)
else:
for i in range(lams.size):
if not on_perfo:
d = theta_prev[:, i] - theta_star
else:
d = theta_prev[:, i] - (theta_star + dvec * theta_prevprev[:, i])
R[i] = (d @ d) + sigma2
base = "_".join(f"{k}_{v}" for k, v in sorted(param.items()))
filename = os.path.join(outdir, f"run_{base}_{rid}.npz")
assert not os.path.exists(filename)
np.savez(filename, lambdas=lams, risks=R, run=rid, sigma2=sigma2, steps=steps, n=n, p=p, param=param, **param)
return filename
for r in range(1, runs + 1):
filename = one_run(seed=1234 + r, rid=r, param=param)
print(f"Saved {filename}")
if __name__ == "__main__":
app()
# grid_params()