| … | … | @@ -114,6 +114,108 @@ static int complete (const int *head, int o, int n, int i, int j) { |
| 114 | 114 | return 1; |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | // ---------------------------------------------------------------- Test A-U (PREREG-U.md): undirected parses |
| 118 | |
| 119 | #define MAXL 24 |
| 120 | typedef struct { int *lnk, *nl; } parse_t; // per token: in-sentence offsets of the tokens it is linked to |
| 121 | |
| 122 | static parse_t parse_alloc (int ntok) { parse_t P = { malloc(sizeof(int) * ntok * MAXL), malloc(sizeof(int) * ntok) }; return P; } |
| 123 | static void parse_clear (parse_t *P, int o, int n) { for (int t = 0; t < n; t++) P->nl[o + t] = 0; } |
| 124 | static int linked (const parse_t *P, int o, int a, int b) { for (int k = 0; k < P->nl[o + a]; k++) if (P->lnk[(o + a) * MAXL + k] == b) return 1; return 0; } |
| 125 | static void add_link (parse_t *P, int o, int a, int b) { |
| 126 | if (a == b || linked(P, o, a, b) || P->nl[o + a] >= MAXL || P->nl[o + b] >= MAXL) return; |
| 127 | P->lnk[(o + a) * MAXL + P->nl[o + a]++] = b; P->lnk[(o + b) * MAXL + P->nl[o + b]++] = a; |
| 128 | } |
| 129 | |
| 130 | static void links_from_heads (const corpus_t *C, const int *head, parse_t *P) { |
| 131 | for (int s = 0; s < C->ns; s++) { |
| 132 | int o = C->off[s], n = C->off[s + 1] - o; |
| 133 | parse_clear(P, o, n); |
| 134 | for (int t = 0; t < n; t++) if (head[o + t] >= 0) add_link(P, o, t, head[o + t]); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // Ungated ordered-pair MI over window counts; pairs never counted get -10. |
| 139 | static double *pair_mi (const pairs_t *P) { |
| 140 | double *M = malloc(sizeof(double) * P->A * P->A); |
| 141 | for (int a = 0; a < P->A; a++) for (int b = 0; b < P->A; b++) { |
| 142 | double n = P->M[(size_t) a * P->A + b]; |
| 143 | M[(size_t) a * P->A + b] = n > 0 ? log2(n * P->N / (P->R[a] * P->Cm[b])) : -10; |
| 144 | } |
| 145 | return M; |
| 146 | } |
| 147 | |
| 148 | // MST: projective spanning tree (Eisner, root edges -1e6 so one root child); MPG: + non-crossing MI > 0 links. |
| 149 | typedef struct { int i, j; double v; } cl_t; |
| 150 | static int cl_cmp (const void *a, const void *b) { |
| 151 | const cl_t *x = a, *y = b; |
| 152 | if (x->v != y->v) return x->v > y->v ? -1 : 1; |
| 153 | return x->i != y->i ? x->i - y->i : x->j - y->j; |
| 154 | } |
| 155 | static void parse_u (const corpus_t *S, const double *MI, int A, int mpg, parse_t *P) { |
| 156 | int hd[MAXN + 1]; |
| 157 | static cl_t cand[MAXN * W]; |
| 158 | for (int s = 0; s < S->ns; s++) { |
| 159 | int o = S->off[s], n = S->off[s + 1] - o, m = n + 1, nc = 0; |
| 160 | for (int h = 0; h < m; h++) for (int d = 0; d < m; d++) sc[h][d] = NEG; |
| 161 | for (int d = 1; d < m; d++) sc[0][d] = -1e6; |
| 162 | for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++) |
| 163 | sc[i + 1][j + 1] = sc[j + 1][i + 1] = MI[(size_t) S->c[o + i] * A + S->c[o + j]]; |
| 164 | eisner(m, hd); |
| 165 | parse_clear(P, o, n); |
| 166 | for (int d = 1; d < m; d++) if (hd[d] > 0) add_link(P, o, d - 1, hd[d] - 1); |
| 167 | if (!mpg) continue; |
| 168 | for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++) { |
| 169 | double v = MI[(size_t) S->c[o + i] * A + S->c[o + j]]; |
| 170 | if (v > 0 && !linked(P, o, i, j)) cand[nc++] = (cl_t) { i, j, v }; |
| 171 | } |
| 172 | qsort(cand, nc, sizeof(cl_t), cl_cmp); |
| 173 | for (int c = 0; c < nc; c++) { |
| 174 | int i = cand[c].i, j = cand[c].j, ok = 1; |
| 175 | for (int a = 0; a < n && ok; a++) for (int k = 0; k < P->nl[o + a] && ok; k++) { |
| 176 | int b = P->lnk[(o + a) * MAXL + k]; |
| 177 | if (a < b && ((a < i && i < b && b < j) || (i < a && a < j && j < b))) ok = 0; |
| 178 | } |
| 179 | if (ok) add_link(P, o, i, j); |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | // uconn: links inside [i, j] connect it. ucomp: uconn and at most one link leaves the span. |
| 185 | static int uconn (const parse_t *P, int o, int i, int j) { |
| 186 | int seen[MAXN] = { 0 }, stack[MAXN], sp = 0, cnt = 1; |
| 187 | seen[i] = 1; stack[sp++] = i; |
| 188 | while (sp) { |
| 189 | int a = stack[--sp]; |
| 190 | for (int k = 0; k < P->nl[o + a]; k++) { |
| 191 | int b = P->lnk[(o + a) * MAXL + k]; |
| 192 | if (b >= i && b <= j && !seen[b]) { seen[b] = 1; cnt++; stack[sp++] = b; } |
| 193 | } |
| 194 | } |
| 195 | return cnt == j - i + 1; |
| 196 | } |
| 197 | static int ucomp (const parse_t *P, int o, int i, int j) { |
| 198 | if (!uconn(P, o, i, j)) return 0; |
| 199 | int cross = 0; |
| 200 | for (int a = i; a <= j; a++) for (int k = 0; k < P->nl[o + a]; k++) { int b = P->lnk[(o + a) * MAXL + k]; cross += b < i || b > j; } |
| 201 | return cross <= 1; |
| 202 | } |
| 203 | |
| 204 | // Undirected link precision / recall / F1 of P on C against the gold tree. |
| 205 | static void link_prf (const corpus_t *C, const parse_t *P, double *pr, double *rc, double *f1) { |
| 206 | long pred = 0, gold = 0, tp = 0; |
| 207 | for (int s = 0; s < C->ns; s++) { |
| 208 | int o = C->off[s], n = C->off[s + 1] - o; |
| 209 | for (int t = 0; t < n; t++) { |
| 210 | for (int k = 0; k < P->nl[o + t]; k++) pred += P->lnk[(o + t) * MAXL + k] > t; |
| 211 | int h = C->h[o + t]; |
| 212 | if (h >= 0) { gold++; tp += linked(P, o, t, h); } |
| 213 | } |
| 214 | } |
| 215 | *pr = pred ? (double) tp / pred : 0; *rc = gold ? (double) tp / gold : 0; |
| 216 | *f1 = *pr + *rc > 0 ? 2 * *pr * *rc / (*pr + *rc) : 0; |
| 217 | } |
| 218 | |
| 117 | 219 | // ---------------------------------------------------------------- span-type table |
| 118 | 220 | |
| 119 | 221 | typedef struct { uint16_t s[LMAX]; uint8_t len, used; uint32_t cnum, hard, first; float soft; } span_t; // hard/first/soft: Test A-soft |
| … | … | @@ -192,9 +294,14 @@ static int gold_spans (const corpus_t *C, int s, br_t *g, int *n_le6) { |
| 192 | 294 | |
| 193 | 295 | // ---------------------------------------------------------------- one arm (Test A: the search space) |
| 194 | 296 | |
| 195 | | enum { RULE_ALL, RULE_CONN, RULE_COMP }; |
| 297 | enum { RULE_ALL, RULE_CONN, RULE_COMP, RULE_UCONN, RULE_UCOMP }; |
| 196 | 298 | |
| 299 | static const parse_t *u_tr, *u_ho; // undirected parses for RULE_UCONN / RULE_UCOMP (set by the caller) |
| 197 | 300 | static int rule_ok (int rule, const int *head, int o, int n, int i, int j) { |
| 301 | if (rule == RULE_UCONN || rule == RULE_UCOMP) { |
| 302 | const parse_t *P = head == (const int *) u_tr ? u_tr : u_ho; |
| 303 | return rule == RULE_UCONN ? uconn(P, o, i, j) : ucomp(P, o, i, j); |
| 304 | } |
| 198 | 305 | return rule == RULE_ALL || (rule == RULE_CONN ? connected(head, o, i, j) : complete(head, o, n, i, j)); |
| 199 | 306 | } |
| 200 | 307 | |
| … | … | @@ -354,16 +461,17 @@ static void run_soft (int seed, int tier, const char *src, const corpus_t *tr, c |
| 354 | 461 | // ---------------------------------------------------------------- main |
| 355 | 462 | |
| 356 | 463 | int main (int argc, char **argv) { |
| 357 | | if (argc < 2) { fprintf(stderr, "usage: l2 <seed> [--tiers a,b] [--heldout N] [--mut-drop] [--jt prefix] [--soft [--soft-scale s]]\n"); return 1; } |
| 464 | if (argc < 2) { fprintf(stderr, "usage: l2 <seed> [--tiers a,b] [--heldout N] [--mut-drop] [--jt prefix] [--soft [--soft-scale s]] [--undirected]\n"); return 1; } |
| 358 | 465 | int seed = atoi(argv[1]); |
| 359 | 466 | int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000; |
| 360 | 467 | const char *jt_prefix = NULL; |
| 361 | | int soft = 0; double soft_scale = 1.0; |
| 468 | int soft = 0, undirected = 0; double soft_scale = 1.0; |
| 362 | 469 | for (int i = 2; i < argc; i++) { |
| 363 | 470 | if (!strcmp(argv[i], "--mut-drop")) mut_drop = 1; |
| 364 | 471 | else if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]); |
| 365 | 472 | else if (!strcmp(argv[i], "--jt") && i + 1 < argc) jt_prefix = argv[++i]; |
| 366 | 473 | else if (!strcmp(argv[i], "--soft")) soft = 1; |
| 474 | else if (!strcmp(argv[i], "--undirected")) undirected = 1; |
| 367 | 475 | else if (!strcmp(argv[i], "--soft-scale") && i + 1 < argc) soft_scale = atof(argv[++i]); |
| 368 | 476 | else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) { |
| 369 | 477 | ntier = 0; |
| … | … | @@ -409,6 +517,22 @@ int main (int argc, char **argv) { |
| 409 | 517 | if (!ok) return 2; |
| 410 | 518 | } |
| 411 | 519 | |
| 520 | // C5-U (PREREG-U.md): with the gold tree as an undirected parse, every gold span is uconn and ucomp. |
| 521 | parse_t Ptr = { 0 }, Pho = { 0 }; |
| 522 | if (undirected) { |
| 523 | Ptr = parse_alloc(full.ntok); Pho = parse_alloc(ho.ntok); |
| 524 | links_from_heads(&ho, ho.h, &Pho); |
| 525 | long g = 0, okc = 0, okp = 0; |
| 526 | br_t gb[MAXN]; |
| 527 | for (int s = 0; s < ho.ns; s++) { |
| 528 | int o = ho.off[s], le6, ng = gold_spans(&ho, s, gb, &le6); |
| 529 | for (int b = 0; b < ng; b++) { g++; okc += uconn(&Pho, o, gb[b].i, gb[b].j); okp += ucomp(&Pho, o, gb[b].i, gb[b].j); } |
| 530 | } |
| 531 | int ok = okc == g && okp == g; |
| 532 | printf("GUARD\tC5-U\t%s\tgold=%ld\tuconn=%ld\tucomp=%ld\n", ok ? "PASS" : "FAIL", g, okc, okp); |
| 533 | if (!ok) return 2; |
| 534 | } |
| 535 | |
| 412 | 536 | int *str = malloc(sizeof(int) * full.ntok), *sho = malloc(sizeof(int) * ho.ntok); |
| 413 | 537 | int *htr = malloc(sizeof(int) * full.ntok), *hho = malloc(sizeof(int) * ho.ntok); |
| 414 | 538 | int *Rmap = malloc(sizeof(int) * V), *perm = malloc(sizeof(int) * V); |
| … | … | @@ -444,6 +568,37 @@ int main (int argc, char **argv) { |
| 444 | 568 | snprintf(nm, sizeof nm, "M-%s-comp", src_name[x]); |
| 445 | 569 | run_arm(seed, tier, nm, RULE_COMP, &trs, &hos, &ho, htr, hho); |
| 446 | 570 | } |
| 571 | |
| 572 | // PREREG-U.md: undirected FOREST / MST / MPG parses, undirected unit rules, link F1 (sources G, L, R). |
| 573 | if (undirected) { |
| 574 | static const int usrc[3] = { SRC_G, SRC_L, SRC_R }; |
| 575 | static const char *pname[3] = { "FOREST", "MST", "MPG" }; |
| 576 | u_tr = &Ptr; u_ho = &Pho; |
| 577 | for (int xi = 0; xi < 3; xi++) { |
| 578 | int x = usrc[xi]; |
| 579 | int A = layer1_symbols(x, &tr, seed, Lmap, Rmap, str); |
| 580 | layer1_symbols(x, &ho, seed, Lmap, Rmap, sho); |
| 581 | corpus_t trs = with_symbols(&tr, str), hos = with_symbols(&ho, sho); |
| 582 | arm_t a = { "l1", A, NULL, 1 }; |
| 583 | pairs_t P = count_pairs(&a, &trs); |
| 584 | double *MI = pair_mi(&P); |
| 585 | free_pairs(&P); |
| 586 | for (int pi = 0; pi < 3; pi++) { |
| 587 | if (pi == 0) { layer1_parse(&trs, &hos, A, htr, hho); links_from_heads(&trs, htr, &Ptr); links_from_heads(&hos, hho, &Pho); } |
| 588 | else { parse_u(&trs, MI, A, pi == 2, &Ptr); parse_u(&hos, MI, A, pi == 2, &Pho); } |
| 589 | double pr, rc, f1; |
| 590 | link_prf(&hos, &Pho, &pr, &rc, &f1); |
| 591 | // link_prf reads gold heads from hos.h, which is the held-out gold tree (with_symbols only replaces c[]). |
| 592 | printf("P\t%d\t%d\t%s\t%s\t%.5f\t%.5f\t%.5f\n", seed, tier, src_name[x], pname[pi], pr, rc, f1); |
| 593 | char nm[32]; |
| 594 | snprintf(nm, sizeof nm, "M-%s-uconn-%s", src_name[x], pname[pi]); |
| 595 | run_arm(seed, tier, nm, RULE_UCONN, &trs, &hos, &ho, (const int *) u_tr, (const int *) u_ho); |
| 596 | snprintf(nm, sizeof nm, "M-%s-ucomp-%s", src_name[x], pname[pi]); |
| 597 | run_arm(seed, tier, nm, RULE_UCOMP, &trs, &hos, &ho, (const int *) u_tr, (const int *) u_ho); |
| 598 | } |
| 599 | free(MI); |
| 600 | } |
| 601 | } |
| 447 | 602 | free(Lmap); |
| 448 | 603 | |
| 449 | 604 | // PREREG-soft.md: rank span types by assembly-ness over KPARSE perturbed parses (sources G and L). |