import os, sys, json, random from pathlib import Path import numpy as np import torch sys.path.insert(0, '/home/maxwelhelp/all/math2nn') import bench SEEDS = tuple(range(8)) SWEEP_SEEDS = tuple(range(4)) TRACK, MODEL = 'dynamics', 'rnn_small' EPOCHS, N, K = 18, 400, 200 GRID = [{'lr': 0.0015}, {'lr': 0.003}, {'lr': 0.006}] def seed_all(s): random.seed(s); np.random.seed(s); torch.manual_seed(s) if torch.cuda.is_available(): try: torch.cuda.manual_seed_all(s) except Exception: pass def logdet_info(A, sigma=1.0): sign, value = np.linalg.slogdet(np.eye(A.shape[0]) + sigma * A) return float(value) if sign > 0 else -1e9 def branch_matrix(x): # The dynamics track stores 8 time steps x 3 state/action coordinates. z = np.asarray(x, dtype=np.float64).reshape(8, 3) z = (z - z.mean(axis=0)) / (z.std(axis=0) + 1e-5) return z def valid_candidates(x, y): # Conflict projection: remove branches with implausible action/outcome # relative to the trusted-data population (robust MAD thresholds). a = np.asarray(x).reshape(-1, 8, 3)[:, :, 2].mean(axis=1) yy = np.asarray(y).reshape(-1) def robust_ok(v): med = np.median(v); mad = np.median(np.abs(v-med)) + 1e-6 return np.abs(v-med) <= 3.5 * 1.4826 * mad return np.where(robust_ok(a) & robust_ok(yy))[0] def gated_greedy(x, y, k=K): mats = [branch_matrix(v) for v in x] # Gate coordinates by control sensitivity: action and angle/state are # relevant; the first coordinate is downweighted, not discarded. gate = np.array([0.45, 1.0, 1.25]) inc = [((m * gate).T @ (m * gate)) / 8.0 for m in mats] valid = list(valid_candidates(x, y)) if len(valid) <= k: return np.asarray(valid, dtype=np.int64) chosen, A = [], np.zeros((3, 3)) remaining = set(valid) for _ in range(k): base = logdet_info(A) best, bg = None, -np.inf for i in remaining: gain = logdet_info(A + inc[i]) - base if gain > bg: best, bg = i, gain chosen.append(best); remaining.remove(best); A += inc[best] return np.asarray(chosen, dtype=np.int64) def random_subset(x, y, seed, k=K): valid = valid_candidates(x, y) rng = np.random.default_rng(seed + 991) if len(valid) <= k: return valid return np.sort(rng.choice(valid, k, replace=False)) def train_one(seed, cfg, method, return_model=False): seed_all(seed) ds = bench.get_dataset(TRACK, seed, n_train=N, n_test=100) idx = gated_greedy(ds['xtr'].numpy(), ds['ytr'].numpy()) if method == 'idea' else random_subset(ds['xtr'].numpy(), ds['ytr'].numpy(), seed) sub = dict(ds) sub['xtr'], sub['ytr'] = ds['xtr'][idx], ds['ytr'][idx] model = bench.make_model(MODEL, ds['input_shape'], ds['out_dim']) out = bench.train_model(model, sub, epochs=EPOCHS, lr=float(cfg['lr']), batch=128) if return_model: return out[0], float(out[1]), ds, idx return float(out[1]) def evaluate_method(method, cfg, seeds=SEEDS): vals = [train_one(s, cfg, method) for s in seeds] return {'mean': float(np.mean(vals)), 'std': float(np.std(vals)), 'per_seed': [float(v) for v in vals], 'n': len(vals)} def signature(): # Signature is measured from trained networks, not from the toy formula: # compare predicted gated information with observed GRU hidden-feature # information on the same trained systems for seed 0. rows = {} for method in ('baseline', 'idea'): model, metric, ds, idx = train_one(0, {'lr': .003}, method, True) model.eval() with torch.no_grad(): try: device = next(model.parameters()).device xin = ds['xtr'].reshape(-1, 8, 3).to(device) h = model.rnn(xin)[0][:, -1, :].detach().cpu().numpy() except Exception: model = model.to('cpu') h = model.rnn(ds['xtr'].reshape(-1, 8, 3))[0][:, -1, :].detach().numpy() # predicted information uses trajectory features; observed uses the # learned GRU representation (a behavior of the trained model). mats = [branch_matrix(v) for v in ds['xtr'].numpy()] gate = np.array([.45, 1., 1.25]) pred = [logdet_info(((m*gate).T@(m*gate))/8.) for m in mats] obs = [logdet_info(np.outer(h[i], h[i])) for i in range(len(h))] ridx = random_subset(ds['xtr'].numpy(), ds['ytr'].numpy(), 0) rows[method] = {'predicted_F': float(logdet_info(sum(((m*gate).T@(m*gate))/8. for m in [mats[i] for i in idx]))), 'predicted_random_F': float(logdet_info(sum(((m*gate).T@(m*gate))/8. for m in [mats[i] for i in ridx]))), 'observed_hidden_mean': float(np.mean([obs[i] for i in idx])), 'observed_random_hidden_mean': float(np.mean([obs[i] for i in ridx])), 'test_metric': metric} predicted_gain = rows['idea']['predicted_F'] > rows['baseline']['predicted_random_F'] observed_gain = rows['idea']['observed_hidden_mean'] > rows['baseline']['observed_random_hidden_mean'] return {'baseline': rows['baseline'], 'idea': rows['idea'], 'predicted_gain_observed': bool(predicted_gain and observed_gain), 'confirmed': bool(predicted_gain and observed_gain)} def main(): # Equal-budget baseline sweep on the same union of learning rates. base = bench.sweep_baseline(lambda cfg: (lambda s: train_one(s, cfg, 'baseline')), GRID, seeds=SWEEP_SEEDS) idea_runs = [evaluate_method('idea', cfg, SEEDS) for cfg in GRID] best_i = int(np.argmin([r['mean'] for r in idea_runs])) idea = idea_runs[best_i] sig = signature() report = bench.make_report(TRACK, MODEL, base, idea, {'mechanism_signature': sig, 'idea_sweep': [{'cfg': c, 'mean': r['mean']} for c, r in zip(GRID, idea_runs)], 'protocol': {'epochs': EPOCHS, 'subset_size': K, 'selection': 'gated greedy logdet + robust conflict projection'}}) Path('bench_report.json').write_text(json.dumps(report, indent=2)) print(json.dumps(report, indent=2)) if __name__ == '__main__': main()