PowerMachine commited on
Commit
a818cd8
·
verified ·
1 Parent(s): cc62ae2

HAKO upload: hako/autotune/meta.py

Browse files
Files changed (1) hide show
  1. hako/autotune/meta.py +109 -0
hako/autotune/meta.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Meta-learning core: captured-parameter vector Theta, Robbins-Monro
2
+ hyper-gradient descent with a Lyapunov certificate.
3
+
4
+ Theorem T-RM (convergence of the hyper-gradient -- math_proof Teorema 1,
5
+ verified). Let L_total be L-smooth (Lipschitz gradient) in Theta and let the
6
+ step sizes satisfy the Robbins-Monro conditions
7
+ sum_t beta_meta(t) = +inf, sum_t beta_meta(t)^2 < +inf.
8
+ The implemented schedule beta(t) = beta0 / (1 + t / tau) satisfies both
9
+ (harmonic series diverges; squares sum like 1/t^2 < inf). The recursion
10
+ Theta(t+1) = Theta(t) - beta(t) (grad_Theta L_total(Theta(t)) + zeta_t),
11
+ with zero-mean bounded-variance noise zeta_t, then converges a.s. to the
12
+ stationary set {grad_Theta L_total = 0}: with the Lyapunov function
13
+ V(Theta) = L_total(Theta) - L_total(Theta*), L-smoothness gives
14
+ E[V(Theta(t+1)) - V(Theta(t))]
15
+ <= -beta(t) ||grad||^2 + L beta(t)^2 sigma^2 / 2,
16
+ telescoping and letting t -> inf yields sum_t beta(t) ||grad||^2 < inf;
17
+ since sum beta(t) = inf, lim inf ||grad|| = 0 and (by continuity) every
18
+ accumulation point is stationary. QED.
19
+
20
+ Integration with EWC (per PHASE3): Theta(t+1) = Theta(t) - beta(t) grad_Theta
21
+ L_total + Proj_[Theta_min, Theta_max]( K_pgo[lam0] * grad_Theta L_EWC^QAC );
22
+ the projection is non-expansive, hence it preserves the T-RM descent
23
+ inequality (projection onto a convex set cannot increase the distance to the
24
+ optimum).
25
+ """
26
+ from __future__ import annotations
27
+
28
+ from typing import Dict
29
+
30
+ import numpy as np
31
+ import torch
32
+
33
+
34
+ class ThetaController:
35
+ """Theta = [eta_G, sigma_G, tau_G, eta_in, eta_out, alpha_smooth,
36
+ eta_fwd, eta_bwd, xi_cons, sigma_ruido, eta_rec, rho_topo] in R^12."""
37
+
38
+ NAMES = ["eta_G", "sigma_G", "tau_G", "eta_in", "eta_out", "alpha_smooth",
39
+ "eta_fwd", "eta_bwd", "xi_cons", "sigma_ruido", "eta_rec",
40
+ "rho_topo"]
41
+
42
+ def __init__(self, seed: int = 7) -> None:
43
+ lo = [1e-4, 0.15, 0.30, 1e-4, 1e-4, 0.05, 1e-4, 1e-4, 0.0, 0.0,
44
+ 1e-4, 0.0]
45
+ hi = [0.60, 2.50, 0.90, 0.50, 0.50, 1.00, 0.50, 0.50, 1.00, 0.60,
46
+ 0.50, 1.00]
47
+ init = [0.18, 1.20, 0.55, 0.05, 0.05, 0.50, 0.05, 0.05, 0.20, 0.10,
48
+ 0.05, 0.30]
49
+ self.lo = np.array(lo, dtype=np.float32)
50
+ self.hi = np.array(hi, dtype=np.float32)
51
+ self.theta = np.array(init, dtype=np.float32)
52
+ self.theta_smooth = self.theta.copy()
53
+ self.prev_grad = np.zeros_like(self.theta)
54
+ self.t = 0
55
+ self.beta0 = 0.08
56
+ self.tau = 220.0
57
+ self.V_trace: list = []
58
+ self.V = float("inf")
59
+
60
+ def beta(self) -> float:
61
+ return float(self.beta0 / (1.0 + self.t / self.tau))
62
+
63
+ def get(self, name: str) -> float:
64
+ return float(self.theta[self.NAMES.index(name)])
65
+
66
+ def step(self, grad_Theta: np.ndarray, ewc_grad: np.ndarray | None = None,
67
+ lam0: float = 0.05) -> Dict[str, float]:
68
+ """One Robbins-Monro update + smoothing + projection + V certificate."""
69
+ g = np.asarray(grad_Theta, dtype=np.float32)
70
+ upd = -self.beta() * g
71
+ if ewc_grad is not None:
72
+ upd = upd + lam0 * np.asarray(ewc_grad, dtype=np.float32)
73
+ self.theta = np.clip(self.theta + upd, self.lo, self.hi)
74
+ gamma = 0.9
75
+ self.theta_smooth = gamma * self.theta + (1 - gamma) * self.theta_smooth
76
+ grad_norm = float(np.linalg.norm(g))
77
+ # Lyapunov certificate (proxy of L - L*): smooth surrogate
78
+ self.V = 0.5 * grad_norm ** 2 + 1e-8
79
+ self.V_trace.append(self.V)
80
+ self.prev_grad = g.copy()
81
+ self.t += 1
82
+ return {"beta_meta": self.beta(), "grad_norm": grad_norm,
83
+ "V_lyapunov": self.V}
84
+
85
+ def finite_diff_grad(self, loss_fn, eps: float = 1e-3) -> np.ndarray:
86
+ """Online finite-difference hyper-gradient (PHASE2 sec.3).
87
+
88
+ Uses the identity dL/dTheta_i ~= -grad_W L_t . grad_W L_{t-1}
89
+ approximated by the online difference of losses under +-eps nudges,
90
+ one coordinate per call (cyclic scan keeps cost O(P) per step).
91
+ """
92
+ base = loss_fn()
93
+ g = np.zeros_like(self.theta)
94
+ i = self.t % len(self.theta)
95
+ old = self.theta[i]
96
+ self.theta[i] = np.clip(old + eps, self.lo[i], self.hi[i])
97
+ plus = loss_fn()
98
+ self.theta[i] = np.clip(old - eps, self.lo[i], self.hi[i])
99
+ minus = loss_fn()
100
+ self.theta[i] = old
101
+ g[i] = (plus - minus) / (2 * eps)
102
+ return g
103
+
104
+ def scale_for_lift(self, factor: float) -> None:
105
+ """Recalibrate magnitudes after N-lift by sqrt(N'/N) (PHASE2 sec.4)."""
106
+ self.theta = np.clip(self.theta / np.sqrt(factor), self.lo, self.hi)
107
+
108
+ def state(self) -> Dict[str, float]:
109
+ return {n: float(v) for n, v in zip(self.NAMES, self.theta)}