| … | … | @@ -0,0 +1,285 @@ |
| 1 | // Graded ("fractal") connectors: see PREREG.md. Reuses the planted grammar, window-pair gate and Eisner |
| 2 | // parser of ../across-cluster/ac.c unchanged. |
| 3 | // |
| 4 | // fractal <seed> [--tiers 3000,10000,30000] [--heldout N] |
| 5 | // Output rows: |
| 6 | // P <seed> <tier> <scorer> <F1> <P> <R> level-1 link F1 on held-out |
| 7 | // R <seed> <tier> <arm> <budget> <recall> <listlen> level-2 type recall at budget (budget -1 = all) |
| 8 | |
| 9 | #define main ac_main |
| 10 | #include "../across-cluster/ac.c" |
| 11 | #undef main |
| 12 | |
| 13 | #define NLEV 4 // W, G, S1, S2 |
| 14 | #define LMAX 6 |
| 15 | |
| 16 | // ---------------------------------------------------------------- hierarchy (level2/l2.c merge_class) |
| 17 | |
| 18 | static int is_noun (int c) { return c <= N_THING_PL; } |
| 19 | static int is_verb (int c) { return c >= V_I_SG && c <= V_SAY_PL; } |
| 20 | static int is_func (int c) { return (c >= DET_SG && c <= DET_ANY) || c == PRON_SG || c == PRON_PL || c == PREP_LOC || c == PREP_WITH || c == COMP; } |
| 21 | |
| 22 | static int merge_class (int c, int level) { |
| 23 | if (level >= 1) { |
| 24 | if (is_noun(c) || is_verb(c)) c -= (c - (is_noun(c) ? N_ANIM_SG : V_I_SG)) % 2; |
| 25 | if (c >= DET_SG && c <= DET_ANY) c = DET_SG; |
| 26 | if (c == PRON_PL) c = PRON_SG; |
| 27 | } |
| 28 | if (level >= 2) { |
| 29 | if (is_noun(c)) c = N_ANIM_SG; |
| 30 | if (is_verb(c)) c = V_I_SG; |
| 31 | } |
| 32 | if (level >= 3 && is_func(c)) c = DET_SG; |
| 33 | return c; |
| 34 | } |
| 35 | |
| 36 | // Level symbols for every token: level 0 = word, 1 = class, 2/3 = coarser. `cls` gives the level-1 class |
| 37 | // of a token (gold: the token's class; random: Rmap of its word); levels 2-3 merge it like S1/S2. |
| 38 | static void level_symbols (const corpus_t *C, const int *rmap, int lev, int *out) { |
| 39 | for (int t = 0; t < C->ntok; t++) { |
| 40 | if (lev == 0) { out[t] = C->w[t]; continue; } |
| 41 | int c = rmap ? rmap[C->w[t]] : C->c[t]; |
| 42 | out[t] = lev == 1 ? c : merge_class(c, lev - 1); // lev 2 = S1, lev 3 = S2 |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | // ---------------------------------------------------------------- per-level scores |
| 47 | |
| 48 | typedef struct { int A[NLEV]; double *S[NLEV]; int *ho[NLEV]; } hier_t; |
| 49 | |
| 50 | static corpus_t with_symbols (const corpus_t *C, int *sym) { corpus_t S = *C; S.c = sym; return S; } |
| 51 | |
| 52 | static void build_hier (hier_t *H, const corpus_t *tr, const corpus_t *ho, const int *rmap) { |
| 53 | int *st = malloc(sizeof(int) * tr->ntok); |
| 54 | for (int l = 0; l < NLEV; l++) { |
| 55 | H->A[l] = l == 0 ? V : NCLS; |
| 56 | level_symbols(tr, rmap, l, st); |
| 57 | corpus_t ts = with_symbols(tr, st); |
| 58 | arm_t a = { "lev", H->A[l], NULL, 1 }; |
| 59 | pairs_t P = count_pairs(&a, &ts); |
| 60 | long ev, acc; |
| 61 | H->S[l] = hard_scores(&P, &ev, &acc); |
| 62 | free_pairs(&P); |
| 63 | H->ho[l] = malloc(sizeof(int) * ho->ntok); |
| 64 | level_symbols(ho, rmap, l, H->ho[l]); |
| 65 | } |
| 66 | free(st); |
| 67 | } |
| 68 | |
| 69 | static void free_hier (hier_t *H) { for (int l = 0; l < NLEV; l++) { free(H->S[l]); free(H->ho[l]); } } |
| 70 | |
| 71 | // 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 |
| 73 | |
| 74 | static double edge_level (const corpus_t *C, int o, int i, int j, void *vctx, int window) { |
| 75 | (void) C; (void) window; |
| 76 | edge_ctx_t *e = vctx; |
| 77 | if (e->mode < NLEV) { |
| 78 | int l = e->mode; |
| 79 | return e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]]; |
| 80 | } |
| 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; |
| 84 | } |
| 85 | return NEG; |
| 86 | } |
| 87 | |
| 88 | // Parse every sentence; head[t] = in-sentence offset of head, or -1 (as level2 parse_all). |
| 89 | static void parse_heads (const corpus_t *C, edge_ctx_t *e, int *head) { |
| 90 | int hd[MAXN + 1]; |
| 91 | for (int s = 0; s < C->ns; s++) { |
| 92 | int o = C->off[s], n = C->off[s + 1] - o, m = n + 1; |
| 93 | for (int h = 0; h < m; h++) for (int d = 0; d < m; d++) sc[h][d] = NEG; |
| 94 | for (int d = 1; d < m; d++) sc[0][d] = 0; |
| 95 | for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++) |
| 96 | sc[i + 1][j + 1] = sc[j + 1][i + 1] = edge_level(C, o, i, j, e, 1); |
| 97 | eisner(m, hd); |
| 98 | for (int d = 1; d < m; d++) head[o + d - 1] = hd[d] - 1; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | static int connected (const int *head, int o, int i, int j) { |
| 103 | int e = 0; |
| 104 | for (int k = i; k <= j; k++) { int h = head[o + k]; if (h >= i && h <= j) e++; } |
| 105 | return e == j - i; |
| 106 | } |
| 107 | |
| 108 | static int complete (const int *head, int o, int n, int i, int j) { |
| 109 | if (!connected(head, o, i, j)) return 0; |
| 110 | for (int k = 0; k < n; k++) { if (k >= i && k <= j) continue; int h = head[o + k]; if (h >= i && h <= j) return 0; } |
| 111 | return 1; |
| 112 | } |
| 113 | |
| 114 | static int structure (const int *head, int o, int n, int i, int j) { return complete(head, o, n, i, j) ? 2 : connected(head, o, i, j) ? 1 : 0; } |
| 115 | |
| 116 | // ---------------------------------------------------------------- level-2 type table (G-class sequences) |
| 117 | |
| 118 | #define NARM 3 // tiering sources: 0 = backoff (gold hierarchy), 1 = G-level parse, 2 = backoff (random hierarchy) |
| 119 | |
| 120 | typedef struct { uint64_t key; double n, ncomp_g; int tier[NARM]; int used; } type_t; |
| 121 | static type_t *types; |
| 122 | static size_t tmask2; |
| 123 | |
| 124 | static uint64_t type_key (const int *cls, int i, int len) { |
| 125 | uint64_t k = (uint64_t) len; |
| 126 | for (int q = 0; q < len; q++) k = k * 32 + (uint64_t) cls[i + q]; |
| 127 | return k; |
| 128 | } |
| 129 | |
| 130 | static type_t *type_get (uint64_t key, int create) { |
| 131 | size_t i = (size_t) (key * 0x9e3779b97f4a7c15ULL) & tmask2; |
| 132 | while (types[i].used) { |
| 133 | if (types[i].key == key) return &types[i]; |
| 134 | i = (i + 1) & tmask2; |
| 135 | } |
| 136 | if (!create) return NULL; |
| 137 | memset(&types[i], 0, sizeof(type_t)); |
| 138 | types[i].used = 1; types[i].key = key; |
| 139 | return &types[i]; |
| 140 | } |
| 141 | |
| 142 | static int gold_spans (const corpus_t *C, int s, int *gi, int *gj) { |
| 143 | int o = C->off[s], n = C->off[s + 1] - o, lo[MAXN], hi[MAXN], k = 0; |
| 144 | for (int t = 0; t < n; t++) lo[t] = hi[t] = t; |
| 145 | for (int t = 0; t < n; t++) for (int h = C->h[o + t]; h >= 0; h = C->h[o + h]) { |
| 146 | if (t < lo[h]) lo[h] = t; |
| 147 | if (t > hi[h]) hi[h] = t; |
| 148 | } |
| 149 | for (int t = 0; t < n; t++) { |
| 150 | int c = C->c[o + t]; |
| 151 | if (!(is_noun(c) || c == PREP_LOC || c == PREP_WITH) || hi[t] == lo[t] || hi[t] - lo[t] + 1 > LMAX) continue; |
| 152 | gi[k] = lo[t]; gj[k] = hi[t]; k++; |
| 153 | } |
| 154 | return k; |
| 155 | } |
| 156 | |
| 157 | typedef struct { type_t *t; double k1, k2; } rank_t; |
| 158 | |
| 159 | static int by_key (const void *a, const void *b) { |
| 160 | const rank_t *x = a, *y = b; |
| 161 | if (x->k1 != y->k1) return x->k1 > y->k1 ? -1 : 1; |
| 162 | if (x->k2 != y->k2) return x->k2 > y->k2 ? -1 : 1; |
| 163 | return (x->t->key > y->t->key) - (x->t->key < y->t->key); |
| 164 | } |
| 165 | |
| 166 | static const long fbudgets[] = { 300, 1000, 3000, 10000, -1 }; |
| 167 | #define NBUD 5 |
| 168 | |
| 169 | // ---------------------------------------------------------------- main |
| 170 | |
| 171 | int main (int argc, char **argv) { |
| 172 | if (argc < 2) { fprintf(stderr, "usage: fractal <seed> [--tiers a,b] [--heldout N]\n"); return 1; } |
| 173 | int seed = atoi(argv[1]); |
| 174 | int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000; |
| 175 | for (int i = 2; i < argc; i++) { |
| 176 | if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]); |
| 177 | else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) { |
| 178 | ntier = 0; |
| 179 | for (char *p = strtok(argv[++i], ","); p && ntier < 16; p = strtok(NULL, ",")) tiers[ntier++] = atoi(p); |
| 180 | } |
| 181 | } |
| 182 | setvbuf(stdout, NULL, _IOLBF, 0); |
| 183 | build_world(); |
| 184 | int maxt = 0; |
| 185 | for (int i = 0; i < ntier; i++) if (tiers[i] > maxt) maxt = tiers[i]; |
| 186 | corpus_t full = gen_corpus(1000003ULL * seed + 1, maxt); |
| 187 | corpus_t ho = gen_corpus(1000003ULL * seed + 2, nho); |
| 188 | |
| 189 | // Random hierarchy: words relabelled into classes with the gold class sizes (as level2's R). |
| 190 | int *rmap = malloc(sizeof(int) * V), *perm = malloc(sizeof(int) * V); |
| 191 | rng_t rr = { 777ULL * seed + 1 }; |
| 192 | for (int w = 0; w < V; w++) perm[w] = w; |
| 193 | for (int w = V - 1; w > 0; w--) { int j = rpick(&rr, w + 1), t = perm[w]; perm[w] = perm[j]; perm[j] = t; } |
| 194 | for (int w = 0; w < V; w++) rmap[w] = primary[perm[w]]; |
| 195 | |
| 196 | tmask2 = (1 << 20) - 1; |
| 197 | types = malloc(sizeof(type_t) * (tmask2 + 1)); |
| 198 | |
| 199 | for (int ti = 0; ti < ntier; ti++) { |
| 200 | int tier = tiers[ti]; |
| 201 | corpus_t tr = prefix(full, tier); |
| 202 | hier_t Hg, Hr; |
| 203 | build_hier(&Hg, &tr, &ho, NULL); |
| 204 | build_hier(&Hr, &tr, &ho, rmap); |
| 205 | |
| 206 | // 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++) { |
| 209 | edge_ctx_t e = { &Hg, { Hg.ho[0], Hg.ho[1], Hg.ho[2], Hg.ho[3] }, mode }; |
| 210 | link_t L = parse_eval(&ho, edge_level, &e, 1); |
| 211 | double p = L.pred ? (double) L.tp / L.pred : 0, r = L.gold ? (double) L.tp / L.gold : 0; |
| 212 | printf("P\t%d\t%d\t%s\t%.5f\t%.5f\t%.5f\n", seed, tier, pname[mode], p + r > 0 ? 2 * p * r / (p + r) : 0, p, r); |
| 213 | } |
| 214 | |
| 215 | // Part 2: parse the training prefix three ways, collect G-class span types with their best structure. |
| 216 | int *sym[NLEV], *symr[NLEV]; |
| 217 | for (int l = 0; l < NLEV; l++) { |
| 218 | sym[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, NULL, l, sym[l]); |
| 219 | symr[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, rmap, l, symr[l]); |
| 220 | } |
| 221 | 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 }; |
| 223 | 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 }; |
| 225 | parse_heads(&tr, &eb, hb); parse_heads(&tr, &eg, hgp); parse_heads(&tr, &er, hrb); |
| 226 | |
| 227 | memset(types, 0, sizeof(type_t) * (tmask2 + 1)); |
| 228 | for (int s = 0; s < tr.ns; s++) { |
| 229 | int o = tr.off[s], n = tr.off[s + 1] - o; |
| 230 | for (int i = 0; i < n; i++) for (int len = 2; len <= LMAX && i + len <= n; len++) { |
| 231 | int j = i + len - 1; |
| 232 | type_t *t = type_get(type_key(tr.c + o, i, len), 1); |
| 233 | t->n += 1; |
| 234 | int sb = structure(hb, o, n, i, j), sg = structure(hgp, o, n, i, j), sr = structure(hrb, o, n, i, j); |
| 235 | if (sb > t->tier[0]) t->tier[0] = sb; |
| 236 | if (sg > t->tier[1]) t->tier[1] = sg; |
| 237 | if (sr > t->tier[2]) t->tier[2] = sr; |
| 238 | if (sg == 2) t->ncomp_g += 1; |
| 239 | } |
| 240 | } |
| 241 | size_t nt = 0; |
| 242 | for (size_t k = 0; k <= tmask2; k++) if (types[k].used) nt++; |
| 243 | rank_t *rk = malloc(sizeof(rank_t) * nt); |
| 244 | |
| 245 | // Held-out gold span types. |
| 246 | size_t ng = 0, capg = 4096; |
| 247 | uint64_t *gkeys = malloc(sizeof(uint64_t) * capg); |
| 248 | int gi[MAXN], gj[MAXN]; |
| 249 | for (int s = 0; s < ho.ns; s++) { |
| 250 | int k = gold_spans(&ho, s, gi, gj), o = ho.off[s]; |
| 251 | for (int q = 0; q < k; q++) { |
| 252 | if (ng == capg) { capg *= 2; gkeys = realloc(gkeys, sizeof(uint64_t) * capg); } |
| 253 | gkeys[ng++] = type_key(ho.c + o, gi[q], gj[q] - gi[q] + 1); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | const char *aname[5] = { "C-G", "HARD", "GRADED", "GRADED-G", "GRADED-R" }; |
| 258 | for (int arm = 0; arm < 5; arm++) { |
| 259 | size_t m = 0; |
| 260 | for (size_t k = 0; k <= tmask2; k++) { |
| 261 | type_t *t = &types[k]; |
| 262 | if (!t->used) continue; |
| 263 | if (arm == 1 && t->tier[1] < 2) continue; // HARD deletes non-assemblies |
| 264 | double k1 = arm == 0 ? t->n : arm == 1 ? t->ncomp_g : arm == 2 ? t->tier[0] : arm == 3 ? t->tier[1] : t->tier[2]; |
| 265 | double k2 = arm >= 2 ? t->n : 0; |
| 266 | rk[m++] = (rank_t) { t, k1, k2 }; |
| 267 | } |
| 268 | qsort(rk, m, sizeof(rank_t), by_key); |
| 269 | for (int b = 0; b < NBUD; b++) { |
| 270 | long B = fbudgets[b] < 0 || (size_t) fbudgets[b] > m ? (long) m : fbudgets[b]; |
| 271 | // mark the first B types of this list (used = 2), count held-out gold spans whose type is marked |
| 272 | for (size_t q = 0; q < m; q++) rk[q].t->used = q < (size_t) B ? 2 : 1; |
| 273 | long hit = 0; |
| 274 | for (size_t g = 0; g < ng; g++) { type_t *t = type_get(gkeys[g], 0); if (t && t->used == 2) hit++; } |
| 275 | for (size_t q = 0; q < m; q++) rk[q].t->used = 1; |
| 276 | printf("R\t%d\t%d\t%s\t%ld\t%.5f\t%zu\n", seed, tier, aname[arm], fbudgets[b], ng ? (double) hit / ng : 0, m); |
| 277 | if (fbudgets[b] < 0) break; |
| 278 | } |
| 279 | } |
| 280 | free(rk); free(gkeys); free(hb); free(hgp); free(hrb); |
| 281 | for (int l = 0; l < NLEV; l++) { free(sym[l]); free(symr[l]); } |
| 282 | free_hier(&Hg); free_hier(&Hr); |
| 283 | } |
| 284 | return 0; |
| 285 | } |