Doubly-Stochastic Hyper-Residual Blocks / stage2_bench.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import sys, json, math, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5import torch.nn as nn
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
  8
  9SEED0 = 2230
 10EPOCHS = 12
 11NTR, NTE = 800, 300
 12BATCH = 128
 13
 14
 15def sinkhorn(logits, tau=1.0, steps=10):
 16    # Stable log-domain alternating normalization; output is nonnegative DS.
 17    z = logits / tau
 18    for _ in range(steps):
 19        z = z - torch.logsumexp(z, dim=-1, keepdim=True)
 20        z = z - torch.logsumexp(z, dim=-2, keepdim=True)
 21    return torch.exp(z)
 22
 23
 24class StreamTransformer(nn.Module):
 25    """S streams of d features, with shared transformer-style feature processing."""
 26    def __init__(self, input_dim, out_dim, S=4, ds=16, depth=2, tau=1.0, sink_steps=10, alpha=.1, constrained=True):
 27        super().__init__()
 28        self.S, self.ds, self.tau, self.sink_steps, self.constrained = S, ds, tau, sink_steps, constrained
 29        self.inp = nn.Linear(1, S * ds)
 30        self.pos = nn.Parameter(torch.zeros(1, 32, S, ds))
 31        self.g = nn.Parameter(torch.eye(S).unsqueeze(0).repeat(depth, 1, 1) * 2.0)
 32        self.blocks = nn.ModuleList()
 33        for _ in range(depth):
 34            self.blocks.append(nn.ModuleDict({
 35                'norm': nn.LayerNorm(ds),
 36                'ff': nn.Sequential(nn.Linear(ds, 64), nn.GELU(), nn.Linear(64, ds))
 37            }))
 38        self.alpha = nn.Parameter(torch.tensor(float(alpha)))
 39        self.out = nn.Linear(32 * S * ds, out_dim)
 40
 41    def forward(self, x):
 42        X = self.inp(x.unsqueeze(-1)).view(x.shape[0], x.shape[1], self.S, self.ds)
 43        X = X + self.pos[:, :x.shape[1]]
 44        for i, block in enumerate(self.blocks):
 45            A = sinkhorn(self.g[i], self.tau, self.sink_steps) if self.constrained else self.g[i]
 46            Xm = torch.einsum('ij,btjd->btid', A, X)
 47            # Shared streamwise residual transformation, preserving stream routing.
 48            F = block['ff'](block['norm'](Xm))
 49            X = Xm + self.alpha * F
 50        return self.out(X.reshape(x.shape[0], -1))
 51
 52
 53def set_seed(s):
 54    random.seed(s); np.random.seed(s); torch.manual_seed(s)
 55    if torch.cuda.is_available():
 56        try: torch.cuda.manual_seed_all(s)
 57        except Exception: pass
 58
 59
 60def train_one(kind, seed, lr, tau=1.0, alpha=.1, return_model=False):
 61    set_seed(seed)
 62    d = get_dataset('sequence', seed, n_train=NTR, n_test=NTE)
 63    if kind == 'baseline':
 64        model = StreamTransformer(d['input_shape'][0], d['out_dim'], tau=tau, alpha=alpha, constrained=False)
 65    else:
 66        model = StreamTransformer(d['input_shape'][0], d['out_dim'], tau=tau, alpha=alpha)
 67    net, metric, hist = train_model(model, d, epochs=EPOCHS, lr=lr, batch=BATCH, log=lambda *_: None)
 68    if net is None: return (float('inf'), None, d) if return_model else float('inf')
 69    if return_model: return float(metric), net, d
 70    return float(metric)
 71
 72
 73def signature(seed=0, lr=3e-3):
 74    metric, net, d = train_one('idea', seed, lr, return_model=True)
 75    device = next(net.parameters()).device
 76    with torch.no_grad():
 77        xt = d['xte'][:64].to(device)
 78        X = net.inp(xt.unsqueeze(-1)).view(xt.shape[0], 32, net.S, net.ds)
 79        ratios, re, ce = [], [], []
 80        for g in net.g:
 81            A = sinkhorn(g, net.tau, net.sink_steps)
 82            Xm = torch.einsum('ij,btjd->btid', A, X)
 83            ratios.append(float(torch.linalg.norm(Xm) / torch.linalg.norm(X)))
 84            re.append(float((A.sum(-1)-1).abs().max())); ce.append(float((A.sum(-2)-1).abs().max()))
 85    # Prediction tested on trained model: DS mixing is non-expansive (allow tiny FP tolerance).
 86    return {'trained_model': True, 'predicted': {'max_frobenius_ratio': '<=1', 'row_col_error': '0'},
 87            'observed': {'max_frobenius_ratio': max(ratios), 'mean_frobenius_ratio': float(np.mean(ratios)),
 88                         'max_row_error': max(re), 'max_col_error': max(ce)},
 89            'confirmed': bool(max(ratios) <= 1.0001 and max(re) < 1e-4 and max(ce) < 1e-4),
 90            'metric_on_signature_model': float(metric)}
 91
 92
 93def main():
 94    # Union parity: every idea learning rate is also evaluated by baseline.
 95    grid = [{'lr': 7e-4}, {'lr': 1e-3}, {'lr': 1.5e-3}, {'lr': 2e-3}, {'lr': 3e-3}]
 96    base = sweep_baseline(lambda c: lambda s: train_one('baseline', s, c['lr']), grid)
 97    best_lr = base['best_cfg']['lr']
 98    idea_grid = [1e-3, 1.5e-3, 2e-3]
 99    idea_runs = []
100    for lr in idea_grid:
101        r = evaluate(lambda s, lr=lr: train_one('idea', s, lr))
102        idea_runs.append({'lr': lr, 'result': r})
103    best = min(idea_runs, key=lambda z: z['result']['mean'])
104    rep = make_report('sequence', 'transformer_tiny', base, best['result'], {
105        'idea_lr_sweep': idea_runs, 'selected_lr': best['lr'], **signature(0, best['lr'])})
106    rep['custom_track'] = None
107    Path('bench_report.json').write_text(json.dumps(rep, indent=2))
108    print(json.dumps(rep, indent=2))
109
110if __name__ == '__main__': main()