AbstractPhil commited on
Commit
3dcca87
·
verified ·
1 Parent(s): 57e06d7

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
Files changed (1) hide show
  1. 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)