diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-21 07:26:54 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-21 07:26:54 -0500 |
| commit | 2f3f44d49df3d1dd42200bae5fa6cbe943c3d42c (patch) | |
| tree | eca525cc1fbbf71f4836beac890f0793b6a18d3c /sdil/core.py | |
| parent | ef12fe0d1b4ca33555b324d0d761c4c997eda6cb (diff) | |
feat: add magnitude-matched raw apical control
Diffstat (limited to 'sdil/core.py')
| -rw-r--r-- | sdil/core.py | 35 |
1 files changed, 32 insertions, 3 deletions
diff --git a/sdil/core.py b/sdil/core.py index 3cd8a97..d7be8c3 100644 --- a/sdil/core.py +++ b/sdil/core.py @@ -219,6 +219,31 @@ class SDILNet: return a, a +def teaching_signal(net, l, c, h_l, cfg): + """Select the signal that drives a hidden layer's local plasticity. + + ``match_innovation_norm`` is a deliberately strong raw-apical control: it + preserves each sample's raw apical direction exactly while rescaling its + Euclidean norm to the norm of the corresponding innovation. Therefore a + raw-vs-residual difference under this control cannot be attributed merely + to the nuisance making the teaching vector larger. + + Returns ``(teaching, raw_apical, innovation)`` so training and diagnostic + probes use one definition of every signal. + """ + a = net.apical(l, c, h_l) + innovation = a - net.baseline(l, h_l) + if cfg.use_residual: + teaching = innovation + elif cfg.raw_scale_control == "match_innovation_norm": + raw_norm = a.norm(dim=1, keepdim=True) + innovation_norm = innovation.norm(dim=1, keepdim=True) + teaching = a * (innovation_norm / raw_norm.clamp_min(1e-12)) + else: + teaching = a + return teaching, a, innovation + + # -------------------------------------------------------------------------- # node perturbation: causal estimate of -grad_{h_l} L used to TRAIN A_l # -------------------------------------------------------------------------- @@ -341,11 +366,15 @@ class SDILConfig: use_residual=True, learn_A=True, learn_P=True, pert_sigma=1e-2, pert_every=5, pert_ndirs=1, momentum=0.0, wd=0.0, settle_steps=0, kappa=0.0, feedback="error", p_update_on_neutral=True, - normalize_delta=False, pert_mode="layerwise"): + normalize_delta=False, pert_mode="layerwise", + raw_scale_control="none"): self.eta = eta self.eta_A = eta_A self.eta_P = eta_P self.use_residual = use_residual # ablation: residual vs raw apical + if raw_scale_control not in ("none", "match_innovation_norm"): + raise ValueError(f"unknown raw_scale_control: {raw_scale_control}") + self.raw_scale_control = raw_scale_control self.learn_A = learn_A # ablation: perturbation-trained vs fixed-random A self.learn_P = learn_P # ablation: predictor on/off self.pert_sigma = pert_sigma @@ -390,7 +419,7 @@ def sdil_step(net, x, y, y_onehot, cfg, step, prev_error=None): # ---- compute innovations for hidden layers ---- r_list, a_list = [], [] for l in range(net.L - 1): - r, a = net.innovation(l, c, h[l + 1], use_residual=cfg.use_residual) + r, a, _ = teaching_signal(net, l, c, h[l + 1], cfg) r_list.append(r) a_list.append(a) @@ -496,7 +525,7 @@ def _settle(net, h, u, c, cfg): dt_over_tau = 1.0 / max(1, cfg.settle_steps) for _ in range(cfg.settle_steps): for l in range(net.L - 1): - r, _ = net.innovation(l, c, h[l + 1], use_residual=cfg.use_residual) + r, _, _ = teaching_signal(net, l, c, h[l + 1], cfg) target = net.act(u[l]) + cfg.kappa * r h[l + 1] = h[l + 1] + dt_over_tau * (-h[l + 1] + target) # recompute downstream pre-activations from settled hidden states |
