| … | … | @@ -1,7 +1,8 @@ |
| 1 | 1 | // Graded ("fractal") connectors: see PREREG.md. Reuses the planted grammar, window-pair gate and Eisner |
| 2 | 2 | // parser of ../across-cluster/ac.c unchanged. |
| 3 | 3 | // |
| 4 | | // fractal <seed> [--tiers 3000,10000,30000] [--heldout N] |
| 4 | // fractal <seed> [--tiers 3000,10000,30000] [--heldout N] [--rule backoff|bestg] |
| 5 | // --rule picks the combined scorer that tiers GRADED and GRADED-R (run 1: backoff; run 2: bestg). |
| 5 | 6 | // Output rows: |
| 6 | 7 | // P <seed> <tier> <scorer> <F1> <P> <R> level-1 link F1 on held-out |
| 7 | 8 | // R <seed> <tier> <arm> <budget> <recall> <listlen> level-2 type recall at budget (budget -1 = all) |
| … | … | @@ -45,7 +46,18 @@ static void level_symbols (const corpus_t *C, const int *rmap, int lev, int *out |
| 45 | 46 | |
| 46 | 47 | // ---------------------------------------------------------------- per-level scores |
| 47 | 48 | |
| 48 | | typedef struct { int A[NLEV]; double *S[NLEV]; int *ho[NLEV]; } hier_t; |
| 49 | typedef struct { int A[NLEV]; double *S[NLEV], *G[NLEV]; int *ho[NLEV]; } hier_t; |
| 50 | |
| 51 | // G statistic of a pair (the significance gated_pmi tests), 0 if the pair fails the gate. |
| 52 | static double gate_g (const pairs_t *P, int a, int b) { |
| 53 | if (gated_pmi(P, a, b) <= NEG) return 0; |
| 54 | double n = P->M[(size_t) a * P->A + b], ra = P->R[a], cb = P->Cm[b], N = P->N; |
| 55 | double o[4] = { n, ra - n, cb - n, N - ra - cb + n }; |
| 56 | double e[4] = { ra * cb / N, ra * (N - cb) / N, (N - ra) * cb / N, (N - ra) * (N - cb) / N }; |
| 57 | double g = 0; |
| 58 | for (int k = 0; k < 4; k++) if (o[k] > 0) g += o[k] * log(o[k] / e[k]); |
| 59 | return 2 * g; |
| 60 | } |
| 49 | 61 | |
| 50 | 62 | static corpus_t with_symbols (const corpus_t *C, int *sym) { corpus_t S = *C; S.c = sym; return S; } |
| 51 | 63 | |
| … | … | @@ -59,6 +71,9 @@ static void build_hier (hier_t *H, const corpus_t *tr, const corpus_t *ho, const |
| 59 | 71 | pairs_t P = count_pairs(&a, &ts); |
| 60 | 72 | long ev, acc; |
| 61 | 73 | H->S[l] = hard_scores(&P, &ev, &acc); |
| 74 | H->G[l] = calloc((size_t) H->A[l] * H->A[l], sizeof(double)); |
| 75 | for (int a2 = 0; a2 < H->A[l]; a2++) for (int b2 = 0; b2 < H->A[l]; b2++) |
| 76 | if (H->S[l][(size_t) a2 * H->A[l] + b2] > NEG) H->G[l][(size_t) a2 * H->A[l] + b2] = gate_g(&P, a2, b2); |
| 62 | 77 | free_pairs(&P); |
| 63 | 78 | H->ho[l] = malloc(sizeof(int) * ho->ntok); |
| 64 | 79 | level_symbols(ho, rmap, l, H->ho[l]); |
| … | … | @@ -66,10 +81,10 @@ static void build_hier (hier_t *H, const corpus_t *tr, const corpus_t *ho, const |
| 66 | 81 | free(st); |
| 67 | 82 | } |
| 68 | 83 | |
| 69 | | static void free_hier (hier_t *H) { for (int l = 0; l < NLEV; l++) { free(H->S[l]); free(H->ho[l]); } } |
| 84 | static void free_hier (hier_t *H) { for (int l = 0; l < NLEV; l++) { free(H->S[l]); free(H->G[l]); free(H->ho[l]); } } |
| 70 | 85 | |
| 71 | 86 | // Edge scorers. ctx = { hier, symbols-per-level for the corpus being parsed, mode }. |
| 72 | | typedef struct { hier_t *H; int *sym[NLEV]; int mode; } edge_ctx_t; // mode 0..3 = single level, 4 = backoff |
| 87 | typedef struct { hier_t *H; int *sym[NLEV]; int mode; } edge_ctx_t; // mode 0..3 = single level, 4 = backoff, 5 = best-G |
| 73 | 88 | |
| 74 | 89 | static double edge_level (const corpus_t *C, int o, int i, int j, void *vctx, int window) { |
| 75 | 90 | (void) C; (void) window; |
| … | … | @@ -78,11 +93,19 @@ static double edge_level (const corpus_t *C, int o, int i, int j, void *vctx, in |
| 78 | 93 | int l = e->mode; |
| 79 | 94 | return e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]]; |
| 80 | 95 | } |
| 81 | | for (int l = 0; l < NLEV; l++) { // backoff: the finest level that passes the gate |
| 82 | | double s = e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]]; |
| 83 | | if (s > NEG) return s; |
| 96 | if (e->mode == NLEV) { |
| 97 | for (int l = 0; l < NLEV; l++) { // backoff: the finest level that passes the gate |
| 98 | double s = e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]]; |
| 99 | if (s > NEG) return s; |
| 100 | } |
| 101 | return NEG; |
| 102 | } |
| 103 | double best = NEG, bg = 0; // best-G: the passing level with the largest G statistic (PREREG-2) |
| 104 | for (int l = 0; l < NLEV; l++) { |
| 105 | size_t k = (size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]; |
| 106 | if (e->H->S[l][k] > NEG && e->H->G[l][k] > bg) { bg = e->H->G[l][k]; best = e->H->S[l][k]; } |
| 84 | 107 | } |
| 85 | | return NEG; |
| 108 | return best; |
| 86 | 109 | } |
| 87 | 110 | |
| 88 | 111 | // Parse every sentence; head[t] = in-sentence offset of head, or -1 (as level2 parse_all). |
| … | … | @@ -171,9 +194,10 @@ static const long fbudgets[] = { 300, 1000, 3000, 10000, -1 }; |
| 171 | 194 | int main (int argc, char **argv) { |
| 172 | 195 | if (argc < 2) { fprintf(stderr, "usage: fractal <seed> [--tiers a,b] [--heldout N]\n"); return 1; } |
| 173 | 196 | int seed = atoi(argv[1]); |
| 174 | | int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000; |
| 197 | int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000, rule = NLEV; // rule: NLEV backoff, NLEV + 1 best-G |
| 175 | 198 | for (int i = 2; i < argc; i++) { |
| 176 | 199 | if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]); |
| 200 | else if (!strcmp(argv[i], "--rule") && i + 1 < argc) { i++; rule = !strcmp(argv[i], "bestg") ? NLEV + 1 : NLEV; } |
| 177 | 201 | else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) { |
| 178 | 202 | ntier = 0; |
| 179 | 203 | for (char *p = strtok(argv[++i], ","); p && ntier < 16; p = strtok(NULL, ",")) tiers[ntier++] = atoi(p); |
| … | … | @@ -204,8 +228,8 @@ int main (int argc, char **argv) { |
| 204 | 228 | build_hier(&Hr, &tr, &ho, rmap); |
| 205 | 229 | |
| 206 | 230 | // Part 1: held-out link F1 per scorer. |
| 207 | | const char *pname[5] = { "W", "G", "S1", "S2", "BACKOFF" }; |
| 208 | | for (int mode = 0; mode <= NLEV; mode++) { |
| 231 | const char *pname[6] = { "W", "G", "S1", "S2", "BACKOFF", "BEST-G" }; |
| 232 | for (int mode = 0; mode <= NLEV + 1; mode++) { |
| 209 | 233 | edge_ctx_t e = { &Hg, { Hg.ho[0], Hg.ho[1], Hg.ho[2], Hg.ho[3] }, mode }; |
| 210 | 234 | link_t L = parse_eval(&ho, edge_level, &e, 1); |
| 211 | 235 | double p = L.pred ? (double) L.tp / L.pred : 0, r = L.gold ? (double) L.tp / L.gold : 0; |
| … | … | @@ -219,9 +243,9 @@ int main (int argc, char **argv) { |
| 219 | 243 | symr[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, rmap, l, symr[l]); |
| 220 | 244 | } |
| 221 | 245 | int *hb = malloc(sizeof(int) * tr.ntok), *hgp = malloc(sizeof(int) * tr.ntok), *hrb = malloc(sizeof(int) * tr.ntok); |
| 222 | | edge_ctx_t eb = { &Hg, { sym[0], sym[1], sym[2], sym[3] }, NLEV }; |
| 246 | edge_ctx_t eb = { &Hg, { sym[0], sym[1], sym[2], sym[3] }, rule }; |
| 223 | 247 | edge_ctx_t eg = { &Hg, { sym[0], sym[1], sym[2], sym[3] }, 1 }; |
| 224 | | edge_ctx_t er = { &Hr, { symr[0], symr[1], symr[2], symr[3] }, NLEV }; |
| 248 | edge_ctx_t er = { &Hr, { symr[0], symr[1], symr[2], symr[3] }, rule }; |
| 225 | 249 | parse_heads(&tr, &eb, hb); parse_heads(&tr, &eg, hgp); parse_heads(&tr, &er, hrb); |
| 226 | 250 | |
| 227 | 251 | memset(types, 0, sizeof(type_t) * (tmask2 + 1)); |