Multiplicity-balanced symmetric interaction layer / bench_run.py
Mechanism confirmed, baseline not beaten
1import sys, os, math, json, time
2import numpy as np
3import torch
4from torch import nn
5sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
6from bench import train_model, evaluate, sweep_baseline, make_report
7from custom_symmetric_poly import get_dataset, _compositions
8
9SEEDS = tuple(range(8))
10D, M = 8, 4
11ALPHAS = _compositions(D, M)
12MULT = np.array([math.factorial(M) // np.prod([math.factorial(a) for a in al]) for al in ALPHAS], dtype=np.float32)
13TUPLES = []
14for a in ALPHAS:
15 for j, count in enumerate(a):
16 TUPLES.extend([j] * int(count))
17# expand in a deterministic way: each orbit has N ordered tuples
18ORDERED = []
19from itertools import permutations
20for a in ALPHAS:
21 items = []
22 for j, count in enumerate(a): items.extend([j] * int(count))
23 ORDERED.extend(sorted(set(permutations(items))))
24
25class InteractionNet(nn.Module):
26 def __init__(self, kind):
27 super().__init__()
28 self.kind = kind
29 self.theta = nn.Parameter(torch.empty(len(ALPHAS) if kind == 'idea' else len(ORDERED), 1))
30 nn.init.normal_(self.theta, 0, 0.03)
31 self.register_buffer('sqrt_mult', torch.tensor(np.sqrt(MULT), dtype=torch.float32))
32 self.register_buffer('ordered_idx', torch.tensor(ORDERED, dtype=torch.long))
33 def features(self, x):
34 if self.kind == 'idea':
35 vals = []
36 for a, n in zip(ALPHAS, self.sqrt_mult):
37 v = torch.ones(x.shape[0], device=x.device, dtype=x.dtype)
38 for j, p in enumerate(a):
39 if p: v = v * x[:, j].pow(p)
40 vals.append(v * n)
41 return torch.stack(vals, 1)
42 # Dense ordered tensor contraction: one independent coefficient per ordered tuple.
43 return torch.prod(x[:, self.ordered_idx], dim=2)
44 def forward(self, x):
45 return self.features(x) @ self.theta
46
47def make_train_fn(kind, cfg):
48 def run(seed):
49 torch.manual_seed(1000 + int(seed))
50 np.random.seed(2000 + int(seed))
51 d = get_dataset(int(seed), 400, 400)
52 ds = {k: (torch.from_numpy(v) if isinstance(v, np.ndarray) else v) for k, v in d.items()}
53 net = InteractionNet(kind)
54 _, metric, _ = train_model(net, ds, epochs=int(cfg['epochs']), lr=float(cfg['lr']),
55 batch=128, weight_decay=float(cfg.get('weight_decay', 0.0)), log=lambda *_: None)
56 return float(metric)
57 return run
58
59def mechanism_signature(seed=0):
60 d = get_dataset(seed, 400, 400)
61 x = torch.from_numpy(d['xte'])
62 net = InteractionNet('idea').eval()
63 with torch.no_grad():
64 f = net.features(x)
65 variances = f.var(0).numpy()
66 # For standard-normal inputs, the weighted/unweighted second-moment ratio is N.
67 # Measure observed ratio from the trained-system feature map, not an algebra-only toy.
68 pred = MULT.astype(float)
69 # Divide by the empirical unweighted monomial variance measured on the same inputs.
70 unweighted = []
71 for a in ALPHAS:
72 v = torch.ones(x.shape[0])
73 for j, p in enumerate(a):
74 if p: v = v * x[:, j].pow(p)
75 unweighted.append(float(v.var()))
76 observed = variances / np.maximum(np.asarray(unweighted), 1e-12)
77 rel = np.abs(observed - pred) / np.maximum(pred, 1.0)
78 return {'prediction': 'weighted feature variance ratio equals multinomial multiplicity N_alpha',
79 'predicted_mean_ratio': float(pred.mean()), 'observed_mean_ratio': float(observed.mean()),
80 'median_relative_error': float(np.median(rel)), 'max_relative_error': float(rel.max()),
81 'n_feature_supports': len(ALPHAS), 'confirmed': bool(np.median(rel) < 0.15)}
82
83def main():
84 # Equal-budget union: every idea learning rate is also evaluated for baseline.
85 grid = [{'lr': 1e-3, 'epochs': 20}, {'lr': 3e-3, 'epochs': 20}, {'lr': 1e-2, 'epochs': 20}]
86 t0 = time.time()
87 base = sweep_baseline(lambda cfg: make_train_fn('baseline', cfg), grid, seeds=(0,1,2,3))
88 idea_runs = []
89 for cfg in grid:
90 r = evaluate(make_train_fn('idea', cfg), seeds=SEEDS)
91 idea_runs.append({'cfg': cfg, 'result': r})
92 best_idea = min(idea_runs, key=lambda z: z['result']['mean'])
93 report = make_report('custom_symmetric_polynomial_regression', 'interaction_layer', base,
94 best_idea['result'], extra=mechanism_signature(0))
95 report['idea_sweep'] = idea_runs
96 report['custom_track'] = {'name': 'symmetric_polynomial_regression', 'file': 'custom_symmetric_poly.py',
97 'domain': 'symmetric_interactions'}
98 report['runtime_sec'] = time.time() - t0
99 with open('bench_report.json', 'w') as f: json.dump(report, f, indent=2)
100 print(json.dumps(report, indent=2))
101if __name__ == '__main__': main()