1use crate::optimizers::Optimizer;
6use crate::random::{random_array, unit};
7use ndarray::{Array1, Array2};
8use ndarray_rand::rand_distr::Uniform;
9use std::collections::HashMap;
10
11#[derive(Clone, Debug)]
15pub struct TextVocabulary {
16 char_to_idx: HashMap<char, usize>,
17 idx_to_char: HashMap<usize, char>,
18 vocab_size: usize,
19}
20
21impl TextVocabulary {
22 pub fn from_text(text: &str) -> Self {
24 let mut chars: Vec<char> = text
25 .chars()
26 .collect::<std::collections::HashSet<_>>()
27 .into_iter()
28 .collect();
29 chars.sort();
30
31 let vocab_size = chars.len();
32 let char_to_idx: HashMap<char, usize> =
33 chars.iter().enumerate().map(|(i, &c)| (c, i)).collect();
34 let idx_to_char: HashMap<usize, char> =
35 chars.iter().enumerate().map(|(i, &c)| (i, c)).collect();
36
37 Self {
38 char_to_idx,
39 idx_to_char,
40 vocab_size,
41 }
42 }
43
44 pub fn from_chars(chars: &[char]) -> Self {
46 let vocab_size = chars.len();
47 let char_to_idx: HashMap<char, usize> =
48 chars.iter().enumerate().map(|(i, &c)| (c, i)).collect();
49 let idx_to_char: HashMap<usize, char> =
50 chars.iter().enumerate().map(|(i, &c)| (i, c)).collect();
51
52 Self {
53 char_to_idx,
54 idx_to_char,
55 vocab_size,
56 }
57 }
58
59 pub fn char_to_index(&self, ch: char) -> Option<usize> {
61 self.char_to_idx.get(&ch).copied()
62 }
63
64 pub fn index_to_char(&self, idx: usize) -> Option<char> {
66 self.idx_to_char.get(&idx).copied()
67 }
68
69 pub fn size(&self) -> usize {
71 self.vocab_size
72 }
73
74 pub fn contains(&self, ch: char) -> bool {
76 self.char_to_idx.contains_key(&ch)
77 }
78
79 pub fn chars(&self) -> Vec<char> {
81 let mut chars: Vec<_> = self.idx_to_char.iter().collect();
82 chars.sort_by_key(|(idx, _)| *idx);
83 chars.into_iter().map(|(_, &ch)| ch).collect()
84 }
85
86 pub fn encode(&self, text: &str) -> Vec<usize> {
88 text.chars()
89 .filter_map(|ch| self.char_to_index(ch))
90 .collect()
91 }
92
93 pub fn decode(&self, indices: &[usize]) -> String {
95 indices
96 .iter()
97 .filter_map(|&idx| self.index_to_char(idx))
98 .collect()
99 }
100}
101
102#[derive(Clone, Debug)]
104pub struct EmbeddingGradients {
105 pub weight: Array2<f64>,
106}
107
108#[derive(Clone, Debug)]
112pub struct CharacterEmbedding {
113 pub weight: Array2<f64>, vocab_size: usize,
115 embed_dim: usize,
116 input_cache: Option<Vec<usize>>,
117}
118
119impl CharacterEmbedding {
120 pub fn new(vocab_size: usize, embed_dim: usize) -> Self {
122 let scale = (1.0 / embed_dim as f64).sqrt();
123 let weight = random_array((vocab_size, embed_dim), Uniform::new(-scale, scale));
124
125 Self {
126 weight,
127 vocab_size,
128 embed_dim,
129 input_cache: None,
130 }
131 }
132
133 pub fn new_zeros(vocab_size: usize, embed_dim: usize) -> Self {
135 Self {
136 weight: Array2::zeros((vocab_size, embed_dim)),
137 vocab_size,
138 embed_dim,
139 input_cache: None,
140 }
141 }
142
143 pub fn from_weights(weight: Array2<f64>) -> Self {
145 let (vocab_size, embed_dim) = weight.dim();
146 Self {
147 weight,
148 vocab_size,
149 embed_dim,
150 input_cache: None,
151 }
152 }
153
154 pub fn embed_dim(&self) -> usize {
156 self.embed_dim
157 }
158
159 pub fn vocab_size(&self) -> usize {
161 self.vocab_size
162 }
163
164 pub fn lookup(&self, char_idx: usize) -> Array1<f64> {
166 assert!(
167 char_idx < self.vocab_size,
168 "Index {} out of vocabulary size {}",
169 char_idx,
170 self.vocab_size
171 );
172 self.weight.row(char_idx).to_owned()
173 }
174
175 pub fn forward(&mut self, char_indices: &[usize]) -> Array2<f64> {
178 self.input_cache = Some(char_indices.to_vec());
179
180 let seq_len = char_indices.len();
181 let mut output = Array2::zeros((seq_len, self.embed_dim));
182
183 for (i, &idx) in char_indices.iter().enumerate() {
184 assert!(
185 idx < self.vocab_size,
186 "Index {} out of vocabulary size {}",
187 idx,
188 self.vocab_size
189 );
190 output.row_mut(i).assign(&self.weight.row(idx));
191 }
192
193 output
194 }
195
196 pub fn backward(&self, grad_output: &Array2<f64>) -> EmbeddingGradients {
199 let indices = self
200 .input_cache
201 .as_ref()
202 .expect("No cached input for backward pass");
203
204 let mut weight_grad = Array2::zeros((self.vocab_size, self.embed_dim));
205
206 for (i, &idx) in indices.iter().enumerate() {
207 for j in 0..self.embed_dim {
208 weight_grad[[idx, j]] += grad_output[[i, j]];
209 }
210 }
211
212 EmbeddingGradients {
213 weight: weight_grad,
214 }
215 }
216
217 pub fn update_parameters<O: Optimizer>(
219 &mut self,
220 gradients: &EmbeddingGradients,
221 optimizer: &mut O,
222 prefix: &str,
223 ) {
224 optimizer.update(
225 &format!("{}_weight", prefix),
226 &mut self.weight,
227 &gradients.weight,
228 );
229 }
230
231 pub fn num_parameters(&self) -> usize {
233 self.weight.len()
234 }
235}
236
237pub fn sample_with_temperature(logits: &Array1<f64>, temperature: f64) -> usize {
241 assert!(temperature > 0.0, "Temperature must be positive");
242
243 let scaled: Vec<f64> = logits.iter().map(|&x| x / temperature).collect();
245
246 let max_val = scaled.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
248 let exp_vals: Vec<f64> = scaled.iter().map(|&x| (x - max_val).exp()).collect();
249 let sum: f64 = exp_vals.iter().sum();
250 let probs: Vec<f64> = exp_vals.iter().map(|&x| x / sum).collect();
251
252 let mut rng_val = unit();
254 for (i, &prob) in probs.iter().enumerate() {
255 rng_val -= prob;
256 if rng_val <= 0.0 {
257 return i;
258 }
259 }
260
261 probs.len() - 1
262}
263
264pub fn sample_top_k(logits: &Array1<f64>, k: usize, temperature: f64) -> usize {
268 assert!(k > 0, "k must be positive");
269 assert!(temperature > 0.0, "Temperature must be positive");
270
271 let k = k.min(logits.len());
272
273 let mut indexed: Vec<(usize, f64)> = logits.iter().enumerate().map(|(i, &v)| (i, v)).collect();
275 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
276
277 let top_k: Vec<(usize, f64)> = indexed.into_iter().take(k).collect();
279
280 let scaled: Vec<f64> = top_k.iter().map(|(_, v)| v / temperature).collect();
282 let max_val = scaled.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
283 let exp_vals: Vec<f64> = scaled.iter().map(|&x| (x - max_val).exp()).collect();
284 let sum: f64 = exp_vals.iter().sum();
285 let probs: Vec<f64> = exp_vals.iter().map(|&x| x / sum).collect();
286
287 let mut rng_val = unit();
289 for (i, &prob) in probs.iter().enumerate() {
290 rng_val -= prob;
291 if rng_val <= 0.0 {
292 return top_k[i].0;
293 }
294 }
295
296 top_k[k - 1].0
297}
298
299pub fn sample_nucleus(logits: &Array1<f64>, p: f64, temperature: f64) -> usize {
303 assert!(p > 0.0 && p <= 1.0, "p must be in (0, 1]");
304 assert!(temperature > 0.0, "Temperature must be positive");
305
306 let scaled: Vec<f64> = logits.iter().map(|&x| x / temperature).collect();
308 let max_val = scaled.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
309 let exp_vals: Vec<f64> = scaled.iter().map(|&x| (x - max_val).exp()).collect();
310 let sum: f64 = exp_vals.iter().sum();
311 let probs: Vec<f64> = exp_vals.iter().map(|&x| x / sum).collect();
312
313 let mut indexed: Vec<(usize, f64)> = probs.iter().enumerate().map(|(i, &v)| (i, v)).collect();
315 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
316
317 let mut cumulative = 0.0;
319 let mut nucleus: Vec<(usize, f64)> = Vec::new();
320 for (idx, prob) in indexed {
321 cumulative += prob;
322 nucleus.push((idx, prob));
323 if cumulative >= p {
324 break;
325 }
326 }
327
328 let nucleus_sum: f64 = nucleus.iter().map(|(_, prob)| prob).sum();
330 let nucleus_probs: Vec<f64> = nucleus.iter().map(|(_, prob)| prob / nucleus_sum).collect();
331
332 let mut rng_val = unit();
334 for (i, &prob) in nucleus_probs.iter().enumerate() {
335 rng_val -= prob;
336 if rng_val <= 0.0 {
337 return nucleus[i].0;
338 }
339 }
340
341 nucleus.last().map(|(idx, _)| *idx).unwrap_or(0)
342}
343
344pub fn argmax(logits: &Array1<f64>) -> usize {
346 logits
347 .iter()
348 .enumerate()
349 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
350 .map(|(idx, _)| idx)
351 .unwrap_or(0)
352}
353
354pub fn softmax(logits: &Array1<f64>) -> Array1<f64> {
356 let max_val = logits.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
357 let exp_vals: Array1<f64> = logits.mapv(|x| (x - max_val).exp());
358 let sum: f64 = exp_vals.sum();
359 exp_vals / sum
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use ndarray::arr1;
366
367 #[test]
368 fn test_vocabulary_from_text() {
369 let vocab = TextVocabulary::from_text("hello");
370 assert_eq!(vocab.size(), 4); assert!(vocab.contains('h'));
372 assert!(vocab.contains('l'));
373 assert!(!vocab.contains('x'));
374 }
375
376 #[test]
377 fn test_vocabulary_encode_decode() {
378 let vocab = TextVocabulary::from_text("abc");
379 let encoded = vocab.encode("cab");
380 let decoded = vocab.decode(&encoded);
381 assert_eq!(decoded, "cab");
382 }
383
384 #[test]
385 fn test_embedding_forward() {
386 let mut emb = CharacterEmbedding::new(10, 8);
387 let output = emb.forward(&[0, 3, 5]);
388 assert_eq!(output.shape(), &[3, 8]);
389 }
390
391 #[test]
392 fn test_embedding_lookup() {
393 let emb = CharacterEmbedding::new(10, 8);
394 let vec = emb.lookup(5);
395 assert_eq!(vec.len(), 8);
396 }
397
398 #[test]
399 fn test_sample_with_temperature() {
400 let logits = arr1(&[1.0, 2.0, 3.0]);
401 let idx = sample_with_temperature(&logits, 1.0);
402 assert!(idx < 3);
403 }
404
405 #[test]
406 fn test_sample_top_k() {
407 let logits = arr1(&[1.0, 5.0, 2.0, 0.5]);
408 let idx = sample_top_k(&logits, 2, 1.0);
409 assert!(idx == 1 || idx == 2);
411 }
412
413 #[test]
414 fn test_sample_nucleus() {
415 let logits = arr1(&[0.0, 10.0, 0.0]); let idx = sample_nucleus(&logits, 0.9, 1.0);
417 assert_eq!(idx, 1); }
419
420 #[test]
421 fn test_argmax() {
422 let logits = arr1(&[1.0, 5.0, 2.0]);
423 assert_eq!(argmax(&logits), 1);
424 }
425
426 #[test]
427 fn test_softmax() {
428 let logits = arr1(&[1.0, 2.0, 3.0]);
429 let probs = softmax(&logits);
430 assert!((probs.sum() - 1.0).abs() < 1e-6);
431 }
432}