Sound active-neuron pruning for SDP verification / mini_experiment.py
Mechanism confirmed, baseline not beaten
1import json
2import time
3import numpy as np
4from pruning_mvp import make_net, prune_report, output_interval, forward
5
6
7def run(seed=123, n_cases=32):
8 rng = np.random.default_rng(seed)
9 rows = []
10 for case in range(n_cases):
11 W, b = make_net(seed=1000 + case)
12 center = rng.uniform(-0.5, 0.5, 2)
13 eps = 0.30
14 xlo, xhi = center - eps, center + eps
15 hiddenW, hiddenb = W[:-1], b[:-1]
16 base_vars = 2 * sum(len(v) for v in hiddenb)
17 t0 = time.perf_counter()
18 base_lo, base_hi = output_interval(W, b, xlo, xhi)
19 base_time = time.perf_counter() - t0
20 row = {'case': case, 'baseline_variables': base_vars,
21 'baseline_interval_seconds': base_time}
22 for tau, name in [(0.0, 'exact_sign'), (1e-6, 'tau_1e-6'),
23 (0.1, 'tau_0.1'), (0.3, 'tau_0.3'), (1.0, 'tau_1.0')]:
24 t0 = time.perf_counter()
25 _, reports = prune_report(hiddenW, hiddenb, xlo, xhi, tau)
26 idea_time = time.perf_counter() - t0
27 retained = sum(r.retained for r in reports)
28 fixed = sum(r.active + r.inactive for r in reports)
29 row[name] = {'variables': 2 * retained, 'fixed_sign': fixed,
30 'contribution_pruned': sum(r.pruned_contribution for r in reports),
31 'seconds': idea_time,
32 # Exact sign mode must preserve the interval exactly.
33 'certificate_equal': bool(tau == 0.0 and np.array_equal(
34 base_lo, output_interval(W, b, xlo, xhi)[0]) and
35 np.array_equal(base_hi, output_interval(W, b, xlo, xhi)[1]))}
36 rows.append(row)
37
38 names = ['exact_sign', 'tau_1e-6', 'tau_0.1', 'tau_0.3', 'tau_1.0']
39 summary = {'cases': n_cases, 'baseline_variables_mean': float(np.mean([r['baseline_variables'] for r in rows])),
40 'baseline_seconds_mean': float(np.mean([r['baseline_interval_seconds'] for r in rows]))}
41 for name in names:
42 vals = [r[name] for r in rows]
43 summary[name] = {
44 'variables_mean': float(np.mean([v['variables'] for v in vals])),
45 'reduction_fraction_mean': float(np.mean([(r['baseline_variables'] - v['variables']) / r['baseline_variables'] for r, v in zip(rows, vals)])),
46 'fixed_sign_mean': float(np.mean([v['fixed_sign'] for v in vals])),
47 'contribution_pruned_mean': float(np.mean([v['contribution_pruned'] for v in vals])),
48 'certificate_equal_all': bool(all(v['certificate_equal'] for v in vals)) if name == 'exact_sign' else None,
49 }
50 # Independent point checks on the first case: output intervals remain sound.
51 W, b = make_net(seed=1000)
52 center = rows[0]['case'] * 0.0 + np.array([0.0, 0.0])
53 xlo, xhi = center - .3, center + .3
54 lo, hi = output_interval(W, b, xlo, xhi)
55 points = rng.uniform(xlo, xhi, (5000, 2))
56 violations = sum(np.any((forward(W, b, p) < lo - 1e-10) | (forward(W, b, p) > hi + 1e-10)) for p in points)
57 result = {'summary': summary, 'independent_soundness_points': 5000,
58 'independent_soundness_violations': int(violations)}
59 with open('results.json', 'w') as f:
60 json.dump(result, f, indent=2)
61 print(json.dumps(result, indent=2))
62
63
64if __name__ == '__main__':
65 run()