frame prototype v0.1: byte-state arm beats index twin (digits 0.275 vs 0.045, twin has only 200 keys for 999 states); RSA says the codebook kept C ancestry; entry channel real vs failing random control
Browse files- proto_frame/proto_train_frame.py +332 -0
proto_frame/proto_train_frame.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Frame prototype v0.1 — training runner (phases 1, 2, 2b + twin).
|
| 2 |
+
|
| 3 |
+
Implements the v0.1 spec (plan: 2026-08-18_lexicon_translation_
|
| 4 |
+
learning_DRAFT.md). Byte-state arm: E_C init from C's own byte-
|
| 5 |
+
structural features; teacher aligns TO the codebook (Route B);
|
| 6 |
+
logit-adjusted cosine InfoNCE (prior in frozen bias, never in
|
| 7 |
+
geometry); student phase against frozen E_C with unit-sphere MSE.
|
| 8 |
+
Index-space twin: same losses/budget, codebook keyed by student
|
| 9 |
+
anchor token id, state posterior via P(state|key). Gauges: per-class
|
| 10 |
+
identification (prior-free + prior-added), masked-reading delta,
|
| 11 |
+
teacher ceiling, oracle-codebook control, parity baseline, drift,
|
| 12 |
+
RSA frame attribution, gallery decay, counterfactual entry.
|
| 13 |
+
"""
|
| 14 |
+
import json
|
| 15 |
+
import sys
|
| 16 |
+
import zlib
|
| 17 |
+
|
| 18 |
+
sys.path.insert(0, r"E:\mirel\geolip-bytelex")
|
| 19 |
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
| 20 |
+
import numpy as np
|
| 21 |
+
import torch
|
| 22 |
+
import torch.nn.functional as F
|
| 23 |
+
import transformers
|
| 24 |
+
from transformers import AutoTokenizer
|
| 25 |
+
|
| 26 |
+
from geolip.bytelex.frame import (apply_whitening, fit_whitening,
|
| 27 |
+
gallery_decay, split_sites,
|
| 28 |
+
state_byte_features, procrustes,
|
| 29 |
+
top_k_accuracy)
|
| 30 |
+
|
| 31 |
+
D = r"E:\mirel\data\bytelex\proto_frame"
|
| 32 |
+
WORDS = r"E:\mirel\data\bytelex\words_of_C.json"
|
| 33 |
+
DEV = "cuda"
|
| 34 |
+
SEED = zlib.crc32(b"frame-proto-v01") & 0xFFFFFFFF
|
| 35 |
+
EPOCHS_T, EPOCHS_S, BS = 6, 6, 512
|
| 36 |
+
torch.manual_seed(SEED)
|
| 37 |
+
|
| 38 |
+
states = json.load(open(WORDS, encoding="utf-8"))
|
| 39 |
+
t5_walk = json.load(open(rf"{D}\t5_walk_of_C.json", encoding="utf-8"))
|
| 40 |
+
dump = np.load(rf"{D}\frame_dump_v2.npz")
|
| 41 |
+
ctx = np.load(rf"{D}\ctx_profiles.npz")
|
| 42 |
+
sid = dump["sid"].astype(np.int64)
|
| 43 |
+
KK = dump["KK"]
|
| 44 |
+
ok = KK[:, 0] > 0
|
| 45 |
+
NS = 999
|
| 46 |
+
|
| 47 |
+
# walk-based class axis (frame-purity fix 7a)
|
| 48 |
+
def cls_of(s):
|
| 49 |
+
t = s["text"]
|
| 50 |
+
if t.isdigit():
|
| 51 |
+
return "digit"
|
| 52 |
+
if t[0].isupper():
|
| 53 |
+
return "Name"
|
| 54 |
+
return "word"
|
| 55 |
+
CLS = np.array([cls_of(s) for s in states])
|
| 56 |
+
site_cls = CLS[sid]
|
| 57 |
+
|
| 58 |
+
sp = split_sites(sid[ok], seed=SEED)
|
| 59 |
+
gix = {k: np.flatnonzero(ok)[v] for k, v in sp.items()}
|
| 60 |
+
print(f"[F] sites fit/train/eval = "
|
| 61 |
+
f"{len(gix['fit'])}/{len(gix['train'])}/{len(gix['eval'])}",
|
| 62 |
+
flush=True)
|
| 63 |
+
|
| 64 |
+
# ---------- Phase 0 stats (fp64, fit/train only)
|
| 65 |
+
H_B = dump["H_B"][:, 0].astype(np.float64) # byte-anchored first
|
| 66 |
+
H_T = dump["H_T"][:, 0].astype(np.float64)
|
| 67 |
+
muB, wB = fit_whitening(H_B[gix["fit"]])
|
| 68 |
+
muT, wT = fit_whitening(H_T[gix["fit"]])
|
| 69 |
+
ZB = apply_whitening(H_B, muB, wB)
|
| 70 |
+
ZT = apply_whitening(H_T, muT, wT)
|
| 71 |
+
evB = np.linalg.eigvalsh(np.cov(H_B[gix["fit"]] - muB, rowvar=False))
|
| 72 |
+
prB = float((evB.sum() ** 2) / (evB ** 2).sum() / len(evB))
|
| 73 |
+
|
| 74 |
+
# byte-structural codebook init (PCA-whiten fp64 -> 256, unit rows)
|
| 75 |
+
feats = state_byte_features([s["text"].encode() for s in states],
|
| 76 |
+
ctx["prev_ctx"], ctx["next_ctx"])
|
| 77 |
+
fc = feats - feats.mean(0)
|
| 78 |
+
u, sv, vt = np.linalg.svd(fc.astype(np.float64), full_matrices=False)
|
| 79 |
+
E0 = u[:, :256] * 1.0 # decorrelated rows
|
| 80 |
+
E0 = E0 / np.linalg.norm(E0, axis=1, keepdims=True)
|
| 81 |
+
|
| 82 |
+
# teacher whitened state-means on TRAIN
|
| 83 |
+
def state_means(z, ix):
|
| 84 |
+
m = np.zeros((NS, z.shape[1]))
|
| 85 |
+
for s in range(NS):
|
| 86 |
+
r = ix[sid[ix] == s]
|
| 87 |
+
if len(r):
|
| 88 |
+
m[s] = z[r].mean(0)
|
| 89 |
+
return m
|
| 90 |
+
mT_train = state_means(ZB, gix["train"])
|
| 91 |
+
R_T = procrustes(mT_train, E0) # 768 -> 256, fp64
|
| 92 |
+
|
| 93 |
+
# priors (frozen, TRAIN)
|
| 94 |
+
cnt = np.bincount(sid[gix["train"]], minlength=NS).astype(np.float64)
|
| 95 |
+
b_prior = np.log(np.maximum(cnt, 0.5) / cnt.sum())
|
| 96 |
+
|
| 97 |
+
# ---------- torch setup
|
| 98 |
+
tZB = torch.tensor(ZB, dtype=torch.float32, device=DEV)
|
| 99 |
+
tZT = torch.tensor(ZT, dtype=torch.float32, device=DEV)
|
| 100 |
+
tsid = torch.tensor(sid, device=DEV)
|
| 101 |
+
E_C = torch.nn.Parameter(torch.tensor(E0, dtype=torch.float32,
|
| 102 |
+
device=DEV))
|
| 103 |
+
W_Tm = torch.nn.Parameter(torch.tensor(R_T, dtype=torch.float32,
|
| 104 |
+
device=DEV))
|
| 105 |
+
s_T = torch.nn.Parameter(torch.tensor(10.0, device=DEV))
|
| 106 |
+
bp = torch.tensor(b_prior, dtype=torch.float32, device=DEV)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def id_logits(z, W, E, s, prior):
|
| 110 |
+
zf = F.normalize(z @ W, dim=-1)
|
| 111 |
+
lg = s.clamp(1, 100) * (zf @ F.normalize(E, dim=-1).T)
|
| 112 |
+
return lg + bp if prior else lg
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def train_phase(params, loss_fn, ixs, epochs, tag):
|
| 116 |
+
opt = torch.optim.Adam(params, lr=1e-3, weight_decay=0.0)
|
| 117 |
+
rng = np.random.default_rng(SEED + 7)
|
| 118 |
+
for ep in range(epochs):
|
| 119 |
+
order = rng.permutation(ixs)
|
| 120 |
+
tot = nb = 0
|
| 121 |
+
for i in range(0, len(order), BS):
|
| 122 |
+
b = torch.tensor(order[i:i + BS], device=DEV)
|
| 123 |
+
opt.zero_grad(set_to_none=True)
|
| 124 |
+
l = loss_fn(b)
|
| 125 |
+
l.backward()
|
| 126 |
+
opt.step()
|
| 127 |
+
with torch.no_grad():
|
| 128 |
+
E_C.data = F.normalize(E_C.data, dim=-1)
|
| 129 |
+
tot += float(l)
|
| 130 |
+
nb += 1
|
| 131 |
+
print(f"[F {tag}] ep{ep} loss={tot / nb:.4f}", flush=True)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# ---------- Phase 1: teacher anchors (L_id^T)
|
| 135 |
+
def loss_T(b):
|
| 136 |
+
lg = id_logits(tZB[b], W_Tm, E_C, s_T, True)
|
| 137 |
+
return F.cross_entropy(lg, tsid[b])
|
| 138 |
+
|
| 139 |
+
E_before = E_C.detach().cpu().numpy().copy()
|
| 140 |
+
train_phase([W_Tm, E_C, s_T], loss_T, gix["train"], EPOCHS_T, "P1")
|
| 141 |
+
E_C.requires_grad_(False)
|
| 142 |
+
s_T.requires_grad_(False)
|
| 143 |
+
|
| 144 |
+
# drift by count decile + dead rows
|
| 145 |
+
drift = np.linalg.norm(E_C.detach().cpu().numpy() - E_before, axis=1)
|
| 146 |
+
dec = np.digitize(cnt, np.quantile(cnt, np.linspace(0, 1, 11)[1:-1]))
|
| 147 |
+
drift_dec = [round(float(drift[dec == d].mean()), 4) for d in range(10)]
|
| 148 |
+
|
| 149 |
+
# ---------- Phase 2: student aligns to frozen center
|
| 150 |
+
mS_train = state_means(ZT, gix["train"])
|
| 151 |
+
R_S0 = procrustes(mS_train, E_C.detach().cpu().numpy().astype(
|
| 152 |
+
np.float64))
|
| 153 |
+
W_Sm = torch.nn.Parameter(torch.tensor(R_S0, dtype=torch.float32,
|
| 154 |
+
device=DEV))
|
| 155 |
+
with torch.no_grad():
|
| 156 |
+
zT_frame = F.normalize(tZB @ W_Tm, dim=-1) # frozen teacher
|
| 157 |
+
|
| 158 |
+
def eval_id(z, W, ix, prior, k=1):
|
| 159 |
+
with torch.no_grad():
|
| 160 |
+
lg = id_logits(z[torch.tensor(ix, device=DEV)], W, E_C, s_T,
|
| 161 |
+
prior).cpu().numpy()
|
| 162 |
+
return top_k_accuracy(lg, sid[ix], k), lg
|
| 163 |
+
|
| 164 |
+
parity1, _ = eval_id(tZT, W_Sm, gix["eval"], False)
|
| 165 |
+
|
| 166 |
+
def loss_S(b):
|
| 167 |
+
lg = id_logits(tZT[b], W_Sm, E_C, s_T, True)
|
| 168 |
+
zs = F.normalize(tZT[b] @ W_Sm, dim=-1)
|
| 169 |
+
return (F.cross_entropy(lg, tsid[b])
|
| 170 |
+
+ ((zs - zT_frame[b]) ** 2).sum(-1).mean())
|
| 171 |
+
|
| 172 |
+
train_phase([W_Sm], loss_S, gix["train"], EPOCHS_S, "P2")
|
| 173 |
+
|
| 174 |
+
# ---------- gauges
|
| 175 |
+
led = {"_env": {"transformers": transformers.__version__,
|
| 176 |
+
"torch": torch.__version__, "seed": int(SEED)},
|
| 177 |
+
"splits": {k: int(len(v)) for k, v in gix.items()},
|
| 178 |
+
"whitening": {"participation_ratio_B": round(prB, 4)},
|
| 179 |
+
"drift_by_decile": drift_dec,
|
| 180 |
+
"dead_rows": int((drift < 1e-4).sum()),
|
| 181 |
+
"parity_baseline_eval_top1": round(float(parity1), 4)}
|
| 182 |
+
ev = gix["eval"]
|
| 183 |
+
accs = {}
|
| 184 |
+
for tag, z, W in (("teacher", tZB, W_Tm), ("student", tZT, W_Sm)):
|
| 185 |
+
for prior in (False, True):
|
| 186 |
+
a1, lg = eval_id(z, W, ev, prior)
|
| 187 |
+
a5 = top_k_accuracy(lg, sid[ev], 5)
|
| 188 |
+
key = f"{tag}_{'prior' if prior else 'balanced'}"
|
| 189 |
+
accs[key] = {"top1": round(a1, 4), "top5": round(a5, 4)}
|
| 190 |
+
per = {}
|
| 191 |
+
for c in ("word", "Name", "digit"):
|
| 192 |
+
m = site_cls[ev] == c
|
| 193 |
+
if m.any():
|
| 194 |
+
per[c] = {"n": int(m.sum()),
|
| 195 |
+
"top1": round(top_k_accuracy(
|
| 196 |
+
lg[m], sid[ev][m]), 4)}
|
| 197 |
+
accs[key]["by_class"] = per
|
| 198 |
+
led["identification"] = accs
|
| 199 |
+
|
| 200 |
+
# masked-reading delta (control): masked readouts through same maps
|
| 201 |
+
MB = apply_whitening(dump["M_B"].astype(np.float64), muB, wB)
|
| 202 |
+
MT = apply_whitening(dump["M_T"].astype(np.float64), muT, wT)
|
| 203 |
+
tMB = torch.tensor(MB, dtype=torch.float32, device=DEV)
|
| 204 |
+
tMT = torch.tensor(MT, dtype=torch.float32, device=DEV)
|
| 205 |
+
delta = {}
|
| 206 |
+
for tag, zu, zm, W in (("teacher", tZB, tMB, W_Tm),
|
| 207 |
+
("student", tZT, tMT, W_Sm)):
|
| 208 |
+
au, lgu = eval_id(zu, W, ev, False)
|
| 209 |
+
am, lgm = eval_id(zm, W, ev, False)
|
| 210 |
+
per = {}
|
| 211 |
+
for c in ("word", "Name", "digit"):
|
| 212 |
+
m = site_cls[ev] == c
|
| 213 |
+
if m.any():
|
| 214 |
+
per[c] = round(top_k_accuracy(lgu[m], sid[ev][m])
|
| 215 |
+
- top_k_accuracy(lgm[m], sid[ev][m]), 4)
|
| 216 |
+
delta[tag] = {"overall": round(au - am, 4), "by_class": per}
|
| 217 |
+
led["reading_delta"] = delta
|
| 218 |
+
|
| 219 |
+
# oracle-codebook control (student vs never-trained student means)
|
| 220 |
+
mS_unit = mS_train / np.maximum(
|
| 221 |
+
np.linalg.norm(mS_train, axis=1, keepdims=True), 1e-9)
|
| 222 |
+
scr = ZT[ev] @ mS_unit.T
|
| 223 |
+
led["oracle_codebook_student_top1"] = round(
|
| 224 |
+
top_k_accuracy(scr, sid[ev]), 4)
|
| 225 |
+
|
| 226 |
+
# RSA frame attribution: whose geometry is final E_C?
|
| 227 |
+
Ef = F.normalize(E_C, dim=-1).cpu().numpy().astype(np.float64)
|
| 228 |
+
def rsa(a, b, rng):
|
| 229 |
+
iu = np.triu_indices(NS, 1)
|
| 230 |
+
pick = rng.choice(len(iu[0]), size=100_000, replace=False)
|
| 231 |
+
va = (a @ a.T)[iu][pick]
|
| 232 |
+
vb = (b @ b.T)[iu][pick]
|
| 233 |
+
ra, rb = np.argsort(np.argsort(va)), np.argsort(np.argsort(vb))
|
| 234 |
+
return float(np.corrcoef(ra, rb)[0, 1])
|
| 235 |
+
rr = np.random.default_rng(SEED + 13)
|
| 236 |
+
mB_unit = mT_train / np.maximum(
|
| 237 |
+
np.linalg.norm(mT_train, axis=1, keepdims=True), 1e-9)
|
| 238 |
+
led["rsa"] = {"E_C_vs_byte_features": round(rsa(Ef, E0, rr), 4),
|
| 239 |
+
"E_C_vs_teacher_means": round(rsa(Ef, mB_unit, rr), 4)}
|
| 240 |
+
|
| 241 |
+
# gallery decay (prior-free, student)
|
| 242 |
+
_, lgS = eval_id(tZT, W_Sm, ev, False)
|
| 243 |
+
sub = rr.choice(len(ev), size=min(2000, len(ev)), replace=False)
|
| 244 |
+
led["gallery_decay_student"] = gallery_decay(lgS[sub], sid[ev][sub],
|
| 245 |
+
seed=SEED + 17)
|
| 246 |
+
|
| 247 |
+
# ---------- index-space twin (same losses/budget, key = t5 anchor id)
|
| 248 |
+
tkA = AutoTokenizer.from_pretrained("google/flan-t5-small")
|
| 249 |
+
lines = open(r"E:\mirel\data\bytelex\codex_v1.txt",
|
| 250 |
+
"rb").read().decode("ascii").split("\n")
|
| 251 |
+
anchor_id = np.full(len(sid), -1, dtype=np.int64)
|
| 252 |
+
by_line = {}
|
| 253 |
+
for k in range(len(sid)):
|
| 254 |
+
by_line.setdefault(int(dump["line"][k]), []).append(k)
|
| 255 |
+
for li, ks in by_line.items():
|
| 256 |
+
e = tkA(lines[li], add_special_tokens=False,
|
| 257 |
+
return_offsets_mapping=True)
|
| 258 |
+
off = e["offset_mapping"]
|
| 259 |
+
for k in ks:
|
| 260 |
+
lo, hi = int(dump["lo"][k]), int(dump["hi"][k])
|
| 261 |
+
ix = [i for i, (s, t) in enumerate(off)
|
| 262 |
+
if t > s and s < hi and t > lo]
|
| 263 |
+
if ix:
|
| 264 |
+
anchor_id[k] = e["input_ids"][ix[0]]
|
| 265 |
+
keys, key_inv = np.unique(anchor_id[anchor_id >= 0],
|
| 266 |
+
return_inverse=False), None
|
| 267 |
+
key_of = {int(a): i for i, a in enumerate(keys)}
|
| 268 |
+
ksite = np.array([key_of.get(int(a), -1) for a in anchor_id])
|
| 269 |
+
NK = len(keys)
|
| 270 |
+
# P(state|key) from TRAIN
|
| 271 |
+
post = np.zeros((NK, NS))
|
| 272 |
+
for k in gix["train"]:
|
| 273 |
+
if ksite[k] >= 0:
|
| 274 |
+
post[ksite[k], sid[k]] += 1
|
| 275 |
+
post = post / np.maximum(post.sum(1, keepdims=True), 1)
|
| 276 |
+
mK_train = np.zeros((NK, 768))
|
| 277 |
+
for kk in range(NK):
|
| 278 |
+
r = gix["train"][ksite[gix["train"]] == kk]
|
| 279 |
+
if len(r):
|
| 280 |
+
mK_train[kk] = ZB[r].mean(0)
|
| 281 |
+
uk, sk, vk = np.linalg.svd(
|
| 282 |
+
(mK_train - mK_train.mean(0)).astype(np.float64),
|
| 283 |
+
full_matrices=False)
|
| 284 |
+
EK0 = uk[:, :256]
|
| 285 |
+
EK0 = EK0 / np.maximum(np.linalg.norm(EK0, axis=1, keepdims=True),
|
| 286 |
+
1e-9)
|
| 287 |
+
E_K = torch.nn.Parameter(torch.tensor(EK0, dtype=torch.float32,
|
| 288 |
+
device=DEV))
|
| 289 |
+
R_K = procrustes(mK_train, EK0) # same-quality init
|
| 290 |
+
W_Ki = torch.nn.Parameter(torch.tensor(R_K, dtype=torch.float32,
|
| 291 |
+
device=DEV))
|
| 292 |
+
s_K = torch.nn.Parameter(torch.tensor(10.0, device=DEV))
|
| 293 |
+
tks = torch.tensor(ksite, device=DEV)
|
| 294 |
+
cntK = np.bincount(ksite[gix["train"]][ksite[gix["train"]] >= 0],
|
| 295 |
+
minlength=NK).astype(np.float64)
|
| 296 |
+
bK = torch.tensor(np.log(np.maximum(cntK, .5) / cntK.sum()),
|
| 297 |
+
dtype=torch.float32, device=DEV)
|
| 298 |
+
trK = gix["train"][ksite[gix["train"]] >= 0]
|
| 299 |
+
|
| 300 |
+
def loss_K(b):
|
| 301 |
+
zf = F.normalize(tZB[b] @ W_Ki, dim=-1)
|
| 302 |
+
lg = s_K.clamp(1, 100) * (zf @ F.normalize(E_K, dim=-1).T) + bK
|
| 303 |
+
return F.cross_entropy(lg, tks[b])
|
| 304 |
+
|
| 305 |
+
train_phase([W_Ki, E_K, s_K], loss_K, trK, EPOCHS_T, "TWIN")
|
| 306 |
+
evK = ev[ksite[ev] >= 0]
|
| 307 |
+
with torch.no_grad():
|
| 308 |
+
zf = F.normalize(tZB[torch.tensor(evK, device=DEV)] @ W_Ki, -1)
|
| 309 |
+
lgK = (s_K.clamp(1, 100) * zf @ F.normalize(E_K, -1).T).cpu().numpy()
|
| 310 |
+
state_scores = np.exp(lgK - lgK.max(1, keepdims=True)) @ post
|
| 311 |
+
twin = {"overall": round(top_k_accuracy(state_scores, sid[evK]), 4),
|
| 312 |
+
"n_keys": int(NK)}
|
| 313 |
+
for c in ("word", "Name", "digit"):
|
| 314 |
+
m = site_cls[evK] == c
|
| 315 |
+
if m.any():
|
| 316 |
+
twin[c] = {"n": int(m.sum()),
|
| 317 |
+
"top1": round(top_k_accuracy(state_scores[m],
|
| 318 |
+
sid[evK][m]), 4)}
|
| 319 |
+
led["index_twin_state_id"] = twin
|
| 320 |
+
|
| 321 |
+
with open(rf"{D}\frame_ledger_v01.json", "w", encoding="utf-8") as f:
|
| 322 |
+
json.dump(led, f, indent=1)
|
| 323 |
+
np.savez(rf"{D}\frame_anchors_v01.npz",
|
| 324 |
+
E_C=E_C.detach().cpu().numpy(),
|
| 325 |
+
W_T=W_Tm.detach().cpu().numpy(),
|
| 326 |
+
W_S=W_Sm.detach().cpu().numpy(),
|
| 327 |
+
s=float(s_T), b_prior=b_prior, E0=E0)
|
| 328 |
+
print(json.dumps(led["identification"], indent=1)[:1500], flush=True)
|
| 329 |
+
print("[F] TWIN:", json.dumps(twin), flush=True)
|
| 330 |
+
print("[F] RSA:", json.dumps(led["rsa"]),
|
| 331 |
+
"| reading_delta:", json.dumps(led["reading_delta"]), flush=True)
|
| 332 |
+
print("[F] PHASES 1-2 + TWIN COMPLETE", flush=True)
|