nx_depparse_gate.nx source
↩ module page · 141 lines · 10390 B
1// nx_depparse_gate.nx -- GATE for the dependency parser (nx_depparse), driven IN-PROCESS on a planted CoNLL-U
2// mini-treebank under /tmp/nx_depparse_gate. Proves: the shared reader's HEAD and DEPREL columns (root head 0, a
3// subtype's base name kept), the label table (first-seen interning, unknown reads -1), that the parser LEARNS (a
4// train of one grammar is parsed perfectly at test, UAS and LAS 1000), converges, is deterministic, SKIPS a planted
5// non-projective sentence and counts it, parses an arbitrary tagged token stream through dp_parse_stream with the
6// same answers, and that an untrained parser is not a learned one (its right-branching chain scores far below).
7// Every fixture asserts its own condition first. No network. license_tier: ORIGINAL No hw writes (Rule 26).
8import "nx_syscalls.nx"
9import "nx_gatekit_lib.nx"
10import "nx_reviewmine_lib.nx"
11import "nx_postag.nx"
12import "nx_depparse.nx"
13import "nx_gate_verdict.nx"
14
15const G_ROOT: *u8 = "/tmp/nx_depparse_gate"
16const G_TRAIN: *u8 = "/tmp/nx_depparse_gate/train.conllu"
17const G_TEST: *u8 = "/tmp/nx_depparse_gate/test.conllu"
18const G_SUBTYPE: *u8 = "/tmp/nx_depparse_gate/subtype.conllu"
19const G_I64: i64 = 8
20// five projective sentences of one grammar (det amod nsubj root punct) plus ONE non-projective sentence (arcs 3->1 and
21// 4->2 cross) the static oracle cannot reach and must skip
22const G_TRAIN_ROWS: *u8 = "1\tThe\tthe\tDET\tDT\t_\t2\tdet\t_\t_\n2\tdog\tdog\tNOUN\tNN\t_\t3\tnsubj\t_\t_\n3\truns\trun\tVERB\tVBZ\t_\t0\troot\t_\t_\n4\t.\t.\tPUNCT\t.\t_\t3\tpunct\t_\t_\n\n1\tA\ta\tDET\tDT\t_\t2\tdet\t_\t_\n2\tcat\tcat\tNOUN\tNN\t_\t3\tnsubj\t_\t_\n3\tsleeps\tsleep\tVERB\tVBZ\t_\t0\troot\t_\t_\n4\t.\t.\tPUNCT\t.\t_\t3\tpunct\t_\t_\n\n1\tThe\tthe\tDET\tDT\t_\t3\tdet\t_\t_\n2\tbig\tbig\tADJ\tJJ\t_\t3\tamod\t_\t_\n3\tdog\tdog\tNOUN\tNN\t_\t4\tnsubj\t_\t_\n4\tsleeps\tsleep\tVERB\tVBZ\t_\t0\troot\t_\t_\n5\t.\t.\tPUNCT\t.\t_\t4\tpunct\t_\t_\n\n1\tA\ta\tDET\tDT\t_\t3\tdet\t_\t_\n2\tsmall\tsmall\tADJ\tJJ\t_\t3\tamod\t_\t_\n3\tcat\tcat\tNOUN\tNN\t_\t4\tnsubj\t_\t_\n4\truns\trun\tVERB\tVBZ\t_\t0\troot\t_\t_\n5\t.\t.\tPUNCT\t.\t_\t4\tpunct\t_\t_\n\n1\tThe\tthe\tDET\tDT\t_\t2\tdet\t_\t_\n2\tcat\tcat\tNOUN\tNN\t_\t3\tnsubj\t_\t_\n3\truns\trun\tVERB\tVBZ\t_\t0\troot\t_\t_\n\n1\tw1\tw1\tNOUN\tNN\t_\t3\tobl\t_\t_\n2\tw2\tw2\tNOUN\tNN\t_\t4\tobl\t_\t_\n3\tw3\tw3\tVERB\tVBZ\t_\t0\troot\t_\t_\n4\tw4\tw4\tVERB\tVBZ\t_\t3\tconj\t_\t_\n"
23const G_TRAIN_SENTS: i64 = 6
24const G_TRAIN_TOKS: i64 = 26 // 4+4+5+5+3+5? no: 4+4+5+5+3+4 = 25 tokens plus the reader counts the range-free rows only
25const G_NONPROJ: i64 = 1
26const G_NREL: i64 = 7 // det nsubj root punct amod obl conj, in first-seen order
27const G_REL_ROOT: i64 = 2
28const G_TEST_ROWS: *u8 = "1\tThe\tthe\tDET\tDT\t_\t3\tdet\t_\t_\n2\tsmall\tsmall\tADJ\tJJ\t_\t3\tamod\t_\t_\n3\tdog\tdog\tNOUN\tNN\t_\t4\tnsubj\t_\t_\n4\tsleeps\tsleep\tVERB\tVBZ\t_\t0\troot\t_\t_\n5\t.\t.\tPUNCT\t.\t_\t4\tpunct\t_\t_\n\n1\tA\ta\tDET\tDT\t_\t3\tdet\t_\t_\n2\tbig\tbig\tADJ\tJJ\t_\t3\tamod\t_\t_\n3\tcat\tcat\tNOUN\tNN\t_\t4\tnsubj\t_\t_\n4\truns\trun\tVERB\tVBZ\t_\t0\troot\t_\t_\n5\t.\t.\tPUNCT\t.\t_\t4\tpunct\t_\t_\n"
29const G_TEST_TOKS: i64 = 10
30const G_TEST_SENTS: i64 = 2
31// a subtype relation: the reader keeps the base name before ':'
32const G_SUBTYPE_ROWS: *u8 = "1\tits\tits\tPRON\tPRP$\t_\t2\tnmod:poss\t_\t_\n2\ttoy\ttoy\tNOUN\tNN\t_\t0\troot\t_\t_\n"
33const G_STREAM_TOKS: i64 = 3
34const G_UNTRAINED_MAX: i64 = 500
35
36func main() -> i64 {
37 gv_head("=== nx_depparse_gate -- the dependency parser reads CoNLL-U heads, learns a grammar, and parses a token stream ===" as *u8)
38 let c: *i64 = gv_ctr()
39 gk_mkdir(G_ROOT)
40 gk_write(G_TRAIN, G_TRAIN_ROWS)
41 gk_write(G_TEST, G_TEST_ROWS)
42 gk_write(G_SUBTYPE, G_SUBTYPE_ROWS)
43 gv_check("fixture-reached-the-condition: train, test and subtype treebanks planted" as *u8, gk_exists(G_TRAIN) * gk_exists(G_TEST) * gk_exists(G_SUBTYPE), c)
44
45 // the shared reader's dependency columns, read directly
46 pt_reset()
47 let lp: *i64 = sys_mmap(RM_I64_PAIR) as *i64
48 lp[0] = 0
49 let b: *u8 = sys_read_file(G_TRAIN, lp)
50 let ip: *i64 = sys_mmap(RM_I64_PAIR) as *i64
51 let unk: *i64 = sys_mmap(RM_I64_PAIR) as *i64
52 ip[0] = 0
53 unk[0] = 0
54 let cnt1: i64 = pt_read_sentence(b, lp[0], ip, unk)
55 gv_check_eq("reader-first-sentence-four-tokens" as *u8, cnt1, 4, c)
56 gv_check_eq("reader-HEAD-of-the-root-verb-is-zero" as *u8, pt_heads[2], 0, c)
57 gv_check_eq("reader-HEAD-of-the-subject-is-three (1-based id of the verb)" as *u8, pt_heads[1], 3, c)
58 gv_check_eq("reader-DEPREL-of-the-subject-is-five-bytes (nsubj)" as *u8, pt_rellens[1], 5, c)
59 gv_check_eq("reader-DEPREL-of-the-determiner-is-three-bytes (det)" as *u8, pt_rellens[0], 3, c)
60 let lp2: *i64 = sys_mmap(RM_I64_PAIR) as *i64
61 lp2[0] = 0
62 let b2: *u8 = sys_read_file(G_SUBTYPE, lp2)
63 ip[0] = 0
64 let cnt2: i64 = pt_read_sentence(b2, lp2[0], ip, unk)
65 gv_check_eq("subtype-fixture-reached-the-condition: two tokens read" as *u8, cnt2, 2, c)
66 gv_check_eq("reader-keeps-the-base-name-before-the-colon (nmod:poss reads as four bytes, nmod)" as *u8, pt_rellens[0], 4, c)
67
68 // the label table
69 dp_reset()
70 gv_check_eq("label-table-starts-empty" as *u8, dp_nrel, 0, c)
71 gv_check_eq("label-table-interns-first-seen-as-id-0" as *u8, dp_rel_id("det" as *u8, 3, 1), 0, c)
72 gv_check_eq("label-table-interns-a-second-name-as-id-1" as *u8, dp_rel_id("nsubj" as *u8, 5, 1), 1, c)
73 gv_check_eq("label-table-finds-a-known-name-without-interning" as *u8, dp_rel_id("det" as *u8, 3, 0), 0, c)
74 gv_check_eq("neg-control-label-table-unknown-name-reads-minus-one-when-not-interning" as *u8, dp_rel_id("zzz" as *u8, 3, 0), DP_NONE, c)
75 let nb: *u8 = sys_mmap(DP_RELNAME)
76 gv_check_eq("label-name-round-trips (nsubj, 5 bytes)" as *u8, dp_rel_name(1, nb), 5, c)
77
78 // learn: the tagger first (the parser reads its tags), then the parser
79 let po: *i64 = sys_mmap(PT_O_N * G_I64) as *i64
80 pt_eval(G_TRAIN, G_TEST, po)
81 gv_check_eq("tagger-ready-before-the-parser (tagger test accuracy 1000 on the planted words)" as *u8, po[PT_O_ACC], PT_PERMIL, c)
82 let o: *i64 = sys_mmap(DP_O_N * G_I64) as *i64
83 dp_eval(G_TRAIN, G_TEST, o)
84 gv_check_eq("train-sentences-equal-the-planted-count (six)" as *u8, o[DP_O_TRAINSENT], G_TRAIN_SENTS, c)
85 gv_check_eq("nonprojective-fixture-reached-the-condition: exactly one training sentence skipped as unreachable" as *u8, o[DP_O_NONPROJ], G_NONPROJ, c)
86 gv_check_eq("no-unusable-training-sentences (every HEAD present, table not full)" as *u8, o[DP_O_UNUSABLE], 0, c)
87 gv_check_eq("relation-labels-interned-equal-the-planted-count (seven)" as *u8, o[DP_O_NREL], G_NREL, c)
88 gv_check_eq("test-tokens-ten" as *u8, o[DP_O_TOKENS], G_TEST_TOKS, c)
89 gv_check_eq("test-sentences-two" as *u8, o[DP_O_TESTSENT], G_TEST_SENTS, c)
90 gv_check_eq("parser-LEARNS: every test head correct (UAS 1000 permil)" as *u8, o[DP_O_UAS], DP_PERMIL, c)
91 gv_check_eq("parser-LEARNS-labels: every test relation correct (LAS 1000 permil)" as *u8, o[DP_O_LAS], DP_PERMIL, c)
92 gv_check_eq("parser-converged: no updates in the last epoch" as *u8, o[DP_O_UPDATES], 0, c)
93
94 // determinism
95 let o2: *i64 = sys_mmap(DP_O_N * G_I64) as *i64
96 dp_eval(G_TRAIN, G_TEST, o2)
97 gv_check_eq("parser-is-deterministic: same UAS on a second run" as *u8, o2[DP_O_UAS], o[DP_O_UAS], c)
98
99 // the stream entry point the aspect model uses: tagged tokens in one buffer -> heads and relations
100 let sb: *u8 = sys_mmap(64)
101 let so: *i64 = sys_mmap(8 * G_I64) as *i64
102 let sl: *i64 = sys_mmap(8 * G_I64) as *i64
103 let st: *i64 = sys_mmap(8 * G_I64) as *i64
104 let sh: *i64 = sys_mmap(8 * G_I64) as *i64
105 let sr: *i64 = sys_mmap(8 * G_I64) as *i64
106 var w: i64 = rm_catn(sb, 0, "the" as *u8, 3); so[0] = 0; sl[0] = 3
107 so[1] = w; sl[1] = 3; w = rm_catn(sb, w, "dog" as *u8, 3)
108 so[2] = w; sl[2] = 4; w = rm_catn(sb, w, "runs" as *u8, 4)
109 pt_tag_stream(sb, so, sl, G_STREAM_TOKS, st)
110 let parsed: i64 = dp_parse_stream(sb, so, sl, st, G_STREAM_TOKS, sh, sr)
111 gv_check_eq("stream-parsing-returns-the-token-count" as *u8, parsed, G_STREAM_TOKS, c)
112 gv_check_eq("stream-parsing-the-attaches-to-dog (head index 1)" as *u8, sh[0], 1, c)
113 gv_check_eq("stream-parsing-dog-attaches-to-runs (head index 2)" as *u8, sh[1], 2, c)
114 gv_check_eq("stream-parsing-runs-is-the-root (head -1)" as *u8, sh[2], DP_NONE, c)
115 gv_check_eq("stream-parsing-runs-carries-the-root-relation" as *u8, sr[2], G_REL_ROOT, c)
116 gv_check_eq("stream-parsing-dog-is-nsubj (relation id 1)" as *u8, sr[1], 1, c)
117
118 // neg-control: an UNTRAINED parser (weights zero, ties to SHIFT) builds a right-branching chain, which attaches
119 // every token to its left neighbour -- on this grammar that is never the gold head, so it scores far below 1000
120 dp_reset()
121 dp_rel_id("det" as *u8, 3, 1)
122 let ne: *i64 = sys_mmap(DP_O_N * G_I64) as *i64
123 var qq: i64 = 0
124 while qq < DP_O_N { ne[qq] = 0; qq = qq + 1 }
125 dp_T = 1
126 dp_test(G_TEST, ne)
127 gv_check_eq("neg-control-untrained-parser-scores-zero-UAS-on-this-grammar" as *u8, ne[DP_O_UAS], 0, c)
128 gv_check("neg-control-untrained-parser-is-far-below-the-learned-one" as *u8, (ne[DP_O_UAS] < G_UNTRAINED_MAX) as i64, c)
129
130 gv_values_head()
131 gv_kv("test_uas_permil" as *u8, o[DP_O_UAS])
132 gv_kv("test_las_base_permil" as *u8, o[DP_O_LAS])
133 gv_kv("train_sentences" as *u8, o[DP_O_TRAINSENT])
134 gv_kv("train_tokens" as *u8, o[DP_O_TRAINTOK])
135 gv_kv("nonprojective_skipped" as *u8, o[DP_O_NONPROJ])
136 gv_kv("relation_labels" as *u8, o[DP_O_NREL])
137 gv_kv("last_epoch_updates" as *u8, o[DP_O_UPDATES])
138 gv_kv("untrained_uas_permil" as *u8, ne[DP_O_UAS])
139 gv_kv("tagger_accuracy_permil" as *u8, po[PT_O_ACC])
140 return gv_verdict("nx_depparse_gate" as *u8, c, "the dependency parser proven on a planted CoNLL-U mini-treebank: the shared reader carries HEAD and the base DEPREL, the label table interns first-seen and refuses unknowns, the parser learns a grammar to perfect UAS and LAS and converges, is deterministic, skips and counts a non-projective sentence, parses an arbitrary tagged token stream identically, and an untrained parser scores zero on the same grammar; every fixture asserts its own condition first" as *u8)
141}