Intrinsic Tangent-Projected Point-Cloud Layer / run_stage2.py

✓✓ Beats tuned baseline

Raw ⬇ ZIP
  1import json, random, sys
  2import numpy as np
  3import torch
  4from torch import nn
  5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  6from bench import train_model, sweep_baseline, evaluate, make_report
  7import tangent_surface_track as track
  8
  9SEEDS = tuple(range(8))
 10EPOCHS = 14
 11BATCH = 64
 12WEIGHT_DECAY = 1e-5
 13
 14
 15def seed_all(s):
 16    random.seed(s); np.random.seed(s); torch.manual_seed(s)
 17    if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
 18
 19
 20def project_input(x):
 21    p, v = x[..., :3], x[..., 4:]
 22    d = torch.cdist(p, p)
 23    k = min(8, p.shape[1]-1)
 24    idx = d.topk(k+1, largest=False).indices[..., 1:]
 25    src = p.unsqueeze(1).expand(-1, p.shape[1], -1, -1)
 26    nb = torch.gather(src, 2, idx.unsqueeze(-1).expand(-1, -1, -1, 3))
 27    c = nb - p.unsqueeze(2)
 28    cov = torch.einsum('bmki,bmkj->bmij', c, c) / float(k)
 29    _, e = torch.linalg.eigh(cov)
 30    n = e[..., 0]
 31    n = n / (n.norm(dim=-1, keepdim=True) + 1e-8)
 32    eye = torch.eye(3, dtype=x.dtype, device=x.device).view(1,1,3,3)
 33    P = eye - n.unsqueeze(-1) * n.unsqueeze(-2)
 34    vp = torch.einsum('bmij,bmj->bmi', P, v)
 35    r = p - p[:, :1]
 36    rq = torch.einsum('bij,bmj->bmi', P[:, 0], r)
 37    out = torch.cat([rq, x[..., 3:4], vp], dim=-1)
 38    return out.reshape(x.shape[0], -1), n, vp
 39
 40
 41class SharedNet(nn.Module):
 42    def __init__(self, intrinsic=False, width=64):
 43        super().__init__(); self.intrinsic = intrinsic
 44        dim = 20 * (7 if not intrinsic else 7)
 45        self.net = nn.Sequential(nn.Linear(dim, width), nn.ReLU(),
 46                                 nn.Linear(width, width), nn.ReLU(),
 47                                 nn.Linear(width, 1))
 48
 49    def forward(self, x):
 50        if self.intrinsic:
 51            z, n, vp = project_input(x)
 52        else:
 53            z = x.reshape(x.shape[0], -1); n = None; vp = None
 54        return self.net(z), n, vp
 55
 56
 57def run_one(seed, intrinsic, lr):
 58    seed_all(seed)
 59    raw = track.get_dataset(seed, 400, 100)
 60    ds = {k: torch.as_tensor(raw[k], dtype=torch.float32) for k in ('xtr','ytr','xte','yte')}
 61    ds.update(task='regression', metric='mse')
 62    model = SharedNet(intrinsic=intrinsic)
 63    class Wrapper(nn.Module):
 64        def __init__(self, m): super().__init__(); self.m=m
 65        def forward(self, x): return self.m(x)[0]
 66    net, metric, hist = train_model(Wrapper(model), ds, epochs=EPOCHS, lr=lr,
 67                                    batch=BATCH, weight_decay=WEIGHT_DECAY, log=lambda *_: None)
 68    if net is None: return float('nan'), None
 69    return float(metric), model
 70
 71
 72def factory(intrinsic, lr):
 73    return lambda seed: run_one(seed, intrinsic, lr)[0]
 74
 75
 76def main():
 77    # Both methods are evaluated at every learning rate in the shared union.
 78    grid = [{'lr': 1e-3}, {'lr': 3e-3}, {'lr': 1e-2}]
 79    base = sweep_baseline(lambda c: factory(False, c['lr']), grid, seeds=(0,1,2,3))
 80    # Ensure baseline is evaluated on all candidate settings as required.
 81    baseline_candidates = []
 82    for c in grid:
 83        baseline_candidates.append({'cfg': c, 'full': evaluate(factory(False, c['lr']), SEEDS)})
 84    best_cfg = min(baseline_candidates, key=lambda z: z['full']['mean'])['cfg']
 85    base['union_full'] = baseline_candidates
 86    idea_candidates = []
 87    for c in grid:
 88        idea_candidates.append({'cfg': c, 'full': evaluate(factory(True, c['lr']), SEEDS)})
 89    idea = min(idea_candidates, key=lambda z: z['full']['mean'])['full']
 90    best_idea_cfg = min(idea_candidates, key=lambda z: z['full']['mean'])['cfg']
 91
 92    # Re-test the proposed mechanism on independently trained benchmark models.
 93    raw_leak, proj_leak, rot_change = [], [], []
 94    for s in SEEDS:
 95        metric, model = run_one(s, True, best_idea_cfg['lr'])
 96        if model is None: continue
 97        raw = track.get_dataset(s+9000, 400, 20)
 98        x = torch.as_tensor(raw['xte'], dtype=torch.float32)
 99        device = next(model.parameters()).device
100        x = x.to(device)
101        with torch.no_grad():
102            _, n, vp = model(x)
103            rawv = x[...,4:]
104            raw_leak.append(float(torch.abs((n*rawv).sum(-1)).mean()))
105            proj_leak.append(float(torch.abs((n*vp).sum(-1)).max()))
106            R = torch.tensor([[0.,-1.,0.],[1.,0.,0.],[0.,0.,1.]], device=device)
107            xr=x.clone(); xr[...,:3]=x[...,:3]@R.T; xr[...,4:]=x[...,4:]@R.T
108            pred1=model(x)[0]; pred2=model(xr)[0]
109            rot_change.append(float(torch.abs(pred1-pred2).mean()/(torch.abs(pred1).mean()+1e-8)))
110    signature = {'raw_mean_normal_component': float(np.mean(raw_leak)),
111                 'projected_max_normal_component': float(np.max(proj_leak)),
112                 'rotation_relative_prediction_change': float(np.mean(rot_change)),
113                 'predicted_projected_leakage': '<1e-5',
114                 'confirmed': bool(np.max(proj_leak) < 1e-5)}
115    report = make_report('tangent_surface_vector_regression', 'shared_mlp',
116                         {'best_cfg': best_cfg, 'sweep': base['sweep'],
117                          'union_full': baseline_candidates, 'full': base['full']},
118                         idea, {'mechanism_signature': signature,
119                                'custom_track': {'name': track.META['name'],
120                                                 'file': 'tangent_surface_track.py',
121                                                 'domain': track.META['domain']},
122                                'idea_sweep': idea_candidates,
123                                'best_idea_cfg': best_idea_cfg})
124    print(json.dumps(report, indent=2))
125
126if __name__ == '__main__': main()