nx_absa_seq_gate.nx source
↩ module page · 287 lines · 24186 B
1// nx_absa_seq_gate.nx -- GATE for the averaged structured perceptron (nx_absa_seq, rung IM28), driven IN-PROCESS on
2// a planted train and test under /tmp/nx_absa_seq_gate. The load-bearing proof is that the model LEARNS: a train
3// where battery and screen are always the aspect term teaches the model to tag them, and on a test carrying
4// both it predicts both (a learning algorithm that did nothing would predict nothing). Controls: a model trained
5// on NOTHING (every record skipped, zero weights) predicts nothing, so the predictions came from the training; and
6// a second run gives the identical F1 (the perceptron is deterministic -- no randomness). Every fixture asserts its
7// 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_absa_lib.nx"
12import "nx_absa_seq.nx"
13import "nx_gate_verdict.nx"
14
15const G_ROOT: *u8 = "/tmp/nx_absa_seq_gate"
16const G_TRAIN: *u8 = "/tmp/nx_absa_seq_gate/train.seg"
17const G_TEST: *u8 = "/tmp/nx_absa_seq_gate/test.seg"
18const G_EMPTYTRAIN: *u8 = "/tmp/nx_absa_seq_gate/emptytrain.seg"
19const G_I64: i64 = 8
20const G_TRAINREC: i64 = 6 // battery x3, screen x3, all found
21const G_NG: i64 = 2 // test gold: battery, screen
22// train: battery and screen are ALWAYS the aspect term; the model must learn w=battery -> B and w=screen -> B
23const G_TRAIN_ROWS: *u8 = "The $T$ lasts long .\nbattery\n1\nI love the $T$ here .\nbattery\n1\nThis $T$ is weak .\nbattery\n-1\nThe $T$ is bright .\nscreen\n1\nI hate the $T$ today .\nscreen\n-1\nThis $T$ cracked badly .\nscreen\n-1\n"
24const G_TEST_ROWS: *u8 = "The $T$ is great .\nbattery\n1\nThe $T$ is dim .\nscreen\n-1\n"
25// a train that teaches NOTHING: the aspect terms never appear in their sentences (no $T$), so every record is skipped,
26// no weight is ever updated, and a zero-weight model must predict nothing (Viterbi ties break to O)
27const G_EMPTYTRAIN_ROWS: *u8 = "The car is fast .\nbattery\n1\nThe road is long .\nscreen\n-1\n"
28// the MERGE: a .seg emits one record per aspect, so ONE sentence with TWO aspects is TWO records that reconstruct to
29// the same string; unmerged they contradict each other (battery B / screen O, then battery O / screen B). Merged they
30// are ONE example labelling both, and a model trained on that one sentence must predict both at test.
31const G_MERGETRAIN: *u8 = "/tmp/nx_absa_seq_gate/mergetrain.seg"
32const G_MERGETEST: *u8 = "/tmp/nx_absa_seq_gate/mergetest.seg"
33const G_MERGETRAIN_ROWS: *u8 = "The $T$ and screen are great .\nbattery\n1\nThe battery and $T$ are great .\nscreen\n1\n"
34const G_MERGETEST_ROWS: *u8 = "The $T$ is good .\nbattery\n1\nThe $T$ is good .\nscreen\n1\n"
35const G_MERGE_RECORDS: i64 = 2
36const G_MERGE_SENTENCES: i64 = 1
37// the OCCURRENCE metric: the same term as the aspect of TWO sentences is ONE distinct term but TWO gold occurrences
38const G_OCCTEST: *u8 = "/tmp/nx_absa_seq_gate/occtest.seg"
39const G_OCCTEST_ROWS: *u8 = "The $T$ is great .\nbattery\n1\nMy $T$ died today .\nbattery\n-1\n"
40const G_OCC_DISTINCT: i64 = 1
41const G_OCC_OCCURRENCES: i64 = 2
42// the MAJORITY RULE: battery is the aspect in ONE training sentence and a plain word in THREE others (an aspect in a
43// quarter of its occurrences) -> excluded from the union's dictionary; screen is an aspect every time -> admitted
44const G_LOWPREC: *u8 = "/tmp/nx_absa_seq_gate/lowprec.seg"
45const G_LOWPREC_ROWS: *u8 = "The battery and $T$ are fine .\nscreen\n1\nMy battery and $T$ look good .\nscreen\n1\nThis battery and $T$ work well .\nscreen\n1\nThe $T$ is great .\nbattery\n1\n"
46const G_LOWPREC_DICT: i64 = 2
47const G_LOWPREC_MAJ: i64 = 1
48// POS features: a planted CoNLL-U treebank turns the tagger on; the model must still learn the planted aspects and the
49// receipt must say the tagger trained on the treebank's token count
50const G_TREEBANK: *u8 = "/tmp/nx_absa_seq_gate/mini.conllu"
51const G_TREEBANK_ROWS: *u8 = "1\tThe\tthe\tDET\tDT\t_\t2\tdet\t_\t_\n2\tbattery\tbattery\tNOUN\tNN\t_\t3\tnsubj\t_\t_\n3\tlasts\tlast\tVERB\tVBZ\t_\t0\troot\t_\t_\n4\tlong\tlong\tADV\tRB\t_\t3\tadvmod\t_\t_\n\n1\tThe\tthe\tDET\tDT\t_\t2\tdet\t_\t_\n2\tscreen\tscreen\tNOUN\tNN\t_\t3\tnsubj\t_\t_\n3\tis\tbe\tAUX\tVBZ\t_\t4\tcop\t_\t_\n4\tbright\tbright\tADJ\tJJ\t_\t0\troot\t_\t_\n"
52const G_TREEBANK_TOKS: i64 = 8
53const G_TREEBANK_SENTS: i64 = 2 // both planted trees are projective, so the parser trains on both
54// clusters: a planted PPMI model knowing four of the training words (two aspect nouns sharing a context, two others)
55const G_CLUST_MODEL: *u8 = "/tmp/nx_absa_seq_gate/mini.ppmi"
56const G_CLUST_WORDS: i64 = 4
57// crf: three Q10 marginals each rounded by at most half a unit plus the partition's own rounding -- four units is the bound
58const G_CRF_MARG_TOL: i64 = 4
59// embeddings: a planted NXEMB1 table for the four planted vocabulary words, 24 coordinates each (the mode's declared width)
60const G_EMB_MODEL: *u8 = "/tmp/nx_absa_seq_gate/mini.emb"
61const G_EMB_DIMS: i64 = 24
62const G_EMB_FILL_A: i64 = 97 // a deterministic fill: ((w+1)(d+1)97 mod 2048) - 1024, distinct per word
63const G_EMB_FILL_MOD: i64 = 2048
64const G_EMB_FILL_OFF: i64 = 1024
65// census dedupe: the SAME sentence carrying the SAME gold term twice (two records) is one gold occurrence to the scorer
66const G_DUPTEST: *u8 = "/tmp/nx_absa_seq_gate/duptest.seg"
67const G_DUPTEST_ROWS: *u8 = "The $T$ is great .\nbattery\n1\nThe $T$ is great .\nbattery\n1\n"
68
69func main() -> i64 {
70 gv_head("=== nx_absa_seq_gate -- the averaged structured perceptron learns aspect terms on a planted train/test ===" as *u8)
71 let c: *i64 = gv_ctr()
72 gk_mkdir(G_ROOT)
73 gk_write(G_TRAIN, G_TRAIN_ROWS)
74 gk_write(G_TEST, G_TEST_ROWS)
75 gk_write(G_EMPTYTRAIN, G_EMPTYTRAIN_ROWS)
76 gk_write(G_MERGETRAIN, G_MERGETRAIN_ROWS)
77 gk_write(G_MERGETEST, G_MERGETEST_ROWS)
78 gk_write(G_OCCTEST, G_OCCTEST_ROWS)
79 gk_write(G_LOWPREC, G_LOWPREC_ROWS)
80 gk_write(G_TREEBANK, G_TREEBANK_ROWS)
81 gv_check("fixture-reached-the-condition: train, test, empty-train, merge, occurrence, low-precision and treebank fixtures planted" as *u8, gk_exists(G_TRAIN) * gk_exists(G_TEST) * gk_exists(G_EMPTYTRAIN) * gk_exists(G_MERGETRAIN) * gk_exists(G_MERGETEST) * gk_exists(G_OCCTEST) * gk_exists(G_LOWPREC) * gk_exists(G_TREEBANK), c)
82 sp_set_treebank(0 as *u8)
83
84 let o: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
85 sp_eval(G_TRAIN, G_TEST, o)
86 gv_check_eq("train-records-used-six" as *u8, o[SP_O_TRAINREC], G_TRAINREC, c)
87 gv_check_eq("train-records-skipped-zero (every planted aspect is a contiguous token run)" as *u8, o[SP_O_TRAINSKIP], 0, c)
88 gv_check_eq("test-gold-terms-two" as *u8, o[SP_O_NG], G_NG, c)
89 gv_check_eq("model-learned-both-aspects: battery and screen predicted and matched" as *u8, o[SP_O_INTER], 2, c)
90 gv_check("model-f1-is-high-on-a-learnable-set (>= 800 permil)" as *u8, (o[SP_O_F1] >= 800) as i64, c)
91 gv_check("matched-never-exceeds-predicted" as *u8, (o[SP_O_INTER] <= o[SP_O_NS]) as i64, c)
92 gv_check("matched-never-exceeds-gold" as *u8, (o[SP_O_INTER] <= o[SP_O_NG]) as i64, c)
93 let f1a: i64 = o[SP_O_F1]
94
95 // determinism: a second run gives the identical F1 (no randomness in training or decoding)
96 let o2: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
97 sp_eval(G_TRAIN, G_TEST, o2)
98 gv_check_eq("perceptron-is-deterministic: the same train and test give the same f1" as *u8, o2[SP_O_F1], f1a, c)
99
100 // neg-control: a model trained on nothing (every record skipped, zero weights) predicts nothing -- NOT a
101 // learning artefact of the harness. This proves the predictions above came from the training, not from a
102 // model that tags everything. (An unseen WORD in a learned CONTEXT is correctly generalised, so that is not
103 // a valid control -- the model is supposed to predict from context; here we remove ALL learning instead.)
104 let ne: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
105 sp_eval(G_EMPTYTRAIN, G_TEST, ne)
106 gv_check_eq("neg-control-no-usable-training-records" as *u8, ne[SP_O_TRAINREC], 0, c)
107 gv_check_eq("neg-control-a-model-trained-on-nothing-predicts-nothing" as *u8, ne[SP_O_NS], 0, c)
108 gv_check_eq("neg-control-and-so-scores-zero-f1-not-a-divide" as *u8, ne[SP_O_F1], 0, c)
109
110 // the record MERGE: two records of ONE sentence with two aspects fold into ONE training sentence carrying both
111 let mg: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
112 sp_eval(G_MERGETRAIN, G_MERGETEST, mg)
113 gv_check_eq("merge-fixture-reached-the-condition: two usable records" as *u8, mg[SP_O_TRAINREC], G_MERGE_RECORDS, c)
114 gv_check_eq("merge-two-same-sentence-records-fold-into-ONE-training-sentence" as *u8, mg[SP_O_TRAINSENT], G_MERGE_SENTENCES, c)
115 gv_check_eq("merge-the-one-merged-example-teaches-BOTH-aspects (battery and screen both predicted)" as *u8, mg[SP_O_INTER], 2, c)
116 gv_check("merge-six-distinct-sentences-stay-six (the main train has no duplicates)" as *u8, (o[SP_O_TRAINSENT] == o[SP_O_TRAINREC]) as i64, c)
117 // the SHUFFLE's unit: the first pass records one group per merged sentence, unusable groups included, and the
118 // shuffled epochs that follow reproduce the same answer (the determinism tooth above ran the whole schedule twice)
119 gv_check_eq("shuffle-groups-recorded-equal-the-merged-sentences (main train: six)" as *u8, o[SP_O_GROUPS], G_TRAINREC, c)
120 gv_check_eq("shuffle-groups-fold-the-two-same-sentence-records-into-one (merge train: one group)" as *u8, mg[SP_O_GROUPS], G_MERGE_SENTENCES, c)
121 gv_check_eq("neg-control-shuffle-groups-count-unusable-sentences-too (empty train: two groups, zero usable records)" as *u8, ne[SP_O_GROUPS], 2, c)
122 // the shuffle is a MODE, off by default (measured a non-result on the full splits); on, the model still learns and
123 // the run is deterministic (the same seeded permutation twice gives the same F1)
124 gv_check_eq("shuffle-off-by-default (the main run walked the file in order)" as *u8, o[SP_O_SHUF], 0, c)
125 sp_set_shuffle(1)
126 let sh1: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
127 sp_eval(G_TRAIN, G_TEST, sh1)
128 let sh2: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
129 sp_eval(G_TRAIN, G_TEST, sh2)
130 sp_set_shuffle(0)
131 gv_check_eq("shuffle-on-when-the-mode-is-set" as *u8, sh1[SP_O_SHUF], 1, c)
132 gv_check_eq("shuffle-on-the-model-still-learns-both-aspects" as *u8, sh1[SP_O_INTER], 2, c)
133 gv_check_eq("shuffle-on-is-deterministic (the seeded permutation gives the same f1 twice)" as *u8, sh2[SP_O_F1], sh1[SP_O_F1], c)
134 // WORD CLUSTERS are a MODE too: off unless a model is set; with a planted model knowing four training words the
135 // clusterer runs and the model still learns both aspects
136 gv_check_eq("clusters-off-by-default (no model: clusters 0)" as *u8, o[SP_O_CLUST], 0, c)
137 let cwb: *u8 = sys_mmap(64)
138 let cwo: *i64 = sys_mmap(G_CLUST_WORDS * G_I64) as *i64
139 let cwl: *i64 = sys_mmap(G_CLUST_WORDS * G_I64) as *i64
140 let cwx: *i64 = sys_mmap(G_CLUST_WORDS * G_I64) as *i64
141 var cw: i64 = 0
142 cwo[0] = cw; cwl[0] = 7; cwx[0] = 0; cw = rm_catn(cwb, cw, "battery" as *u8, 7)
143 cwo[1] = cw; cwl[1] = 6; cwx[1] = 0; cw = rm_catn(cwb, cw, "screen" as *u8, 6)
144 cwo[2] = cw; cwl[2] = 5; cwx[2] = 1; cw = rm_catn(cwb, cw, "lasts" as *u8, 5)
145 cwo[3] = cw; cwl[3] = 6; cwx[3] = 2; cw = rm_catn(cwb, cw, "bright" as *u8, 6)
146 gv_check_eq("cluster-fixture-reached-the-condition: the planted PPMI model was written" as *u8, wc_plant_model(G_CLUST_MODEL, cwb, cwo, cwl, cwx, G_CLUST_WORDS), 1, c)
147 sp_set_clusters(G_CLUST_MODEL)
148 let cl: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
149 sp_eval(G_TRAIN, G_TEST, cl)
150 sp_set_clusters(0 as *u8)
151 gv_check_eq("clusters-on-when-a-model-is-set" as *u8, cl[SP_O_CLUST], 1, c)
152 gv_check_eq("cluster-fixture-reached-the-condition: the model knows the four planted training words" as *u8, cl[SP_O_CLUST_INMODEL], G_CLUST_WORDS, c)
153 gv_check_eq("clusters-on-the-model-still-learns-both-aspects" as *u8, cl[SP_O_INTER], 2, c)
154 // THE CRF OBJECTIVE is a MODE too: off by default; on, forward-backward marginals partition to one, no sentence's
155 // partition falls below its gold path, the likelihood rises across the epochs, the model learns, and it is deterministic
156 gv_check_eq("crf-off-by-default (crf 0)" as *u8, o[SP_O_CRF], 0, c)
157 gv_check_eq("crf-off-leaves-the-likelihood-counters-at-zero" as *u8, o[SP_O_CRF_NLL_LAST], 0, c)
158 sp_set_crf(1)
159 let cr: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
160 sp_eval(G_TRAIN, G_TEST, cr)
161 let cr2: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
162 sp_eval(G_TRAIN, G_TEST, cr2)
163 let ce: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
164 sp_eval(G_EMPTYTRAIN, G_TEST, ce)
165 sp_set_crf(0)
166 gv_check_eq("crf-on-when-the-mode-is-set" as *u8, cr[SP_O_CRF], 1, c)
167 gv_check("crf-fixture-reached-the-condition: the untrained model's first-epoch nll is positive" as *u8, (cr[SP_O_CRF_NLL_FIRST] > 0) as i64, c)
168 gv_check("crf-likelihood-rises: last-epoch nll below the first" as *u8, (cr[SP_O_CRF_NLL_LAST] < cr[SP_O_CRF_NLL_FIRST]) as i64, c)
169 gv_check("crf-marginals-partition-to-one-within-rounding" as *u8, (cr[SP_O_CRF_MARGDEV] <= G_CRF_MARG_TOL) as i64, c)
170 gv_check_eq("crf-no-partition-below-its-gold-path (impossible sentences 0)" as *u8, cr[SP_O_CRF_NEGNLL], 0, c)
171 gv_check_eq("crf-the-model-learns-both-aspects" as *u8, cr[SP_O_INTER], 2, c)
172 gv_check_eq("crf-is-deterministic (same f1 twice)" as *u8, cr2[SP_O_F1], cr[SP_O_F1], c)
173 gv_check_eq("crf-is-deterministic (same last-epoch nll twice)" as *u8, cr2[SP_O_CRF_NLL_LAST], cr[SP_O_CRF_NLL_LAST], c)
174 gv_check_eq("neg-control-crf-trained-on-nothing-predicts-nothing" as *u8, ce[SP_O_NS], 0, c)
175 // DENSE EMBEDDINGS are a MODE too: off by default; with a planted 24-coordinate table over the four planted vocabulary
176 // words the features are on, the training tokens the vocabulary knows are counted, and the model still learns
177 gv_check_eq("embed-off-by-default (embed 0)" as *u8, o[SP_O_EMB], 0, c)
178 let ev: *i64 = sys_mmap(G_CLUST_WORDS * G_EMB_DIMS * G_I64) as *i64
179 var ewi: i64 = 0
180 while ewi < G_CLUST_WORDS {
181 var edi: i64 = 0
182 while edi < G_EMB_DIMS { ev[ewi * G_EMB_DIMS + edi] = (((ewi + 1) * (edi + 1) * G_EMB_FILL_A) % G_EMB_FILL_MOD) - G_EMB_FILL_OFF; edi = edi + 1 }
183 ewi = ewi + 1
184 }
185 gv_check_eq("embed-fixture-reached-the-condition: the planted table was written" as *u8, ef_plant(G_EMB_MODEL, cwb, cwo, cwl, ev, G_CLUST_WORDS, G_EMB_DIMS), 1, c)
186 sp_set_embed(G_CLUST_MODEL, G_EMB_MODEL)
187 let eo: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
188 sp_eval(G_TRAIN, G_TEST, eo)
189 sp_set_embed(0 as *u8, 0 as *u8)
190 gv_check_eq("embed-on-when-a-table-is-set" as *u8, eo[SP_O_EMB], 1, c)
191 gv_check_eq("embed-table-width-reads-back" as *u8, eo[SP_O_EMB_DIM], G_EMB_DIMS, c)
192 gv_check("embed-fixture-reached-the-condition: the vocabulary knows some training tokens (hits > 0)" as *u8, (eo[SP_O_EMB_HITS] > 0) as i64, c)
193 gv_check("embed-lookups-cover-every-training-token-pass (lookups >= hits)" as *u8, (eo[SP_O_EMB_LOOKUPS] >= eo[SP_O_EMB_HITS]) as i64, c)
194 gv_check_eq("embed-on-the-model-still-learns-both-aspects" as *u8, eo[SP_O_INTER], 2, c)
195 // THE ERROR CENSUS partitions every perceptron miss by cause and must sum to gold minus matched: on the learnable set
196 // nothing is missed, and on the empty-train run (predicts nothing) both misses are dictionary-seen single tokens
197 gv_check_eq("census-no-misses-when-everything-is-matched (parts sum 0)" as *u8, o[SP_O_EC_UNTOK] + o[SP_O_EC_UNSEEN1] + o[SP_O_EC_UNSEENM] + o[SP_O_EC_SEEN1] + o[SP_O_EC_SEENM], 0, c)
198 gv_check_eq("census-union-misses-none-when-everything-is-matched" as *u8, o[SP_O_EC_UNIONMISS], 0, c)
199 gv_check_eq("census-empty-train-misses-are-two-dictionary-seen-single-tokens" as *u8, ne[SP_O_EC_SEEN1], 2, c)
200 gv_check_eq("census-empty-train-parts-sum-to-gold-minus-matched" as *u8, ne[SP_O_EC_UNTOK] + ne[SP_O_EC_UNSEEN1] + ne[SP_O_EC_UNSEENM] + ne[SP_O_EC_SEEN1] + ne[SP_O_EC_SEENM], ne[SP_O_OC_NG] - ne[SP_O_OC_TP], c)
201 gv_check_eq("census-empty-train-the-union-arm-still-catches-both (dictionary terms) so union misses 0" as *u8, ne[SP_O_EC_UNIONMISS], 0, c)
202 // a gold term repeated as the aspect of ONE sentence: the scorer counts one occurrence (the gold list dedupes), and the
203 // census must count one miss too -- measured on the real laptops split before this tooth existed (parts 415, gold minus
204 // matched 414), the defect the partition claim caught on its first run
205 gk_write(G_DUPTEST, G_DUPTEST_ROWS)
206 gv_check_eq("census-dup-fixture-reached-the-condition: the duplicated-gold test was written" as *u8, gk_exists(G_DUPTEST), 1, c)
207 let du: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
208 sp_eval(G_EMPTYTRAIN, G_DUPTEST, du)
209 gv_check_eq("census-dup-the-scorer-counts-the-repeated-gold-once" as *u8, du[SP_O_OC_NG], 1, c)
210 gv_check_eq("census-dup-the-untrained-model-matches-none" as *u8, du[SP_O_OC_TP], 0, c)
211 gv_check_eq("census-dup-parts-equal-gold-minus-matched (one, not two)" as *u8, du[SP_O_EC_UNTOK] + du[SP_O_EC_UNSEEN1] + du[SP_O_EC_UNSEENM] + du[SP_O_EC_SEEN1] + du[SP_O_EC_SEENM], du[SP_O_OC_NG] - du[SP_O_OC_TP], c)
212 gv_check_eq("census-dup-union-misses-counted-once-too" as *u8, du[SP_O_EC_UNIONMISS], 0, c)
213
214 // the UNION arm: perceptron predictions unioned with the dictionary tagger's -- it can never lose a perceptron
215 // hit, and on the planted set (dictionary == the two aspects) it matches the perceptron exactly
216 gv_check("union-never-loses-a-perceptron-hit (union matched >= perceptron matched)" as *u8, (o[SP_O_U_INTER] >= o[SP_O_INTER]) as i64, c)
217 gv_check_eq("union-on-the-planted-set-matches-both-aspects" as *u8, o[SP_O_U_INTER], 2, c)
218 // the dictionary is loaded from the TERM lines, so even the teach-nothing train yields a two-term dictionary; the
219 // discriminating control is that an UNTRAINED model contributes nothing and every union hit is the dictionary's
220 gv_check_eq("neg-control-untrained-model-contributes-nothing-to-the-union (perceptron matched 0)" as *u8, ne[SP_O_INTER], 0, c)
221 gv_check_eq("neg-control-union-hits-for-an-untrained-model-are-the-dictionary-alone (two dictionary terms)" as *u8, ne[SP_O_U_INTER], 2, c)
222
223 // the OCCURRENCE metric (the paper's unit): on the main test each sentence has one aspect, so both metrics agree;
224 // on the occurrence fixture one term is the aspect of TWO sentences -- ONE distinct term, TWO gold occurrences
225 gv_check_eq("occurrence-metric-agrees-with-the-set-metric-when-every-term-occurs-once (main test)" as *u8, o[SP_O_OC_NG], o[SP_O_NG], c)
226 gv_check_eq("occurrence-metric-main-test-matched-two" as *u8, o[SP_O_OC_TP], 2, c)
227 let oc: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
228 sp_eval(G_TRAIN, G_OCCTEST, oc)
229 gv_check_eq("occurrence-fixture-reached-the-condition: ONE distinct gold term" as *u8, oc[SP_O_NG], G_OCC_DISTINCT, c)
230 gv_check_eq("occurrence-fixture-counts-TWO-gold-occurrences (the same term in two sentences)" as *u8, oc[SP_O_OC_NG], G_OCC_OCCURRENCES, c)
231 gv_check_eq("occurrence-fixture-two-test-sentences-decoded" as *u8, oc[SP_O_TESTSENT], G_OCC_OCCURRENCES, c)
232 gv_check_eq("occurrence-fixture-the-learned-term-is-matched-in-BOTH-sentences" as *u8, oc[SP_O_OC_TP], G_OCC_OCCURRENCES, c)
233 gv_check("occurrence-matched-never-exceeds-occurrence-predicted" as *u8, (oc[SP_O_OC_TP] <= oc[SP_O_OC_NS]) as i64, c)
234
235 // the MAJORITY RULE for the union's dictionary: on the main train both planted aspects are ALWAYS aspects, so both
236 // pass; on the low-precision train battery is an aspect in one of four sentences and is excluded
237 gv_check_eq("majority-rule-admits-every-always-aspect-term (main train: two of two)" as *u8, o[SP_O_DICT_MAJ], G_NG, c)
238 let lpz: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
239 sp_eval(G_LOWPREC, G_TEST, lpz)
240 gv_check_eq("low-precision-fixture-reached-the-condition: two dictionary terms" as *u8, lpz[SP_O_DICT], G_LOWPREC_DICT, c)
241 gv_check_eq("majority-rule-excludes-the-term-that-is-an-aspect-in-a-quarter-of-its-occurrences (one of two pass)" as *u8, lpz[SP_O_DICT_MAJ], G_LOWPREC_MAJ, c)
242 gv_check("majority-rule-never-admits-more-terms-than-the-dictionary-holds" as *u8, (lpz[SP_O_DICT_MAJ] <= lpz[SP_O_DICT]) as i64, c)
243
244 // POS features on: with the planted treebank the tagger trains and the model still learns both planted aspects
245 gv_check_eq("pos-features-off-by-default (no treebank: pos_features 0)" as *u8, o[SP_O_POS], 0, c)
246 sp_set_treebank(G_TREEBANK)
247 sp_set_parse(1)
248 let po: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
249 sp_eval(G_TRAIN, G_TEST, po)
250 gv_check_eq("pos-fixture-reached-the-condition: the tagger trained on the planted treebank's eight tokens" as *u8, po[SP_O_POS_TOK], G_TREEBANK_TOKS, c)
251 gv_check_eq("pos-features-on-when-a-treebank-is-set" as *u8, po[SP_O_POS], 1, c)
252 gv_check_eq("pos-features-on-the-model-still-learns-both-aspects" as *u8, po[SP_O_INTER], 2, c)
253 gv_check("pos-features-on-f1-stays-high (>= 800 permil)" as *u8, (po[SP_O_F1] >= 800) as i64, c)
254 // parse features ride the same treebank: the parser trains on its two projective trees and every sentence is parsed
255 gv_check_eq("parse-fixture-reached-the-condition: the parser trained on the planted treebank's two sentences" as *u8, po[SP_O_PARSE_SENT], G_TREEBANK_SENTS, c)
256 gv_check_eq("parse-features-on-when-the-parse-mode-is-set" as *u8, po[SP_O_PARSE], 1, c)
257 gv_check_eq("parse-features-off-without-a-treebank" as *u8, o[SP_O_PARSE], 0, c)
258 // the DEFAULT with a treebank is the tagger alone: setting the treebank again resets the parse switch to off (the
259 // arm that measured best on the full splits), and the model still learns
260 sp_set_treebank(G_TREEBANK)
261 let pn: *i64 = sys_mmap(SP_O_N * G_I64) as *i64
262 sp_eval(G_TRAIN, G_TEST, pn)
263 gv_check_eq("parse-off-by-default-with-a-treebank-keeps-the-tagger (pos_features 1)" as *u8, pn[SP_O_POS], 1, c)
264 gv_check_eq("parse-off-by-default-with-a-treebank (parse_features 0 unless the parse mode is set)" as *u8, pn[SP_O_PARSE], 0, c)
265 gv_check_eq("parse-off-by-default-the-model-still-learns-both-aspects" as *u8, pn[SP_O_INTER], 2, c)
266 sp_set_treebank(0 as *u8)
267
268 gv_values_head()
269 gv_kv("pos_f1_permil" as *u8, po[SP_O_F1])
270 gv_kv("pos_train_tokens" as *u8, po[SP_O_POS_TOK])
271 gv_kv("parse_train_sentences" as *u8, po[SP_O_PARSE_SENT])
272 gv_kv("noparse_f1_permil" as *u8, pn[SP_O_F1])
273 gv_kv("dict_majority_main" as *u8, o[SP_O_DICT_MAJ])
274 gv_kv("dict_majority_lowprec" as *u8, lpz[SP_O_DICT_MAJ])
275 gv_kv("occ_f1_permil_main" as *u8, o[SP_O_OC_F1])
276 gv_kv("occ_fixture_gold_occurrences" as *u8, oc[SP_O_OC_NG])
277 gv_kv("occ_fixture_distinct_terms" as *u8, oc[SP_O_NG])
278 gv_kv("union_f1_permil" as *u8, o[SP_O_U_F1])
279 gv_kv("merge_train_records" as *u8, mg[SP_O_TRAINREC])
280 gv_kv("merge_train_sentences" as *u8, mg[SP_O_TRAINSENT])
281 gv_kv("merge_matched" as *u8, mg[SP_O_INTER])
282 gv_kv("seq_f1_permil" as *u8, f1a)
283 gv_kv("predicted" as *u8, o[SP_O_NS])
284 gv_kv("matched" as *u8, o[SP_O_INTER])
285 gv_kv("gold_terms" as *u8, o[SP_O_NG])
286 return gv_verdict("nx_absa_seq_gate" as *u8, c, "the averaged structured perceptron proven to LEARN on a planted train and test: it tags the two always-aspect words and predicts both at test, gives an identical F1 on a second run (deterministic), a model trained on nothing predicts nothing, two same-sentence records merge into one example that teaches both aspects, the union arm never loses a perceptron hit while an untrained model contributes nothing to it, the per-occurrence metric counts a term twice when it is the aspect of two sentences while the set metric counts it once, and the majority rule admits an always-aspect term to the union's dictionary while excluding one that is an aspect in a quarter of its occurrences, and with a planted treebank the POS tagger trains and the model still learns; every fixture asserts its own condition first" as *u8)
287}