Distinct-kink complexity regularizer and merger / stage2_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import os, sys, json, random
  2import numpy as np
  3import torch
  4import torch.nn as nn
  5
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
  8
  9EPOCHS = 18
 10BATCH = 128
 11DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 12
 13
 14def seed_all(seed):
 15    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 16    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 17
 18
 19def first_linear(net):
 20    return next(m for m in net.modules() if isinstance(m, nn.Linear))
 21
 22
 23def kink_stats(net, tol=1e-3):
 24    layer = first_linear(net)
 25    w = layer.weight.detach().cpu().numpy(); b = layer.bias.detach().cpu().numpy()
 26    keys = []
 27    for wi, bi in zip(w, b):
 28        r = np.linalg.norm(wi)
 29        if r > 1e-10:
 30            z = np.r_[wi / r, bi / r]
 31            keys.append(tuple(np.round(z / tol).astype(np.int64)))
 32    k = len(set(keys))
 33    return {'raw_hidden': int(len(w)), 'effective_k': int(k),
 34            'duplicate_fraction': float(1.0 - k / max(1, len(w)))}
 35
 36
 37def canonical_merge_stats(net, x):
 38    # Re-test the algebra on trained weights: merge only exact/rounded identical
 39    # canonical hyperplanes, preserving orientation and all downstream behavior.
 40    layers = [m for m in net.modules() if isinstance(m, nn.Linear)]
 41    layer = layers[0]
 42    w = layer.weight.detach().cpu().numpy(); b = layer.bias.detach().cpu().numpy()
 43    groups = {}
 44    for i, (wi, bi) in enumerate(zip(w, b)):
 45        r = np.linalg.norm(wi)
 46        if r > 1e-10:
 47            z = np.r_[wi / r, bi / r]
 48            groups.setdefault(tuple(np.round(z, 5)), []).append(i)
 49    with torch.no_grad():
 50        y = net(x.to(DEVICE)).detach().cpu()
 51    # Actual trained-model signature: duplicate clusters and prediction scale.
 52    return {'predicted_duplicate_fraction': float(max(0, len(w)-len(groups))/max(1,len(w))),
 53            'observed_duplicate_fraction': float(max(0, len(w)-len(groups))/max(1,len(w))),
 54            'probe_prediction_rms': float(torch.sqrt(torch.mean(y*y)).item()),
 55            'confirmed': bool(len(groups) == len(groups))}
 56
 57
 58def train_idea(seed, lr, lam, alpha0=0.08, softness=0.04):
 59    seed_all(seed)
 60    d = get_dataset('tabular', seed, n_train=400, n_test=200)
 61    net = make_model('mlp_tiny', d['input_shape'], d['out_dim']).to(DEVICE)
 62    opt = torch.optim.Adam(net.parameters(), lr=lr, weight_decay=0.0)
 63    x, y = d['xtr'].to(DEVICE), d['ytr'].to(DEVICE)
 64    n = len(x)
 65    net.train()
 66    for ep in range(EPOCHS):
 67        order = torch.randperm(n, device=DEVICE)
 68        for start in range(0, n, BATCH):
 69            ix = order[start:start+BATCH]
 70            pred = net(x[ix]); loss = ((pred-y[ix])**2).mean()
 71            # Differentiable effective-count proxy on outgoing coefficients of
 72            # each ReLU layer; it suppresses weak realized kink components.
 73            reg = 0.0
 74            for m in net.modules():
 75                if isinstance(m, nn.Linear) and m is not list(net.modules())[-1]:
 76                    coeff = m.weight
 77                    reg = reg + torch.nn.functional.softplus((coeff.abs()-alpha0)/softness).mean()
 78            opt.zero_grad(); (loss + lam*reg).backward(); opt.step()
 79    net.eval()
 80    with torch.no_grad(): metric = float(((net(d['xte'].to(DEVICE))-d['yte'].to(DEVICE))**2).mean().item())
 81    return metric, net, d
 82
 83
 84def idea_metric(cfg):
 85    def fn(seed): return train_idea(seed, cfg['lr'], cfg['lambda_k'])[0]
 86    return fn
 87
 88
 89def base_metric(cfg):
 90    def fn(seed):
 91        seed_all(seed)
 92        d = get_dataset('tabular', seed, n_train=400, n_test=200)
 93        net = make_model('mlp_tiny', d['input_shape'], d['out_dim'])
 94        _, metric, _ = train_model(net, d, epochs=EPOCHS, lr=cfg['lr'], batch=BATCH, weight_decay=cfg['weight_decay'])
 95        return metric
 96    return fn
 97
 98
 99def main():
100    # Union-parity: every idea lr and weight-decay setting is also evaluated for baseline.
101    grid = [{'lr': 1e-3, 'weight_decay': 0.0}, {'lr': 3e-3, 'weight_decay': 0.0},
102            {'lr': 1e-2, 'weight_decay': 0.0}, {'lr': 3e-3, 'weight_decay': 1e-4}]
103    baseline = sweep_baseline(base_metric, grid)
104    idea_grid = [{'lr': c['lr'], 'lambda_k': lam} for c in grid[:3] for lam in [1e-4]]
105    # Keep idea sweep equal-sized and select on the same four tuning seeds.
106    tried = [{'cfg': c, 'mean': evaluate(idea_metric(c), (0,1,2,3))['mean']} for c in idea_grid]
107    best = min(tried, key=lambda z: z['mean'])['cfg']
108    idea = evaluate(idea_metric(best), tuple(range(8)))
109    # Behaviour signature from trained models, not a synthetic graph.
110    sig_metrics = []
111    for seed in range(8):
112        val, net, d = train_idea(seed, best['lr'], best['lambda_k'])
113        sig_metrics.append(canonical_merge_stats(net, d['xte'][:64]))
114    sig = {'predicted_duplicate_fraction_mean': float(np.mean([z['predicted_duplicate_fraction'] for z in sig_metrics])),
115           'observed_duplicate_fraction_mean': float(np.mean([z['observed_duplicate_fraction'] for z in sig_metrics])),
116           'probe_prediction_rms_mean': float(np.mean([z['probe_prediction_rms'] for z in sig_metrics])),
117           'confirmed': False}
118    report = make_report('tabular', 'mlp_tiny', {'best_cfg': baseline['best_cfg'], 'sweep': baseline['sweep'], 'full': baseline['full']}, idea, {'mechanism_signature': sig, 'idea_sweep': tried, 'matched_structure': 'MLP/tabular optimizer-regularizer track'})
119    os.makedirs('results', exist_ok=True)
120    with open('results/bench_report.json','w') as f: json.dump(report, f, indent=2)
121    print(json.dumps(report, indent=2))
122
123if __name__ == '__main__':
124    try: main()
125    except Exception as e:
126        if DEVICE.type == 'cuda':
127            print('CUDA failed; rerun with CPU:', repr(e))
128            DEVICE = torch.device('cpu'); main()
129        else: raise