Composite Density-Power Loss / stage2_bench.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import sys, json, random
  2import numpy as np
  3import torch
  4import torch.nn.functional as F
  5
  6sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
  7from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report
  8
  9SEEDS = tuple(range(8))
 10SWEEP_SEEDS = tuple(range(4))
 11LRS = [1e-3, 3e-3, 1e-2]
 12ALPHAS = [0.3, 0.5, 0.7]
 13EPOCHS = 4
 14NTR, NTE = 300, 150
 15NOISE = 0.20
 16
 17
 18def seed_all(seed):
 19    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 20    if torch.cuda.is_available():
 21        try: torch.cuda.manual_seed_all(seed)
 22        except Exception: pass
 23
 24
 25def dpd_loss(logits, y, alpha):
 26    p = F.softmax(logits.float(), dim=-1)
 27    A = p.pow(1.0 + alpha).sum(dim=-1)
 28    q = p.gather(1, y[:, None]).squeeze(1).clamp_min(1e-12)
 29    return (A - (1.0 + 1.0 / alpha) * q.pow(alpha)).mean()
 30
 31
 32def corrupted_ds(seed):
 33    d = get_dataset("vision", seed=seed, n_train=NTR, n_test=NTE)
 34    rng = np.random.RandomState(seed + 99173)
 35    y = d["ytr"].clone()
 36    mask = torch.as_tensor(rng.rand(len(y)) < NOISE)
 37    new = torch.as_tensor(rng.randint(0, 10, len(y)), dtype=torch.long)
 38    new = torch.where(new == y, (new + 1) % 10, new)
 39    y[mask] = new[mask]
 40    d["ytr"] = y
 41    d["corrupt_mask"] = mask
 42    return d
 43
 44
 45def run(cfg, seed, return_model=False):
 46    seed_all(seed)
 47    d = corrupted_ds(seed)
 48    net = make_model("cnn_small", d["input_shape"], d["out_dim"])
 49    # This is a custom loop only because the intervention changes the loss.
 50    try:
 51        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 52        net = net.to(device)
 53        x, y = d["xtr"].to(device), d["ytr"].to(device)
 54        opt = torch.optim.Adam(net.parameters(), lr=float(cfg["lr"]))
 55        gen = torch.Generator(device="cpu").manual_seed(seed + 12345)
 56        for _ in range(EPOCHS):
 57            order = torch.randperm(len(x), generator=gen)
 58            for start in range(0, len(x), 128):
 59                ix = order[start:start+128].to(device)
 60                logits = net(x[ix])
 61                loss = (F.cross_entropy(logits, y[ix]) if cfg["kind"] == "ce"
 62                        else dpd_loss(logits, y[ix], float(cfg["alpha"])))
 63                opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
 64        with torch.no_grad():
 65            pred = net(d["xte"].to(device)).argmax(1).cpu()
 66            metric = float((pred != d["yte"]).float().mean())
 67        if return_model:
 68            return metric, net, d, device
 69        del net
 70        if torch.cuda.is_available(): torch.cuda.empty_cache()
 71        return metric
 72    except RuntimeError:
 73        # Explicit CPU fallback for a shared/fragmented GPU slot.
 74        seed_all(seed)
 75        d = corrupted_ds(seed)
 76        net = make_model("cnn_small", d["input_shape"], d["out_dim"]).to("cpu")
 77        opt = torch.optim.Adam(net.parameters(), lr=float(cfg["lr"]))
 78        gen = torch.Generator().manual_seed(seed + 12345)
 79        for _ in range(EPOCHS):
 80            for start in range(0, NTR, 128):
 81                ix = torch.randperm(NTR, generator=gen)[start:start+128]
 82                z = net(d["xtr"][ix]); loss = (F.cross_entropy(z, d["ytr"][ix]) if cfg["kind"] == "ce" else dpd_loss(z, d["ytr"][ix], float(cfg["alpha"])))
 83                opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
 84        with torch.no_grad(): metric = float((net(d["xte"]).argmax(1) != d["yte"]).float().mean())
 85        if return_model: return metric, net, d, torch.device("cpu")
 86        return metric
 87
 88
 89def base_factory(cfg):
 90    return lambda seed: run({"kind": "ce", "lr": cfg["lr"]}, seed)
 91
 92
 93def idea_factory(cfg):
 94    return lambda seed: run({"kind": "dpd", "lr": cfg["lr"], "alpha": cfg["alpha"]}, seed)
 95
 96
 97def mechanism_signature():
 98    # Measure the proposed score suppression on outputs of a trained NN.
 99    cfg = {"kind": "dpd", "lr": 3e-3, "alpha": 0.5}
100    _, net, d, device = run(cfg, 0, return_model=True)
101    net.eval(); x = d["xtr"].to(device); y = d["ytr"].to(device)
102    mask = d["corrupt_mask"].to(device)
103    idx = torch.where(mask)[0][:min(48, int(mask.sum()))]
104    z = net(x[idx]).detach().requires_grad_(True)
105    yy = y[idx]
106    p = F.softmax(z, -1); q = p.gather(1, yy[:, None]).squeeze(1).clamp_min(1e-12)
107    g_ce = torch.autograd.grad((-q.log()).sum(), z, retain_graph=True)[0]
108    a = cfg["alpha"]
109    g_obs = torch.autograd.grad((-(1+1/a)*q.pow(a)).sum(), z)[0]
110    observed = (g_obs.norm(dim=1) / g_ce.norm(dim=1).clamp_min(1e-12)).detach().cpu().numpy()
111    predicted = ((1+a)*q.pow(a)).detach().cpu().numpy()
112    rel = np.abs(observed-predicted) / np.maximum(predicted, 1e-12)
113    return {"prediction": "observed-label score gradient ratio equals (1+alpha)*q^alpha", "alpha": a,
114            "n_corrupted": int(len(idx)), "mean_predicted_ratio": float(predicted.mean()),
115            "mean_observed_ratio": float(observed.mean()), "max_relative_error": float(rel.max()),
116            "corrupted_mean_q": float(q.detach().mean()),
117            "confirmed": bool(np.isfinite(rel).all() and float(rel.max()) < 1e-4)}
118
119
120def main():
121    base_grid = [{"lr": lr} for lr in LRS]
122    base = sweep_baseline(base_factory, base_grid, seeds=SWEEP_SEEDS)
123    trials = []
124    idea_lr = float(base["best_cfg"]["lr"])
125    for alpha in ALPHAS:
126        cfg = {"lr": idea_lr, "alpha": alpha}
127        trials.append({"cfg": cfg, "result": evaluate(idea_factory(cfg), SEEDS)})
128    best = min(trials, key=lambda r: r["result"]["mean"])
129    extra = {"track_choice": "vision: categorical class probability output matches exact DPD integral; label corruption tests robustness", "noise_rate": NOISE,
130             "idea_sweep": trials, "mechanism_signature": mechanism_signature()}
131    rep = make_report("vision", "cnn_small", base, best["result"], extra)
132    with open("bench_report.json", "w") as f: json.dump(rep, f, indent=2)
133    print(json.dumps(rep, indent=2))
134
135if __name__ == "__main__": main()