Time-Delay Error Adaptive Optimizer / experiment.py

Mechanism failed

Raw ⬇ ZIP
 1import json, math, random
 2from pathlib import Path
 3import numpy as np
 4
 5SEED = 2096
 6np.random.seed(SEED); random.seed(SEED)
 7OUT = Path('results.json')
 8
 9def toy_scaling():
10    # g(t)=a+b*t+c*t^2 gives exact linear-extrapolation residual 2|c|h^2.
11    hs = np.logspace(-3, -0.3, 12)
12    a, b, c = 0.7, -0.4, 1.3
13    vals = []
14    for h in hs:
15        old2 = a + b*(-h) + c*(-h)**2
16        old1 = a
17        current = a + b*h + c*h*h
18        predicted = old1 + (old1-old2)
19        vals.append(abs(current-predicted))
20    slope = float(np.polyfit(np.log(hs), np.log(vals), 1)[0])
21    C, eps = 2.0*abs(c), 0.05
22    claimed_target = math.sqrt(eps/C)
23    # This is exactly the formula in the idea: h_next=sqrt(eps/||r||),
24    # with projection and ratio limiting. Its fixed point is fourth-root,
25    # because ||r||=C*h^2; this is tested separately from the claimed target.
26    h, hmin, hmax, q, delta = 0.8, 1e-4, 2.0, 1.35, 1e-12
27    trace=[]
28    for _ in range(100):
29        residual=C*h*h
30        raw=math.sqrt(eps/(residual+delta))
31        nh=float(np.clip(raw,hmin,hmax)); nh=float(np.clip(nh,h/q,q*h))
32        trace.append((h,residual,nh)); h=nh
33    observed_h=float(np.mean([z[0] for z in trace[-10:]]))
34    observed_r=float(np.mean([z[1] for z in trace[-10:]]))
35    formula_fixed_point=(eps/C)**0.25
36    rng=np.random.default_rng(SEED); h=.2; noisy_ratios=[]
37    for _ in range(100):
38        measured=max(1e-9,C*h*h*(1+.15*rng.normal()))
39        nh=float(np.clip(math.sqrt(eps/measured),hmin,hmax))
40        nh=float(np.clip(nh,h/q,q*h)); noisy_ratios.append(nh/h); h=nh
41    allrat=np.array([z[2]/z[0] for z in trace]+noisy_ratios)
42    return {'residual_scaling': {'observed_slope':slope,'predicted_slope':2.0,'absolute_error':abs(slope-2)},
43      'scheduler_target': {'C':C,'epsilon':eps,'claimed_sqrt_target':claimed_target,
44        'observed_h':observed_h,'observed_residual':observed_r,
45        'formula_fixed_point_fourth_root':formula_fixed_point,
46        'observed_vs_fourth_root_relative_error':abs(observed_h-formula_fixed_point)/formula_fixed_point,
47        'claimed_target_relative_error':abs(observed_h-claimed_target)/claimed_target,
48        'note':'The implemented formula lacks the multiplicative h_k present in the paper-style ratio controller.'},
49      'ratio_sweep': {'observed_min':float(allrat.min()),'observed_max':float(allrat.max()),'predicted_bounds':[1/q,q]}}
50
51def benchmark():
52    import torch
53    torch.manual_seed(SEED); np.random.seed(SEED)
54    device='cuda' if torch.cuda.is_available() else 'cpu'
55    try:
56        n=1200; rng=np.random.default_rng(SEED)
57        x=np.concatenate([rng.normal([-1.,-1.],.75,(n//2,2)),rng.normal([1.,1.],.75,(n//2,2))]).astype('float32')
58        y=np.concatenate([np.zeros(n//2),np.ones(n//2)]).astype('int64'); p=rng.permutation(n); x=x[p]; y=y[p]
59        X=torch.tensor(x,device=device); Y=torch.tensor(y,device=device)
60        def run(kind):
61            torch.manual_seed(SEED+3)
62            model=torch.nn.Sequential(torch.nn.Linear(2,24),torch.nn.Tanh(),torch.nn.Linear(24,2)).to(device)
63            lossfn=torch.nn.CrossEntropyLoss(); params=list(model.parameters()); prev=[]; intervals=[.08,.08]
64            h=.08; eps=.003; hmin=.015; hmax=.20; q=1.25; beta=.8; R=None; losses=[]; hs=[]; accs=[]
65            for k in range(260):
66                ix=torch.randint(0,n,(64,),device=device); model.zero_grad(set_to_none=True)
67                loss=lossfn(model(X[ix]),Y[ix]); loss.backward(); g=torch.cat([p.grad.detach().reshape(-1) for p in params])
68                if kind=='adaptive' and len(prev)>=2:
69                    pred=prev[-1]+(h/intervals[-1])*(prev[-1]-prev[-2]); res=torch.linalg.vector_norm(g-pred).item()
70                    R=res if R is None else beta*R+(1-beta)*res
71                    nh=float(np.clip(math.sqrt(eps/(R+1e-8)),hmin,hmax)); nh=float(np.clip(nh,h/q,q*h))
72                else: nh=h
73                with torch.no_grad():
74                    for z in params: z -= nh*z.grad
75                prev.append(g); prev=prev[-3:]; intervals.append(nh); intervals=intervals[-3:]
76                h=nh if kind=='adaptive' else (.08*(.5*(1+math.cos(math.pi*(k+1)/260)) if kind=='cosine' else 1.))
77                losses.append(float(loss)); hs.append(nh)
78                with torch.no_grad(): accs.append(float((model(X).argmax(1)==Y).float().mean()))
79            return {'final_loss':float(np.mean(losses[-20:])),'final_accuracy':float(np.mean(accs[-20:])),'best_loss':float(min(losses)),'mean_interval':float(np.mean(hs)),'interval_min':float(min(hs)),'interval_max':float(max(hs))}
80        out={k:run(k) for k in ['fixed','cosine','adaptive']}; out['device']=device; return out
81    except Exception as e:
82        if device=='cuda':
83            torch.cuda.empty_cache(); return {'cuda_error':str(e),'fallback':'run with CUDA_VISIBLE_DEVICES=-1'}
84        return {'error':str(e)}
85
86def main():
87    data={'seed':SEED,'mechanism':toy_scaling()}
88    try: data['benchmark']=benchmark()
89    except Exception as e: data['benchmark_error']=repr(e)
90    OUT.write_text(json.dumps(data,indent=2)); print(json.dumps(data,indent=2))
91if __name__=='__main__': main()