import sys, json, itertools, random from pathlib import Path import numpy as np import torch import torch.nn as nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, sweep_baseline, evaluate, make_report SEED = 3020 EPOCHS = 12 BATCH = 128 SEEDS = tuple(range(8)) SWEEP_SEEDS = tuple(range(4)) LR_GRID = [0.0015, 0.003, 0.006] P_GRID = [0.30, 0.40, 0.50] # Ten Friedman coordinates are mapped to the ten edges of K5. EDGES = [(i, j) for i in range(5) for j in range(i + 1, 5)] def is_tree(c): parent = list(range(5)) def find(x): while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x for k in c: a, b = EDGES[k]; ra, rb = find(a), find(b) if ra == rb: return False parent[ra] = rb return len({find(i) for i in range(5)}) == 1 def forest_law(weights=None): w = np.ones(10) if weights is None else np.asarray(weights, float) masks = [] ws = [] for c in itertools.combinations(range(10), 4): if is_tree(c): m = np.zeros(10, dtype=np.float32); m[list(c)] = 1 masks.append(m); ws.append(float(np.prod(w[list(c)]))) ws = np.asarray(ws); return np.asarray(masks), ws / ws.sum() def math_check(): masks, p = forest_law() inc = p @ masks joint = np.einsum('s,si,sj->ij', p, masks, masks) cov = joint - np.outer(inc, inc) # Z(A) is the unsigned numerator of trees contained in A. z = {} for bits in range(1 << 10): z[bits] = float(sum(1 for m in masks if all((bits >> i) & 1 for i in np.flatnonzero(m)))) min_slack, violations = float('inf'), 0 for a in range(1 << 10): for b in range(1 << 10): slack = z[a] * z[b] - z[a | b] * z[a & b] min_slack = min(min_slack, slack) violations += int(slack < -1e-10) off = cov[np.triu_indices(10, 1)] return {'num_spanning_trees': int(len(masks)), 'multiaffine': bool(np.all(masks*masks == masks)), 'mean_inclusion': float(inc.mean()), 'max_pair_covariance': float(off.max()), 'min_pair_covariance': float(off.min()), 'negative_pairwise_dependence': bool(off.max() <= 1e-12), 'log_submodular_min_slack': float(min_slack), 'log_submodular_violations': int(violations)} def seed_all(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) def train_masked(kind, seed, lr, prob=0.4, collect=False): seed_all(seed) ds = get_dataset('tabular', seed, n_train=4000, n_test=1000) model = make_model('mlp_tiny', ds['input_shape'], ds['out_dim']) masks, fprob = forest_law() rng = np.random.default_rng(seed + 100003) device = 'cuda' if torch.cuda.is_available() else 'cpu' try: model = model.to(device) xtr, ytr = ds['xtr'].to(device), ds['ytr'].to(device) xte, yte = ds['xte'].to(device), ds['yte'].to(device) opt = torch.optim.Adam(model.parameters(), lr=lr) model.train() for ep in range(EPOCHS): order = torch.randperm(len(xtr), device=device) for st in range(0, len(xtr), BATCH): ix = order[st:st+BATCH]; xb, yb = xtr[ix], ytr[ix] if kind == 'forest': mm = masks[rng.choice(len(masks), len(ix), p=fprob)] scale = 0.4 else: mm = (rng.random((len(ix), 10)) < prob).astype(np.float32) scale = prob xb = xb * torch.as_tensor(mm, device=device) / scale loss = (model(xb) - yb).square().mean() opt.zero_grad(); loss.backward(); opt.step() model.eval() with torch.no_grad(): metric = (model(xte) - yte).square().mean().item() if collect: # Re-test the trained model under route masks: output variance is a # behavioral signature, not an analytic identity. xx = xte[:128]; preds = [] for _ in range(160): mm = torch.as_tensor(masks[rng.choice(len(masks), len(xx), p=fprob)], device=device) with torch.no_grad(): preds.append(model(xx * mm / 0.4).squeeze(1).cpu().numpy()) output_var = float(np.var(np.stack(preds), axis=0).mean()) return metric, model, {'output_mask_variance': output_var} return metric except Exception: if torch.cuda.is_available(): torch.cuda.empty_cache() torch.set_default_device('cpu') # One retry on CPU, with deterministic same configuration. seed_all(seed) ds = get_dataset('tabular', seed, n_train=4000, n_test=1000) model = make_model('mlp_tiny', ds['input_shape'], ds['out_dim']) opt = torch.optim.Adam(model.parameters(), lr=lr); masks, fprob = forest_law(); rng=np.random.default_rng(seed+100003) for ep in range(EPOCHS): order=torch.randperm(len(ds['xtr'])) for st in range(0,4000,BATCH): ix=order[st:st+BATCH]; mm=masks[rng.choice(len(masks),len(ix),p=fprob)] if kind=='forest' else (rng.random((len(ix),10))