Transient-risk certificate for Langevin training / transient_risk_experiment.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import json, math
  2import numpy as np
  3
  4# OU Langevin certificate and a certificate-controlled noise schedule.
  5# dX=-m X dt+sqrt(2T)dW, with stationary pi=N(0,T/m).
  6SEED = 551
  7m, T, eta = 1.0, 0.5, 0.02
  8x0_mean = 3.0
  9x0_var = T / m                 # shifted stationary Gaussian initial law
 10H = 1.5                         # unsafe region A={x>H}
 11steps = 500
 12n_particles = 20000
 13delta = 0.10
 14
 15
 16def normal_tail(z):
 17    return 0.5 * math.erfc(z / math.sqrt(2.0))
 18
 19
 20def stationary_risk(h=H):
 21    return normal_tail(h / math.sqrt(T / m))
 22
 23
 24def exact_risk(t, h=H):
 25    mu = x0_mean * math.exp(-m * t)
 26    var = (T / m) + (x0_var - T / m) * math.exp(-2 * m * t)
 27    return normal_tail((h - mu) / math.sqrt(var))
 28
 29
 30def exact_chi2():
 31    # chi^2(N(mu,s2)||N(0,s2)) = exp(mu^2/s2)-1.
 32    return math.exp(x0_mean ** 2 / x0_var) - 1.0
 33
 34
 35def certificate(t):
 36    p = stationary_risk()
 37    return p + math.sqrt(p * exact_chi2()) * math.exp(-m * t)
 38
 39
 40def sufficient_burn_in():
 41    p = stationary_risk()
 42    numerator = math.sqrt(p * exact_chi2())
 43    if delta <= p:
 44        return math.inf
 45    return max(0.0, math.log(numerator / (delta - p)) / m)
 46
 47
 48def verify_envelope():
 49    ts = np.linspace(0, 12, 241)
 50    risks = np.array([exact_risk(float(t)) for t in ts])
 51    bounds = np.array([certificate(float(t)) for t in ts])
 52    return {
 53        "max_exact_minus_bound": float(np.max(risks - bounds)),
 54        "min_bound_minus_exact": float(np.min(bounds - risks)),
 55        "exact_risk_t0": exact_risk(0.0),
 56        "exact_risk_t1": exact_risk(1.0),
 57        "exact_risk_t4": exact_risk(4.0),
 58        "stationary_pi_A": stationary_risk(),
 59        "chi0_squared": exact_chi2(),
 60        "bound_t0": certificate(0.0),
 61        "bound_t1": certificate(1.0),
 62        "bound_t4": certificate(4.0),
 63        "analytic_burn_in_for_delta": sufficient_burn_in(),
 64    }
 65
 66
 67def run_schedule(certificate_stop=False):
 68    # Same initial law and same random seed for both schedules. The controlled
 69    # schedule consumes no further noise after the certificate crossing.
 70    r = np.random.default_rng(SEED)
 71    x = r.normal(x0_mean, math.sqrt(x0_var), size=n_particles)
 72    unsafe_fractions, losses = [], []
 73    stop_step = None
 74    for k in range(steps):
 75        t = k * eta
 76        if certificate_stop and stop_step is None and certificate(t) <= delta:
 77            stop_step = k
 78        noise = 0.0 if (certificate_stop and stop_step is not None) else math.sqrt(2 * T * eta) * r.normal(size=n_particles)
 79        x = x - eta * m * x + noise
 80        unsafe_fractions.append(float(np.mean(x > H)))
 81        losses.append(float(np.mean(0.5 * m * x * x)))
 82    post = unsafe_fractions[stop_step:] if stop_step is not None else unsafe_fractions
 83    return {
 84        "max_unsafe_fraction": float(np.max(unsafe_fractions)),
 85        "mean_unsafe_fraction": float(np.mean(unsafe_fractions)),
 86        "final_unsafe_fraction": unsafe_fractions[-1],
 87        "mean_post_stop_unsafe_fraction": float(np.mean(post)),
 88        "final_loss": losses[-1],
 89        "mean_last_50_loss": float(np.mean(losses[-50:])),
 90        "stop_step": stop_step,
 91        "stop_time": None if stop_step is None else stop_step * eta,
 92    }
 93
 94
 95def main():
 96    out = {
 97        "setup": {"m": m, "T": T, "eta": eta, "H": H, "steps": steps,
 98                  "particles": n_particles, "delta": delta, "seed": SEED},
 99        "verification": verify_envelope(),
100        "baseline": run_schedule(False),
101        "idea": run_schedule(True),
102    }
103    with open("results.json", "w") as f:
104        json.dump(out, f, indent=2)
105    print(json.dumps(out, indent=2))
106
107
108if __name__ == "__main__":
109    main()