OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-layer-discovery commits / 946988d

experiments/jigsaw-transformer: v0 harness + smoke test — audit works, learner does not
Event log, replay and mutation detection pass; count-based EM from a random start stays diffuse (V-measure 0.42 vs type-level 0.78). No full run made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
946988d12b2a7ea47b2087c11699189f34c6d1d4
by plankatron <fleeky@gmail.com> · 2026-10-18T20:52:55.000Z

Changed files (3) +431 -0

experiments/jigsaw-transformer/.gitignore +1 -0
……@@ -0,0 +1 @@
1jt
experiments/jigsaw-transformer/SMOKE.md +18 -0
……@@ -0,0 +1,18 @@
1# Jigsaw transformer v0 — smoke test (1 seed, 3K sentences, 500 held-out), before any full run
2
3| | V-measure | many-to-one | sense acc. | parse F1 |
4|---|---|---|---|---|
5| JT batch (soft random init) | 0.413 | 0.437 | 0.413 | attention 0.320 |
6| JT batch (hard random init) | 0.421 | 0.452 | 0.386 | attention 0.254 |
7| JT continuous (hard init) | 0.423 | 0.449 | 0.417 | — |
8| TYPE (exchange, one class per word) | 0.777 | 0.603 | 0.612 | — |
9| MAJ (majority gold class per word) | 0.977 | 0.987 | 0.881 | — |
10| word-level MI tree / gold-class MI tree | — | — | — | 0.147 / 0.705 |
11
12- **Audit works:** hash-chained event log, replay reproduces every event, a one-event count change is detected at
13 exactly that event; `explain` traces a token's class and attention to counts.
14- **The learner does not:** EM from a random start stays near the symmetric fixed point (top class probability
15 ~0.05-0.21, attention ~uniform, class-pair PMIs ~0). Learning classes *from scratch* by soft EM is the hard
16 problem (unsupervised tagging by EM), and v0 does not solve it.
17- The init change (soft → hard random) was made after this smoke test; it is within PREREG's "random (seeded)"
18 but did not help. No full run has been made.
experiments/jigsaw-transformer/jt.c +412 -0
……@@ -0,0 +1,412 @@
1// Jigsaw transformer v0: count-based attention over jigsaws. See PREREG.md.
2// Reuses the planted grammar, exchange clusterer and Eisner parser of ../across-cluster/ac.c unchanged.
3//
4// jt <seed> [--tiers 3000,10000,30000] [--heldout N] [--K 30] [--shuffle] [--explain file] [--log file]
5// Output rows (stdout):
6// V <seed> <tier> <K> <method> <vmeasure> <m1acc> <senseacc> methods: JT-BATCH JT-CONT TYPE MAJ GOLD
7// L <seed> <tier> <K> <parser> <F1> parsers: ATTN WORD-MI GOLD-MI
8// A <seed> <tier> <K> <check> <PASS|FAIL> <detail> audit checks: replay, mutation
9
10#define main ac_main
11#include "../across-cluster/ac.c"
12#undef main
13
14#define LAMBDA 0.1
15#define LAYERS 2
16#define ITERS 10
17#define BATCH 1000
18
19static int KJ = 30;
20
21// ---------------------------------------------------------------- parameters (all counts)
22
23typedef struct {
24 double *pi; // V x KJ : soft count of class z on word w
25 double *phL, *phR; // KJ x KJ : soft count of left/right neighbour class y around class z
26 double *N; // KJ x KJ : soft count of class pair (left x, right y) within the window
27} counts_t;
28
29typedef struct { double *lpi, *lphL, *lphR, *pmi; } params_t; // logs / PMI+ derived from counts
30
31static void counts_alloc (counts_t *c) {
32 c->pi = calloc((size_t) V * KJ, sizeof(double));
33 c->phL = calloc((size_t) KJ * KJ, sizeof(double)); c->phR = calloc((size_t) KJ * KJ, sizeof(double));
34 c->N = calloc((size_t) KJ * KJ, sizeof(double));
35}
36static void counts_zero (counts_t *c) {
37 memset(c->pi, 0, sizeof(double) * V * KJ); memset(c->phL, 0, sizeof(double) * KJ * KJ);
38 memset(c->phR, 0, sizeof(double) * KJ * KJ); memset(c->N, 0, sizeof(double) * KJ * KJ);
39}
40static void counts_free (counts_t *c) { free(c->pi); free(c->phL); free(c->phR); free(c->N); }
41
42static void params_alloc (params_t *p) {
43 p->lpi = malloc(sizeof(double) * V * KJ);
44 p->lphL = malloc(sizeof(double) * KJ * KJ); p->lphR = malloc(sizeof(double) * KJ * KJ); p->pmi = malloc(sizeof(double) * KJ * KJ);
45}
46static void params_free (params_t *p) { free(p->lpi); free(p->lphL); free(p->lphR); free(p->pmi); }
47
48// M-step: add-λ smoothing, renormalise; PMI+ over class pairs.
49static void derive (const counts_t *c, params_t *p) {
50 for (int w = 0; w < V; w++) {
51 double s = 0;
52 for (int z = 0; z < KJ; z++) s += c->pi[(size_t) w * KJ + z];
53 for (int z = 0; z < KJ; z++) p->lpi[(size_t) w * KJ + z] = log((c->pi[(size_t) w * KJ + z] + LAMBDA) / (s + KJ * LAMBDA));
54 }
55 for (int z = 0; z < KJ; z++) {
56 double sl = 0, sr = 0;
57 for (int y = 0; y < KJ; y++) { sl += c->phL[z * KJ + y]; sr += c->phR[z * KJ + y]; }
58 for (int y = 0; y < KJ; y++) {
59 p->lphL[z * KJ + y] = log((c->phL[z * KJ + y] + LAMBDA) / (sl + KJ * LAMBDA));
60 p->lphR[z * KJ + y] = log((c->phR[z * KJ + y] + LAMBDA) / (sr + KJ * LAMBDA));
61 }
62 }
63 double T = 0, *rs = calloc(KJ, sizeof(double)), *cs = calloc(KJ, sizeof(double));
64 for (int x = 0; x < KJ; x++) for (int y = 0; y < KJ; y++) { double v = c->N[x * KJ + y] + LAMBDA; T += v; rs[x] += v; cs[y] += v; }
65 for (int x = 0; x < KJ; x++) for (int y = 0; y < KJ; y++) {
66 double v = log2((c->N[x * KJ + y] + LAMBDA) * T / (rs[x] * cs[y]));
67 p->pmi[x * KJ + y] = v > 0 ? v : 0;
68 }
69 free(rs); free(cs);
70}
71
72// ---------------------------------------------------------------- one sentence: layers of attention
73
74typedef struct { double q[MAXN][64], u[MAXN][64], mL[MAXN][64], mR[MAXN][64], alpha[MAXN][MAXN]; int n; } sent_state_t;
75
76// E-step on tokens [o, o+n) of C. Leaves q (final), alpha and messages (final layer) in S.
77static void estep (const corpus_t *C, int o, int n, const params_t *p, sent_state_t *S) {
78 S->n = n;
79 for (int t = 0; t < n; t++) { // layer 0: q from the word alone
80 double mx = -1e300, sum = 0;
81 const double *lp = p->lpi + (size_t) C->w[o + t] * KJ;
82 for (int z = 0; z < KJ; z++) if (lp[z] > mx) mx = lp[z];
83 for (int z = 0; z < KJ; z++) sum += (S->q[t][z] = exp(lp[z] - mx));
84 for (int z = 0; z < KJ; z++) S->q[t][z] /= sum;
85 }
86 static double nq[MAXN][64];
87 for (int layer = 0; layer < LAYERS; layer++) {
88 for (int t = 0; t < n; t++) for (int x = 0; x < KJ; x++) { // u_t = PMI+ q_t (t in the right role)
89 double s = 0;
90 for (int y = 0; y < KJ; y++) s += p->pmi[x * KJ + y] * S->q[t][y];
91 S->u[t][x] = s;
92 }
93 for (int t = 0; t < n; t++) {
94 double tot = 0;
95 for (int j = 0; j < n; j++) {
96 S->alpha[t][j] = 0;
97 if (j == t || abs(j - t) > W) continue;
98 int a = t < j ? t : j, b = t < j ? j : t;
99 double s = 0;
100 for (int x = 0; x < KJ; x++) s += S->q[a][x] * S->u[b][x];
101 S->alpha[t][j] = s > 0 ? s : 0;
102 tot += S->alpha[t][j];
103 }
104 int cnt = 0;
105 for (int j = 0; j < n; j++) if (j != t && abs(j - t) <= W) cnt++;
106 for (int j = 0; j < n; j++) {
107 if (j == t || abs(j - t) > W) continue;
108 S->alpha[t][j] = tot > 0 ? S->alpha[t][j] / tot : 1.0 / cnt;
109 }
110 for (int y = 0; y < KJ; y++) { S->mL[t][y] = 0; S->mR[t][y] = 0; }
111 for (int j = 0; j < n; j++) {
112 if (S->alpha[t][j] == 0) continue;
113 double *m = j < t ? S->mL[t] : S->mR[t];
114 for (int y = 0; y < KJ; y++) m[y] += S->alpha[t][j] * S->q[j][y];
115 }
116 double mx = -1e300, sum = 0, e[64];
117 const double *lp = p->lpi + (size_t) C->w[o + t] * KJ;
118 for (int z = 0; z < KJ; z++) {
119 double v = lp[z];
120 for (int y = 0; y < KJ; y++) v += S->mL[t][y] * p->lphL[z * KJ + y] + S->mR[t][y] * p->lphR[z * KJ + y];
121 e[z] = v;
122 if (v > mx) mx = v;
123 }
124 for (int z = 0; z < KJ; z++) sum += (nq[t][z] = exp(e[z] - mx));
125 for (int z = 0; z < KJ; z++) nq[t][z] /= sum;
126 }
127 for (int t = 0; t < n; t++) memcpy(S->q[t], nq[t], sizeof(double) * KJ);
128 }
129}
130
131// Add one sentence's soft counts.
132static void accumulate (const corpus_t *C, int o, const sent_state_t *S, counts_t *c) {
133 int n = S->n;
134 for (int t = 0; t < n; t++) {
135 double *pi = c->pi + (size_t) C->w[o + t] * KJ;
136 for (int z = 0; z < KJ; z++) {
137 pi[z] += S->q[t][z];
138 for (int y = 0; y < KJ; y++) {
139 c->phL[z * KJ + y] += S->q[t][z] * S->mL[t][y];
140 c->phR[z * KJ + y] += S->q[t][z] * S->mR[t][y];
141 }
142 }
143 double win[64] = { 0 };
144 for (int j = t + 1; j < n && j - t <= W; j++) for (int y = 0; y < KJ; y++) win[y] += S->q[j][y];
145 for (int x = 0; x < KJ; x++) for (int y = 0; y < KJ; y++) c->N[x * KJ + y] += S->q[t][x] * win[y];
146 }
147}
148
149// ---------------------------------------------------------------- event log (audit)
150
151static uint64_t fnv_bytes (uint64_t h, const void *p, size_t n) {
152 const unsigned char *b = p;
153 for (size_t i = 0; i < n; i++) h = (h ^ b[i]) * 1099511628211ULL;
154 return h;
155}
156
157static uint64_t counts_hash (const counts_t *c) {
158 uint64_t h = 1469598103934665603ULL;
159 h = fnv_bytes(h, c->pi, sizeof(double) * V * KJ);
160 h = fnv_bytes(h, c->phL, sizeof(double) * KJ * KJ); h = fnv_bytes(h, c->phR, sizeof(double) * KJ * KJ);
161 return fnv_bytes(h, c->N, sizeof(double) * KJ * KJ);
162}
163
164static double counts_mass (const double *a, size_t n) { double s = 0; for (size_t i = 0; i < n; i++) s += a[i]; return s; }
165
166// ---------------------------------------------------------------- training
167
168static void init_counts (counts_t *c, uint64_t seed) {
169 rng_t r = { seed };
170 counts_zero(c);
171 for (int w = 0; w < V; w++) c->pi[(size_t) w * KJ + rpick(&r, KJ)] = 1.0; // random hard start: one class per word
172}
173
174static void train_batch (const corpus_t *tr, uint64_t seed, params_t *p) {
175 counts_t c; counts_alloc(&c); init_counts(&c, seed);
176 derive(&c, p);
177 static sent_state_t S;
178 for (int it = 0; it < ITERS; it++) {
179 counts_zero(&c);
180 for (int s = 0; s < tr->ns; s++) { estep(tr, tr->off[s], tr->off[s + 1] - tr->off[s], p, &S); accumulate(tr, tr->off[s], &S, &c); }
181 derive(&c, p);
182 }
183 counts_free(&c);
184}
185
186// Continuous: batches seen once, counts never deleted. Each M-step is an event (hash-chained).
187// mutate_at >= 0 perturbs that batch's counts (audit mutation test). expect != NULL: verify hashes.
188static int train_cont (const corpus_t *tr, uint64_t seed, params_t *p, uint64_t *hashes, int *nev, const uint64_t *expect, int mutate_at, FILE *log) {
189 counts_t c; counts_alloc(&c); init_counts(&c, seed);
190 derive(&c, p);
191 static sent_state_t S;
192 uint64_t prev = 0;
193 int ev = 0, first_bad = -1;
194 for (int b0 = 0; b0 < tr->ns; b0 += BATCH) {
195 int b1 = b0 + BATCH < tr->ns ? b0 + BATCH : tr->ns;
196 double m0 = counts_mass(c.N, (size_t) KJ * KJ);
197 uint64_t sh = fnv_bytes(1469598103934665603ULL, tr->w + tr->off[b0], sizeof(int) * (tr->off[b1] - tr->off[b0]));
198 for (int s = b0; s < b1; s++) { estep(tr, tr->off[s], tr->off[s + 1] - tr->off[s], p, &S); accumulate(tr, tr->off[s], &S, &c); }
199 if (ev == mutate_at) c.N[0] += 1.0;
200 derive(&c, p);
201 uint64_t h = fnv_bytes(prev, &sh, sizeof sh);
202 h = fnv_bytes(h, (uint64_t[]) { counts_hash(&c) }, sizeof(uint64_t));
203 if (log) fprintf(log, "{\"event\":%d,\"batch\":[%d,%d],\"sentences_hash\":\"%016llx\",\"pair_mass_added\":%.6f,\"prev\":\"%016llx\",\"hash\":\"%016llx\"}\n",
204 ev, b0, b1, (unsigned long long) sh, counts_mass(c.N, (size_t) KJ * KJ) - m0, (unsigned long long) prev, (unsigned long long) h);
205 if (expect && first_bad < 0 && expect[ev] != h) first_bad = ev;
206 if (hashes) hashes[ev] = h;
207 prev = h;
208 ev++;
209 }
210 if (nev) *nev = ev;
211 counts_free(&c);
212 return first_bad;
213}
214
215// ---------------------------------------------------------------- evaluation
216
217static int *poly; // word has >= 2 gold classes
218
219static void mark_polysemous (void) {
220 poly = calloc(V, sizeof(int));
221 int *ncls = calloc(V, sizeof(int));
222 for (int c = 0; c < NCLS; c++) for (int k = 0; k < ncw[c]; k++) ncls[cw[c][k]]++;
223 for (int w = 0; w < V; w++) poly[w] = ncls[w] >= 2;
224 free(ncls);
225}
226
227// V-measure, many-to-one accuracy, and sense accuracy (polysemous occurrences) of labels lab[] vs gold.
228static void score_labels (const corpus_t *C, const int *lab, int nlab, double *vm, double *m1, double *sense) {
229 double *ct = calloc((size_t) nlab * NCLS, sizeof(double)), *rl = calloc(nlab, sizeof(double)), *rg = calloc(NCLS, sizeof(double)), Nt = C->ntok;
230 for (int t = 0; t < C->ntok; t++) { ct[(size_t) lab[t] * NCLS + C->c[t]]++; rl[lab[t]]++; rg[C->c[t]]++; }
231 double Hc = 0, Hk = 0, Hck = 0, Hkc = 0;
232 for (int g = 0; g < NCLS; g++) if (rg[g] > 0) Hc -= rg[g] / Nt * log(rg[g] / Nt);
233 for (int l = 0; l < nlab; l++) if (rl[l] > 0) Hk -= rl[l] / Nt * log(rl[l] / Nt);
234 for (int l = 0; l < nlab; l++) for (int g = 0; g < NCLS; g++) {
235 double v = ct[(size_t) l * NCLS + g];
236 if (v <= 0) continue;
237 Hck -= v / Nt * log(v / rl[l]);
238 Hkc -= v / Nt * log(v / rg[g]);
239 }
240 double hom = Hc > 0 ? 1 - Hck / Hc : 1, com = Hk > 0 ? 1 - Hkc / Hk : 1;
241 *vm = hom + com > 0 ? 2 * hom * com / (hom + com) : 0;
242 int *map = malloc(sizeof(int) * nlab);
243 for (int l = 0; l < nlab; l++) { int b = 0; for (int g = 1; g < NCLS; g++) if (ct[(size_t) l * NCLS + g] > ct[(size_t) l * NCLS + b]) b = g; map[l] = b; }
244 double ok = 0, pt = 0, pok = 0;
245 for (int t = 0; t < C->ntok; t++) {
246 int r = map[lab[t]] == C->c[t];
247 ok += r;
248 if (poly[C->w[t]]) { pt++; pok += r; }
249 }
250 *m1 = ok / Nt; *sense = pt > 0 ? pok / pt : 0;
251 free(ct); free(rl); free(rg); free(map);
252}
253
254static void jt_labels (const corpus_t *C, const params_t *p, int *lab) {
255 static sent_state_t S;
256 for (int s = 0; s < C->ns; s++) {
257 int o = C->off[s];
258 estep(C, o, C->off[s + 1] - o, p, &S);
259 for (int t = 0; t < S.n; t++) { int b = 0; for (int z = 1; z < KJ; z++) if (S.q[t][z] > S.q[t][b]) b = z; lab[o + t] = b; }
260 }
261}
262
263static double attention_parse_f1 (const corpus_t *C, const params_t *p) {
264 static sent_state_t S;
265 int head[MAXN + 1];
266 long tp = 0, pred = 0, gold = 0;
267 for (int s = 0; s < C->ns; s++) {
268 int o = C->off[s], n = C->off[s + 1] - o, m = n + 1;
269 estep(C, o, n, p, &S);
270 for (int h = 0; h < m; h++) for (int d = 0; d < m; d++) sc[h][d] = NEG;
271 for (int d = 1; d < m; d++) sc[0][d] = 0;
272 for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++) {
273 double v = S.alpha[i][j] + S.alpha[j][i];
274 sc[i + 1][j + 1] = sc[j + 1][i + 1] = v > 0 ? v : NEG;
275 }
276 eisner(m, head);
277 for (int d = 1; d < m; d++) {
278 if (C->h[o + d - 1] >= 0) gold++;
279 if (head[d] > 0) {
280 pred++;
281 int a = head[d] - 1, b = d - 1;
282 if (C->h[o + b] == a || C->h[o + a] == b) tp++;
283 }
284 }
285 }
286 double pr = pred ? (double) tp / pred : 0, rc = gold ? (double) tp / gold : 0;
287 return pr + rc > 0 ? 2 * pr * rc / (pr + rc) : 0;
288}
289
290static double mi_parse_f1 (const corpus_t *tr, const corpus_t *ho, int gold_classes) {
291 arm_t a = { gold_classes ? "G" : "U", gold_classes ? NCLS : V, NULL, gold_classes };
292 pairs_t P = count_pairs(&a, tr);
293 long ev, acc;
294 double *S = hard_scores(&P, &ev, &acc);
295 arm_edge_t e = { &a, S };
296 link_t L = parse_eval(ho, edge_arm, &e, 1);
297 free(S); free_pairs(&P);
298 double pr = L.pred ? (double) L.tp / L.pred : 0, rc = L.gold ? (double) L.tp / L.gold : 0;
299 return pr + rc > 0 ? 2 * pr * rc / (pr + rc) : 0;
300}
301
302static void explain (FILE *f, const corpus_t *C, const params_t *p, const counts_t *unused) {
303 (void) unused;
304 static sent_state_t S;
305 for (int s = 0; s < C->ns; s++) { // first polysemous occurrence
306 int o = C->off[s], n = C->off[s + 1] - o, t = -1;
307 for (int k = 0; k < n && t < 0; k++) if (poly[C->w[o + k]]) t = k;
308 if (t < 0) continue;
309 estep(C, o, n, p, &S);
310 fprintf(f, "sentence %d, token %d (word %d, gold class %d)\n", s, t, C->w[o + t], C->c[o + t]);
311 for (int z = 0; z < KJ; z++) if (S.q[t][z] > 0.05) fprintf(f, " q[class %d] = %.3f (log pi %.3f)\n", z, S.q[t][z], p->lpi[(size_t) C->w[o + t] * KJ + z]);
312 for (int j = 0; j < n; j++) if (S.alpha[t][j] > 0.05) {
313 int bt = 0, bj = 0;
314 for (int z = 1; z < KJ; z++) { if (S.q[t][z] > S.q[t][bt]) bt = z; if (S.q[j][z] > S.q[j][bj]) bj = z; }
315 int a = t < j ? bt : bj, b = t < j ? bj : bt;
316 fprintf(f, " attends token %d (word %d) alpha %.3f ; class pair (%d,%d) PMI+ %.3f\n", j, C->w[o + j], S.alpha[t][j], a, b, p->pmi[a * KJ + b]);
317 }
318 return;
319 }
320}
321
322// ---------------------------------------------------------------- main
323
324static corpus_t shuffle_within (corpus_t C, uint64_t seed) {
325 rng_t r = { seed };
326 corpus_t S = C;
327 S.w = malloc(sizeof(int) * C.ntok); S.c = malloc(sizeof(int) * C.ntok);
328 memcpy(S.w, C.w, sizeof(int) * C.ntok); memcpy(S.c, C.c, sizeof(int) * C.ntok);
329 for (int s = 0; s < C.ns; s++) {
330 int o = C.off[s], n = C.off[s + 1] - o;
331 for (int i = n - 1; i > 0; i--) {
332 int j = rpick(&r, i + 1);
333 int t = S.w[o + i]; S.w[o + i] = S.w[o + j]; S.w[o + j] = t;
334 t = S.c[o + i]; S.c[o + i] = S.c[o + j]; S.c[o + j] = t;
335 }
336 }
337 return S;
338}
339
340int main (int argc, char **argv) {
341 if (argc < 2) { fprintf(stderr, "usage: jt <seed> [--tiers a,b] [--heldout N] [--K k] [--shuffle] [--explain f] [--log f]\n"); return 1; }
342 int seed = atoi(argv[1]), tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000, shuffle = 0;
343 const char *explain_path = NULL, *log_path = NULL;
344 for (int i = 2; i < argc; i++) {
345 if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]);
346 else if (!strcmp(argv[i], "--K") && i + 1 < argc) KJ = atoi(argv[++i]);
347 else if (!strcmp(argv[i], "--shuffle")) shuffle = 1;
348 else if (!strcmp(argv[i], "--explain") && i + 1 < argc) explain_path = argv[++i];
349 else if (!strcmp(argv[i], "--log") && i + 1 < argc) log_path = argv[++i];
350 else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) {
351 ntier = 0;
352 for (char *p = strtok(argv[++i], ","); p && ntier < 16; p = strtok(NULL, ",")) tiers[ntier++] = atoi(p);
353 }
354 }
355 if (KJ > 64) { fprintf(stderr, "K <= 64\n"); return 1; }
356 setvbuf(stdout, NULL, _IOLBF, 0);
357 build_world();
358 mark_polysemous();
359 int maxt = 0;
360 for (int i = 0; i < ntier; i++) if (tiers[i] > maxt) maxt = tiers[i];
361 corpus_t full = gen_corpus(1000003ULL * seed + 1, maxt), ho = gen_corpus(1000003ULL * seed + 2, nho);
362 if (shuffle) { full = shuffle_within(full, 99ULL * seed + 1); ho = shuffle_within(ho, 99ULL * seed + 2); }
363 const char *tag = shuffle ? "-SHUF" : "";
364 int *lab = malloc(sizeof(int) * ho.ntok);
365 params_t p; params_alloc(&p);
366
367 for (int ti = 0; ti < ntier; ti++) {
368 int tier = tiers[ti];
369 corpus_t tr = prefix(full, tier);
370 double vm, m1, se;
371
372 train_batch(&tr, 1000ULL * seed + 7, &p);
373 jt_labels(&ho, &p, lab); score_labels(&ho, lab, KJ, &vm, &m1, &se);
374 printf("V\t%d\t%d\t%d\tJT-BATCH%s\t%.5f\t%.5f\t%.5f\n", seed, tier, KJ, tag, vm, m1, se);
375 printf("L\t%d\t%d\t%d\tATTN%s\t%.5f\n", seed, tier, KJ, tag, attention_parse_f1(&ho, &p));
376 if (explain_path && ti == ntier - 1) { FILE *f = fopen(explain_path, "w"); if (f) { explain(f, &ho, &p, NULL); fclose(f); } }
377
378 // Continuous mode + audit (event log, replay, mutation).
379 int nb = (tier + BATCH - 1) / BATCH, nev = 0;
380 uint64_t *hs = malloc(sizeof(uint64_t) * nb), *hs2 = malloc(sizeof(uint64_t) * nb);
381 FILE *log = log_path && ti == ntier - 1 ? fopen(log_path, "w") : NULL;
382 train_cont(&tr, 1000ULL * seed + 7, &p, hs, &nev, NULL, -1, log);
383 if (log) fclose(log);
384 jt_labels(&ho, &p, lab); score_labels(&ho, lab, KJ, &vm, &m1, &se);
385 printf("V\t%d\t%d\t%d\tJT-CONT%s\t%.5f\t%.5f\t%.5f\n", seed, tier, KJ, tag, vm, m1, se);
386 int bad = train_cont(&tr, 1000ULL * seed + 7, &p, hs2, NULL, hs, -1, NULL);
387 printf("A\t%d\t%d\t%d\treplay\t%s\tevents=%d first_mismatch=%d\n", seed, tier, KJ, bad < 0 ? "PASS" : "FAIL", nev, bad);
388 int at = nev / 2;
389 bad = train_cont(&tr, 1000ULL * seed + 7, &p, hs2, NULL, hs, at, NULL);
390 printf("A\t%d\t%d\t%d\tmutation\t%s\taltered=%d detected_at=%d\n", seed, tier, KJ, bad == at ? "PASS" : "FAIL", at, bad);
391 free(hs); free(hs2);
392
393 if (KJ == 30 && !shuffle) { // baselines (KJ-independent)
394 int passes, *L = exchange(&tr, &passes);
395 for (int t = 0; t < ho.ntok; t++) lab[t] = L[ho.w[t]];
396 score_labels(&ho, lab, NCLS, &vm, &m1, &se); // exchange labels are < ac.c K = 30
397 printf("V\t%d\t%d\t%d\tTYPE\t%.5f\t%.5f\t%.5f\n", seed, tier, KJ, vm, m1, se);
398 free(L);
399 int *maj = malloc(sizeof(int) * V), *cnt = calloc((size_t) V * NCLS, sizeof(int));
400 for (int t = 0; t < tr.ntok; t++) cnt[(size_t) tr.w[t] * NCLS + tr.c[t]]++;
401 for (int w = 0; w < V; w++) { int b = primary[w]; for (int g = 0; g < NCLS; g++) if (cnt[(size_t) w * NCLS + g] > cnt[(size_t) w * NCLS + b]) b = g; maj[w] = b; }
402 for (int t = 0; t < ho.ntok; t++) lab[t] = maj[ho.w[t]];
403 score_labels(&ho, lab, NCLS, &vm, &m1, &se);
404 printf("V\t%d\t%d\t%d\tMAJ\t%.5f\t%.5f\t%.5f\n", seed, tier, KJ, vm, m1, se);
405 free(maj); free(cnt);
406 printf("L\t%d\t%d\t%d\tWORD-MI\t%.5f\n", seed, tier, KJ, mi_parse_f1(&tr, &ho, 0));
407 printf("L\t%d\t%d\t%d\tGOLD-MI\t%.5f\n", seed, tier, KJ, mi_parse_f1(&tr, &ho, 1));
408 }
409 }
410 params_free(&p);
411 return 0;
412}