| … | … | @@ -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 | |
| 19 | static int KJ = 30; |
| 20 | |
| 21 | // ---------------------------------------------------------------- parameters (all counts) |
| 22 | |
| 23 | typedef 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 | |
| 29 | typedef struct { double *lpi, *lphL, *lphR, *pmi; } params_t; // logs / PMI+ derived from counts |
| 30 | |
| 31 | static 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 | } |
| 36 | static 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 | } |
| 40 | static void counts_free (counts_t *c) { free(c->pi); free(c->phL); free(c->phR); free(c->N); } |
| 41 | |
| 42 | static 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 | } |
| 46 | static 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. |
| 49 | static 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 | |
| 74 | typedef 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. |
| 77 | static 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. |
| 132 | static 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 | |
| 151 | static 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 | |
| 157 | static 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 | |
| 164 | static 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 | |
| 168 | static 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 | |
| 174 | static 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. |
| 188 | static 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 | |
| 217 | static int *poly; // word has >= 2 gold classes |
| 218 | |
| 219 | static 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. |
| 228 | static 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 | |
| 254 | static 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 | |
| 263 | static 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 | |
| 290 | static 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 | |
| 302 | static 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 | |
| 324 | static 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 | |
| 340 | int 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 | } |