OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-layer-discovery commits / 862a535

experiments/fractal: BEST-G scorer (--rule bestg) for run 2; run 1 output unchanged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
862a53511d626f77156d43cab385ae98e2c97783
by plankatron <fleeky@gmail.com> · 2026-10-18T20:22:40.000Z

Changed files (3) +49 -22

experiments/fractal/fractal.c +37 -13
……@@ -1,7 +1,8 @@
11// Graded ("fractal") connectors: see PREREG.md. Reuses the planted grammar, window-pair gate and Eisner
22// parser of ../across-cluster/ac.c unchanged.
33//
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).
56// Output rows:
67// P <seed> <tier> <scorer> <F1> <P> <R> level-1 link F1 on held-out
78// 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
4546
4647// ---------------------------------------------------------------- per-level scores
4748
48typedef struct { int A[NLEV]; double *S[NLEV]; int *ho[NLEV]; } hier_t;
49typedef 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.
52static 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}
4961
5062static corpus_t with_symbols (const corpus_t *C, int *sym) { corpus_t S = *C; S.c = sym; return S; }
5163
……@@ -59,6 +71,9 @@ static void build_hier (hier_t *H, const corpus_t *tr, const corpus_t *ho, const
5971 pairs_t P = count_pairs(&a, &ts);
6072 long ev, acc;
6173 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);
6277 free_pairs(&P);
6378 H->ho[l] = malloc(sizeof(int) * ho->ntok);
6479 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
6681 free(st);
6782}
6883
69static void free_hier (hier_t *H) { for (int l = 0; l < NLEV; l++) { free(H->S[l]); free(H->ho[l]); } }
84static 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]); } }
7085
7186// Edge scorers. ctx = { hier, symbols-per-level for the corpus being parsed, mode }.
72typedef struct { hier_t *H; int *sym[NLEV]; int mode; } edge_ctx_t; // mode 0..3 = single level, 4 = backoff
87typedef struct { hier_t *H; int *sym[NLEV]; int mode; } edge_ctx_t; // mode 0..3 = single level, 4 = backoff, 5 = best-G
7388
7489static double edge_level (const corpus_t *C, int o, int i, int j, void *vctx, int window) {
7590 (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
7893 int l = e->mode;
7994 return e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]];
8095 }
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]; }
84107 }
85 return NEG;
108 return best;
86109}
87110
88111// 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 };
171194int main (int argc, char **argv) {
172195 if (argc < 2) { fprintf(stderr, "usage: fractal <seed> [--tiers a,b] [--heldout N]\n"); return 1; }
173196 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
175198 for (int i = 2; i < argc; i++) {
176199 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; }
177201 else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) {
178202 ntier = 0;
179203 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) {
204228 build_hier(&Hr, &tr, &ho, rmap);
205229
206230 // 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++) {
209233 edge_ctx_t e = { &Hg, { Hg.ho[0], Hg.ho[1], Hg.ho[2], Hg.ho[3] }, mode };
210234 link_t L = parse_eval(&ho, edge_level, &e, 1);
211235 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) {
219243 symr[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, rmap, l, symr[l]);
220244 }
221245 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 };
223247 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 };
225249 parse_heads(&tr, &eb, hb); parse_heads(&tr, &eg, hgp); parse_heads(&tr, &er, hrb);
226250
227251 memset(types, 0, sizeof(type_t) * (tmask2 + 1));
experiments/fractal/run.sh +3 -2
……@@ -1,5 +1,6 @@
11#!/bin/sh
2# Full pre-registered run: 10 seeds x tiers 3K,10K,30K in parallel. Output: results/<stamp>/seed-N.tsv
2# Full pre-registered run: 10 seeds x tiers 3K,10K,30K in parallel; extra args go to fractal (run 2: --rule bestg).
3# Output: results/<stamp>/seed-N.tsv
34set -e
45cd "$(dirname "$0")"
56gcc -O2 -Wall -Wno-unused-function -o fractal fractal.c -lm 2>/dev/null
……@@ -7,6 +8,6 @@ out=results/$(date +%s)
78mkdir -p "$out"
89git rev-parse HEAD > "$out/commit.txt"
910sha256sum fractal.c ../across-cluster/ac.c > "$out/src.sha256"
10for s in 1 2 3 4 5 6 7 8 9 10; do ./fractal $s > "$out/seed-$s.tsv" & done
11for s in 1 2 3 4 5 6 7 8 9 10; do ./fractal $s "$@" > "$out/seed-$s.tsv" & done
1112wait
1213echo "$out"
experiments/fractal/summarize.py +9 -7
……@@ -1,5 +1,6 @@
11#!/usr/bin/env python3
2# Score a results/<stamp>/ directory against PREREG.md. Usage: summarize.py results/<stamp>
2# Score a results/<stamp>/ directory against PREREG.md (run 1) or PREREG-2.md (run 2: pass BEST-G).
3# Usage: summarize.py results/<stamp> [BACKOFF|BEST-G]
34import glob, math, os, sys
45
56T975 = {9: 2.262}
……@@ -17,14 +18,15 @@ for f in sorted(glob.glob(os.path.join(sys.argv[1], 'seed-*.tsv'))):
1718 elif p[0] == 'R':
1819 R[(int(p[2]), p[3], int(p[4]), int(p[1]))] = float(p[5]); L[(int(p[2]), p[3], int(p[1]))] = int(p[6])
1920seeds = sorted({k[2] for k in P}); tiers = sorted({k[0] for k in P})
21COMB = sys.argv[2] if len(sys.argv) > 2 else 'BACKOFF' # the combined scorer under test
2022out = []
2123def say(s=''): out.append(s); print(s)
2224def pv(t, a): return [P[(t, a, s)] for s in seeds]
2325def rv(t, a, b): return [R[(t, a, b, s)] for s in seeds]
2426
2527say('## Level-1 link F1 (mean)')
26say('tier\t' + '\t'.join(['W', 'G', 'S1', 'S2', 'BACKOFF']))
27for t in tiers: say(f'{t}\t' + '\t'.join(f'{mean(pv(t, a)):.3f}' for a in ['W', 'G', 'S1', 'S2', 'BACKOFF']))
28say('tier\t' + '\t'.join(['W', 'G', 'S1', 'S2', COMB]))
29for t in tiers: say(f'{t}\t' + '\t'.join(f'{mean(pv(t, a)):.3f}' for a in ['W', 'G', 'S1', 'S2', COMB]))
2830say('\n## Level-2 type recall at budget (mean)')
2931arms = ['C-G', 'HARD', 'GRADED', 'GRADED-G', 'GRADED-R']
3032for t in tiers:
……@@ -39,12 +41,12 @@ for t in tiers:
3941 best = max(['W', 'G', 'S1', 'S2'], key=lambda a: mean(pv(t, a)))
4042 if t <= 10000:
4143 for a in ['W', 'G', 'S1', 'S2']:
42 c = ci([x - y for x, y in zip(pv(t, 'BACKOFF'), pv(t, a))])
44 c = ci([x - y for x, y in zip(pv(t, COMB), pv(t, a))])
4345 ok &= c[1] > 0
44 if a == best: cells.append(f'{t}: BACKOFF-{a} {c[0]:+.3f}[{c[1]:+.3f}]')
46 if a == best: cells.append(f'{t}: {COMB}-{a} {c[0]:+.3f}[{c[1]:+.3f}]')
4547 else:
46 d = mean(pv(t, 'BACKOFF')) - mean(pv(t, best)); ok &= d >= -0.01
47 cells.append(f'{t}: BACKOFF-{best} {d:+.3f}')
48 d = mean(pv(t, COMB)) - mean(pv(t, best)); ok &= d >= -0.01
49 cells.append(f'{t}: {COMB}-{best} {d:+.3f}')
4850verdict('F1', ok, '; '.join(cells))
4951g = [(t, mean(rv(t, 'GRADED', -1))) for t in tiers]
5052verdict('F2', all(x >= 0.95 for _, x in g), ' '.join(f'{t}:{x:.3f}' for t, x in g) + ' | HARD ' + ' '.join(f'{t}:{mean(rv(t, "HARD", -1)):.3f}' for t in tiers))