code wiki / (root) / nx_text2motion.nx

nx_text2motion.nx source

↩ module page · 165 lines · 7764 B

1// nx_text2motion.nx -- TEXT-CONDITIONED neural motion (the MotionGPT headline, S6b): a description -> motion, 2// via LEARNED weights, no keyword if-else. Two learned stages, sovereign + integer-deterministic: 3// (1) TEXT ENCODER: bag-of-words over a small vocab -> a linear classifier (weight matrix W[class][word]) 4// TRAINED by the perceptron rule (error-driven weight updates) on (caption -> move-class) pairs. This is 5// real supervised learning; the honest test is GENERALIZATION to captions never trained on (held-out). 6// (2) CLASS-CONDITIONED generator: per class, a token-transition model learned from that class's motion tokens 7// (composes with nx_motion_neural's VQ codebook), so text -> class -> a generated motion-token sequence. 8// The chain: caption -> t2m_encode (learned argmax) -> class -> t2m_gen_tokens (learned transitions) -> decode 9// via the VQ codebook -> drive the rig. Fixed vocab + fixed classes = data; grow by adding captions/moves. 10// license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_motion_neural.nx" // MN_K/MN_D/MN_FR/MN_MOVES + mn_* + skeleton, transitively 13 14const T2M_VOCAB: i64 = 32 // known word slots 15const T2M_CLASSES: i64 = 10 // = MN_MOVES (wave/cheer/sway/kick/lookaround/armcircle/clap/punch/bow/jumpingjack) 16 17// ---- fixed word vocabulary (DATA; grows by adding a row + training captions) -------------------------- 18// returns the vocab slot for a lowercase word slice, or -1 (out-of-vocab -> ignored in the BoW, like a real 19// encoder's UNK). Matched by exact bytes (caller lowercases). Words chosen to span the 6 move classes' captions. 20func t2m_word_id(w: *u8, off: i64, len: i64) -> i64 { 21 if t2m_weq(w, off, len, "wave" as *u8) == 1 { return 0 } 22 if t2m_weq(w, off, len, "hello" as *u8) == 1 { return 1 } 23 if t2m_weq(w, off, len, "hi" as *u8) == 1 { return 2 } 24 if t2m_weq(w, off, len, "greet" as *u8) == 1 { return 3 } 25 if t2m_weq(w, off, len, "cheer" as *u8) == 1 { return 4 } 26 if t2m_weq(w, off, len, "celebrate" as *u8) == 1 { return 5 } 27 if t2m_weq(w, off, len, "win" as *u8) == 1 { return 6 } 28 if t2m_weq(w, off, len, "excited" as *u8) == 1 { return 7 } 29 if t2m_weq(w, off, len, "sway" as *u8) == 1 { return 8 } 30 if t2m_weq(w, off, len, "dance" as *u8) == 1 { return 9 } 31 if t2m_weq(w, off, len, "rhythm" as *u8) == 1 { return 10 } 32 if t2m_weq(w, off, len, "groove" as *u8) == 1 { return 11 } 33 if t2m_weq(w, off, len, "kick" as *u8) == 1 { return 12 } 34 if t2m_weq(w, off, len, "march" as *u8) == 1 { return 13 } 35 if t2m_weq(w, off, len, "step" as *u8) == 1 { return 14 } 36 if t2m_weq(w, off, len, "leg" as *u8) == 1 { return 15 } 37 if t2m_weq(w, off, len, "look" as *u8) == 1 { return 16 } 38 if t2m_weq(w, off, len, "around" as *u8) == 1 { return 17 } 39 if t2m_weq(w, off, len, "search" as *u8) == 1 { return 18 } 40 if t2m_weq(w, off, len, "scan" as *u8) == 1 { return 19 } 41 if t2m_weq(w, off, len, "stretch" as *u8) == 1 { return 20 } 42 if t2m_weq(w, off, len, "reach" as *u8) == 1 { return 21 } 43 if t2m_weq(w, off, len, "circle" as *u8) == 1 { return 22 } 44 if t2m_weq(w, off, len, "arm" as *u8) == 1 { return 23 } 45 if t2m_weq(w, off, len, "clap" as *u8) == 1 { return 24 } 46 if t2m_weq(w, off, len, "applaud" as *u8) == 1 { return 25 } 47 if t2m_weq(w, off, len, "punch" as *u8) == 1 { return 26 } 48 if t2m_weq(w, off, len, "jab" as *u8) == 1 { return 27 } 49 if t2m_weq(w, off, len, "fight" as *u8) == 1 { return 28 } 50 if t2m_weq(w, off, len, "bow" as *u8) == 1 { return 29 } 51 if t2m_weq(w, off, len, "jump" as *u8) == 1 { return 30 } 52 if t2m_weq(w, off, len, "jack" as *u8) == 1 { return 31 } 53 return 0 - 1 54} 55func t2m_weq(w: *u8, off: i64, len: i64, s: *u8) -> i64 { 56 var sl: i64 = 0 57 while s[sl] != (0 as u8) { sl = sl + 1 } 58 if sl != len { return 0 } 59 var i: i64 = 0 60 while i < len { if (w[off + i] as i64) != (s[i] as i64) { return 0 } i = i + 1 } 61 return 1 62} 63func t2m_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 64// bag-of-words: fill bow[T2M_VOCAB] (1 if word present) from a caption; returns #known words. 65func t2m_bow(cap: *u8, bow: *i64) -> i64 { 66 var i: i64 = 0 67 while i < T2M_VOCAB { bow[i] = 0; i = i + 1 } 68 var known: i64 = 0 69 var p: i64 = 0 70 var start: i64 = 0 71 var scanning: i64 = 1 72 while scanning == 1 { 73 let c: i64 = cap[p] as i64 74 if c == 0 { scanning = 0 } 75 var boundary: i64 = 0 76 if c == 0 { boundary = 1 } else { if c == 32 { boundary = 1 } } 77 if boundary == 1 { 78 if p > start { 79 // lowercase the word slice in place-safe copy 80 let wl: i64 = p - start 81 let tmp: *u8 = sys_mmap(64) 82 var j: i64 = 0 83 while j < wl { tmp[j] = t2m_lc(cap[start + j] as i64) as u8; j = j + 1 } 84 let id: i64 = t2m_word_id(tmp, 0, wl) 85 if id >= 0 { if bow[id] == 0 { bow[id] = 1; known = known + 1 } } 86 } 87 start = p + 1 88 } 89 p = p + 1 90 } 91 return known 92} 93 94// ---- learned text encoder: W[class*VOCAB + word], perceptron-trained ----------------------------------- 95// score class c for a bow = sum_w W[c][w]*bow[w]; predict argmax. 96func t2m_score(W: *i64, c: i64, bow: *i64) -> i64 { 97 var s: i64 = 0 98 var w: i64 = 0 99 while w < T2M_VOCAB { s = s + W[c * T2M_VOCAB + w] * bow[w]; w = w + 1 } 100 return s 101} 102func t2m_predict(W: *i64, bow: *i64) -> i64 { 103 var best: i64 = 0 104 var bs: i64 = t2m_score(W, 0, bow) 105 var c: i64 = 1 106 while c < T2M_CLASSES { 107 let s: i64 = t2m_score(W, c, bow) 108 if s > bs { bs = s; best = c } 109 c = c + 1 110 } 111 return best 112} 113// train W by the perceptron rule over (caption,label) pairs for `epochs`. On a mistake: W[label]+=bow, 114// W[pred]-=bow. Returns training mistakes in the LAST epoch (0 = separated the training set). caps/labels are 115// parallel arrays; ncap pairs; cap_ptr[i] is a *u8. 116func t2m_train(W: *i64, cap_ptr: *i64, labels: *i64, ncap: i64, epochs: i64) -> i64 { 117 var i: i64 = 0 118 while i < T2M_CLASSES * T2M_VOCAB { W[i] = 0; i = i + 1 } 119 let bow: *i64 = sys_mmap(T2M_VOCAB * 8) as *i64 120 var last_mistakes: i64 = 0 121 var e: i64 = 0 122 while e < epochs { 123 var mistakes: i64 = 0 124 var n: i64 = 0 125 while n < ncap { 126 t2m_bow(cap_ptr[n] as *u8, bow) 127 let pred: i64 = t2m_predict(W, bow) 128 let lab: i64 = labels[n] 129 if pred != lab { 130 mistakes = mistakes + 1 131 var w: i64 = 0 132 while w < T2M_VOCAB { 133 W[lab * T2M_VOCAB + w] = W[lab * T2M_VOCAB + w] + bow[w] 134 W[pred * T2M_VOCAB + w] = W[pred * T2M_VOCAB + w] - bow[w] 135 w = w + 1 136 } 137 } 138 n = n + 1 139 } 140 last_mistakes = mistakes 141 e = e + 1 142 } 143 return last_mistakes 144} 145// caption -> predicted motion class (the learned encoder). 146func t2m_encode(W: *i64, cap: *u8) -> i64 { 147 let bow: *i64 = sys_mmap(T2M_VOCAB * 8) as *i64 148 t2m_bow(cap, bow) 149 return t2m_predict(W, bow) 150} 151 152// ---- class-conditioned generation: per-class token-transition table from that class's corpus tokens ----- 153// trans_c is MN_K*MN_K for ONE class, built from tokens[class*MN_FR .. +MN_FR). 154func t2m_class_bigram(tokens: *i64, cls: i64, trans_c: *i64) -> i64 { 155 var i: i64 = 0 156 while i < MN_K * MN_K { trans_c[i] = 0; i = i + 1 } 157 var f: i64 = 0 158 while f < MN_FR - 1 { 159 let a: i64 = tokens[cls * MN_FR + f] 160 let b: i64 = tokens[cls * MN_FR + f + 1] 161 trans_c[a * MN_K + b] = trans_c[a * MN_K + b] + 1 162 f = f + 1 163 } 164 return 0 165}