code wiki / _hdl_build / nx_ppmi_svd.nx
nx_ppmi_svd.nx source
↩ module page · 70 lines · 3327 B
1// nx_ppmi_svd.nx -- the sovereign semantic upgrade (Researcher-specced, built bits-up): PPMI weighting
2// over the team's co-occurrence counts. Raw co-occurrence over-credits FREQUENT terms (a term that is
3// in every doc co-occurs with everything, so it inflates unrelated docs). Pointwise Mutual Information
4// fixes this: PMI(a,b) = log( P(a,b) / (P(a)P(b)) ) = log( cooc(a,b)*D / (df(a)*df(b)) ); PPMI keeps the
5// POSITIVE part, so a term that co-occurs only AT CHANCE scores 0 and stops polluting retrieval. This is
6// the count-based dense-embedding rung (Levy & Goldberg 2014: PPMI is competitive with word2vec; the
7// further SVD dimensionality reduction is the next sub-rung, flagged not faked). Integer-only: PMI uses
8// the fixed-point ln from nx_bm25. license_tier: ORIGINAL
9
10import "nx_research_extract.nx" // re_has
11import "nx_bm25.nx" // bm_ln_micro (fixed-point ln)
12import "nx_syscalls.nx"
13
14func ps_doc(ptrs: *i64, k: i64) -> *u8 { return ptrs[k] as *u8 }
15
16// document frequency / co-occurrence over the corpus.
17func ps_df(ptrs: *i64, lens: *i64, N: i64, term: *u8) -> i64 {
18 var c: i64 = 0; var i: i64 = 0
19 while i < N { if re_has(ps_doc(ptrs, i), lens[i], term) == 1 { c = c + 1 } i = i + 1 }
20 return c
21}
22func ps_cooc(ptrs: *i64, lens: *i64, N: i64, a: *u8, b: *u8) -> i64 {
23 var c: i64 = 0; var i: i64 = 0
24 while i < N { if re_has(ps_doc(ptrs, i), lens[i], a) == 1 { if re_has(ps_doc(ptrs, i), lens[i], b) == 1 { c = c + 1 } } i = i + 1 }
25 return c
26}
27
28// PPMI in micro-units: max(0, ln( cooc*D / (df_a*df_b) )). 0 if they never co-occur OR only at chance.
29func ps_ppmi_micro(cooc: i64, df_a: i64, df_b: i64, D: i64) -> i64 {
30 if cooc <= 0 { return 0 }
31 if df_a <= 0 { return 0 }
32 if df_b <= 0 { return 0 }
33 let pmi: i64 = bm_ln_micro(cooc * D, df_a * df_b)
34 if pmi < 0 { return 0 }
35 return pmi
36}
37
38// the query's PPMI vector over the vocabulary.
39func ps_build_qvec(ptrs: *i64, lens: *i64, N: i64, qterm: *u8, vocab: *i64, V: i64, D: i64, qvec: *i64) -> i64 {
40 let dfq: i64 = ps_df(ptrs, lens, N, qterm)
41 var t: i64 = 0
42 while t < V {
43 let term: *u8 = vocab[t] as *u8
44 let cooc: i64 = ps_cooc(ptrs, lens, N, qterm, term)
45 let dft: i64 = ps_df(ptrs, lens, N, term)
46 qvec[t] = ps_ppmi_micro(cooc, dfq, dft, D)
47 t = t + 1
48 }
49 return 0
50}
51
52// PPMI-weighted semantic score: sum of qvec[t] for vocab terms present in the doc.
53func ps_score(ptrs: *i64, lens: *i64, k: i64, qvec: *i64, vocab: *i64, V: i64) -> i64 {
54 var s: i64 = 0; var t: i64 = 0
55 while t < V { if re_has(ps_doc(ptrs, k), lens[k], vocab[t] as *u8) == 1 { s = s + qvec[t] } t = t + 1 }
56 return s
57}
58
59func ps_best(ptrs: *i64, lens: *i64, N: i64, qvec: *i64, vocab: *i64, V: i64) -> i64 {
60 var best: i64 = 0 - 1; var bestscore: i64 = 0; var i: i64 = 0
61 while i < N { let sc: i64 = ps_score(ptrs, lens, i, qvec, vocab, V); if sc > bestscore { bestscore = sc; best = i } i = i + 1 }
62 return best
63}
64
65// the RAW co-occurrence vector + score (no PPMI) -- kept to PROVE PPMI removes frequent-term pollution.
66func ps_build_raw_qvec(ptrs: *i64, lens: *i64, N: i64, qterm: *u8, vocab: *i64, V: i64, qvec: *i64) -> i64 {
67 var t: i64 = 0
68 while t < V { qvec[t] = ps_cooc(ptrs, lens, N, qterm, vocab[t] as *u8); t = t + 1 }
69 return 0
70}