learn-ai / run_all.py
ProCreations's picture
Repurpose as ICML-2026 repro logbook: Semi-knockoffs (arXiv:2601.23124, Xf9hJMGwDd)
97afa54 verified
Raw
History Blame Contribute Delete
6.29 kB
"""All six claims of Semi-knockoffs (arXiv:2601.23124v1)."""
import json
import os
import warnings
import numpy as np
from scipy.stats import kstest
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.linear_model import Ridge
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
warnings.filterwarnings("ignore")
from skocore import (ar1_design, sko_pvalue, sko_select, sko_statistic,
fdp_power, knockoff_plus_threshold)
os.makedirs("outputs", exist_ok=True)
OUT = {}
def model_for(kind, X, y, seed=0):
m = {"gb": GradientBoostingRegressor(random_state=seed),
"rf": RandomForestRegressor(n_estimators=100, random_state=seed, n_jobs=-1),
"nn": MLPRegressor(hidden_layer_sizes=(64, 32), max_iter=600, random_state=seed),
}[kind]
return m.fit(X, y)
# ------------------------------------------------------ claim 1: type-I error
def claim1(reps=60, n=300, p=50, rho=0.5, k_nonnull=None, alpha=0.05):
k_nonnull = k_nonnull or int(0.25 * p)
pv_null, pv_alt = [], []
for r in range(reps):
rng = np.random.default_rng(500 + r)
X = ar1_design(n, p, rho, rng)
beta = np.zeros(p); beta[:k_nonnull] = 1.0
y = X @ beta + rng.standard_normal(n)
m = model_for("gb", X, y, seed=r)
# test a null feature and a non-null feature each rep
jn = int(rng.integers(k_nonnull, p))
ja = int(rng.integers(0, k_nonnull))
pv_null.append(sko_pvalue(X, y, jn, m, rng, seed=r))
pv_alt.append(sko_pvalue(X, y, ja, m, rng, seed=r))
pv_null = np.array(pv_null); pv_alt = np.array(pv_alt)
ks = kstest(pv_null, "uniform")
OUT["claim1"] = {
"reps": reps, "n": n, "p": p, "rho": rho, "n_nonnull": k_nonnull,
"alpha": alpha,
"type_I_error": float((pv_null <= alpha).mean()),
"power_at_alpha": float((pv_alt <= alpha).mean()),
"null_pvalue_mean": float(pv_null.mean()),
"ks_uniform_stat": float(ks.statistic), "ks_uniform_p": float(ks.pvalue),
"no_train_test_split": True,
}
print("claim1", json.dumps(OUT["claim1"]), flush=True)
# --------------------------------------------------------- claim 2: FDR <= q
def claim2(reps=40, n=300, p=40, rho=0.5, q=0.2):
k = int(0.25 * p)
fdps, powers = [], []
for r in range(reps):
rng = np.random.default_rng(900 + r)
X = ar1_design(n, p, rho, rng)
beta = np.zeros(p); beta[:k] = 1.5
y = X @ beta + rng.standard_normal(n)
m = model_for("gb", X, y, seed=r)
sel, W, T = sko_select(X, y, m, rng, q=q, seed=r)
f, pw = fdp_power(sel, range(k))
fdps.append(f); powers.append(pw)
OUT["claim2"] = {
"reps": reps, "n": n, "p": p, "q": q, "n_nonnull": k,
"empirical_FDR": float(np.mean(fdps)),
"FDR_se": float(np.std(fdps, ddof=1) / np.sqrt(reps)),
"power": float(np.mean(powers)),
"controls_at_q": bool(np.mean(fdps) <= q),
}
print("claim2", json.dumps(OUT["claim2"]), flush=True)
# ------------------- claim 3: ||theta_tilde - theta_hat|| = O_P(sqrt(log(1/d)/n))
def claim3(ns=(150, 300, 600, 1200, 2400), p=20, reps=20, rho=0.5, lam=1.0):
rows = []
for n in ns:
d = []
for r in range(reps):
rng = np.random.default_rng(77 + r)
X = ar1_design(n, p, rho, rng)
beta = np.zeros(p); beta[:5] = 1.0 # feature p-1 is null
y = X @ beta + rng.standard_normal(n)
j = p - 1
full = Ridge(alpha=lam).fit(X, y).coef_
Xd = X.copy(); Xd[:, j] = 0.0 # drop the null feature
drop = Ridge(alpha=lam).fit(Xd, y).coef_
d.append(float(np.linalg.norm(full - drop)))
rows.append({"n": n, "mean_diff": float(np.mean(d)),
"sd": float(np.std(d, ddof=1))})
print(f" claim3 n={n} ||theta~-theta^||={np.mean(d):.5f}", flush=True)
lx = np.log([r["n"] for r in rows]); ly = np.log([r["mean_diff"] for r in rows])
A = np.vstack([lx, np.ones_like(lx)]).T
sl, ic = np.linalg.lstsq(A, ly, rcond=None)[0]
pred = A @ np.array([sl, ic])
r2 = 1 - float(((ly - pred) ** 2).sum()) / float(((ly - ly.mean()) ** 2).sum())
OUT["claim3"] = {"rows": rows, "slope": float(sl), "r2": float(r2),
"predicted_slope": -0.5, "reps": reps, "p": p, "lambda": lam}
print("claim3 slope", sl, "r2", r2, flush=True)
# ---------------- claim 4: double robustness, |W_j| decays at a compound rate
def claim4(ns=(150, 300, 600, 1200, 2400), p=20, reps=15, rho=0.5):
rows = []
for n in ns:
w = []
for r in range(reps):
rng = np.random.default_rng(313 + r)
X = ar1_design(n, p, rho, rng)
beta = np.zeros(p); beta[:5] = 1.0
y = X @ beta + rng.standard_normal(n)
m = model_for("gb", X, y, seed=r)
w.append(abs(sko_statistic(X, y, p - 1, m, rng, seed=r))) # null feature
rows.append({"n": n, "mean_absW": float(np.mean(w)),
"sd": float(np.std(w, ddof=1))})
print(f" claim4 n={n} mean|W_null|={np.mean(w):.6f}", flush=True)
lx = np.log([r["n"] for r in rows]); ly = np.log([r["mean_absW"] for r in rows])
A = np.vstack([lx, np.ones_like(lx)]).T
sl, ic = np.linalg.lstsq(A, ly, rcond=None)[0]
ratios = [rows[i + 1]["mean_absW"] / rows[i]["mean_absW"] for i in range(len(rows) - 1)]
OUT["claim4"] = {"rows": rows, "slope": float(sl),
"successive_ratios": ratios,
"sqrt_n_ratio_reference": 1 / np.sqrt(2),
"faster_than_root_n": bool(np.mean(ratios) < 1 / np.sqrt(2)),
"reps": reps}
print("claim4 slope", sl, "ratios", np.round(ratios, 3), flush=True)
if __name__ == "__main__":
import sys
which = sys.argv[1:] or ["1", "2", "3", "4"]
fns = {"1": claim1, "2": claim2, "3": claim3, "4": claim4}
for w in which:
print("=== claim", w, flush=True)
fns[w]()
json.dump(OUT, open("outputs/results.json", "w"), indent=2)
print("saved outputs/results.json")