Teleporting Simplicial Diffusion Layer / registered_bench.py

Failed on benchmark

Raw ⬇ ZIP
 1import sys, json
 2import numpy as np
 3import torch
 4import torch.nn as nn
 5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
 6import bench
 7from bench import train_model, evaluate, sweep_baseline, make_report
 8
 9TRACK = 'geometric_graph_diffusion'
10
11def graph_operator(x):
12    # x[...,2:4] are the observed node coordinates in the registered track.
13    xy = x[..., 2:4]
14    d2 = ((xy[:, :, None, :] - xy[:, None, :, :]) ** 2).sum(-1)
15    b, n, _ = d2.shape
16    adj = torch.zeros_like(d2)
17    # Five nearest neighbors, symmetrized, matching the registered generator.
18    ind = torch.argsort(d2, dim=-1)[:, :, 1:6]
19    adj.scatter_(2, ind, 1.0)
20    adj = torch.maximum(adj, adj.transpose(1, 2))
21    w = adj * torch.clamp(d2, min=1e-5)
22    p = w / torch.clamp(w.sum(-1, keepdim=True), min=1e-8)
23    return 0.55 * torch.eye(n, device=x.device).unsqueeze(0) + 0.45 * p
24
25class GraphDiffusionNet(nn.Module):
26    # The two systems share every learnable parameter and differ only in alpha.
27    def __init__(self, alpha):
28        super().__init__()
29        self.alpha = float(alpha)
30        self.fc1 = nn.Linear(5, 32)
31        self.fc2 = nn.Linear(32, 1)
32    def forward(self, x):
33        q = graph_operator(x)
34        if self.alpha:
35            n = x.shape[1]
36            q = (1.0-self.alpha)*q + self.alpha*torch.ones_like(q)/n
37        h = torch.relu(self.fc1(x))
38        h = torch.bmm(q, h)
39        z = self.fc2(h).squeeze(-1)
40        # Root marker makes this a scalar graph readout, as required by target.
41        root = x[..., 4]
42        return (z * root).sum(1, keepdim=True)
43
44def run(cfg, seed, return_model=False):
45    torch.manual_seed(9000 + int(seed)); np.random.seed(9000 + int(seed))
46    ds = bench.get_dataset(TRACK, int(seed), n_train=400, n_test=160)
47    net = GraphDiffusionNet(cfg['alpha'])
48    net, metric, history = train_model(net, ds, epochs=cfg['epochs'], lr=cfg['lr'],
49                                       batch=128, weight_decay=cfg['wd'], log=lambda *_: None)
50    return (float(metric), net, ds) if return_model else float(metric)
51
52def factory(cfg):
53    return lambda seed: run(cfg, seed)
54
55def main():
56    # Shared union of all learning-rate/regularization settings on both sides.
57    common = [(lr, wd) for lr in (0.003, 0.01, 0.03) for wd in (0.0, 0.0001)]
58    baseline_grid = [{'alpha': 0.0, 'lr': lr, 'wd': wd, 'epochs': 20} for lr,wd in common]
59    baseline = sweep_baseline(factory, baseline_grid, seeds=(0,1,2,3))
60    best = baseline['best_cfg']
61    idea_grid = []
62    for alpha in (0.03, 0.1, 0.3):
63        c = dict(best); c['alpha'] = alpha; idea_grid.append(c)
64    idea_candidates = [{'cfg': c, 'result': evaluate(factory(c))} for c in idea_grid]
65    chosen = min(idea_candidates, key=lambda z: z['result']['mean'])
66    idea = chosen['result']
67
68    # Behavioural signature from trained systems, not an analytical toy graph.
69    rows=[]
70    for alpha in (0.0, 0.03, 0.1, 0.3):
71        cfg=dict(best); cfg['alpha']=alpha
72        _, net, ds = run(cfg, 0, return_model=True)
73        net.eval(); x=ds['xte'].to(next(net.parameters()).device)
74        with torch.no_grad():
75            q=graph_operator(x)
76            h=torch.relu(net.fc1(x)); hc=h-h.mean(1,keepdim=True)
77            before=hc.norm().item()
78            n=x.shape[1]; qt=(1-alpha)*q + alpha*torch.ones_like(q)/n
79            after=torch.bmm(qt,hc).norm().item()
80        rows.append({'alpha':alpha,'observed_mean_centered_norm_ratio':after/max(before,1e-12), 'predicted':1-alpha})
81    err=max(abs(r['observed_mean_centered_norm_ratio']-r['predicted']) for r in rows)
82    extra={'prediction':'uniform teleportation contracts mean-zero feature modes approximately by 1-alpha',
83           'trained_model_rows':rows,'max_abs_error':float(err),'confirmed':bool(err < 0.10),
84           'idea_sweep':idea_candidates,
85           'track_match':'registered geometric_graph_diffusion: node-field regression on geometric kNN graphs'}
86    report=make_report(TRACK,'graph_diffusion_net',baseline,idea,extra)
87    report['idea_candidates']=idea_candidates
88    with open('bench_report.json','w') as f: json.dump(report,f,indent=2)
89    print(json.dumps(report,indent=2))
90
91if __name__=='__main__': main()