1use crate::{
4 Codebook, CodebookConfig, CodebookStats, LayerCodebook, LayerType, NeuralError, Result,
5};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TrainingConfig {
11 pub max_iterations: usize,
13 pub convergence_threshold: f32,
15 pub batch_size: usize,
17 pub learning_rate: f32,
19 pub use_kmeans_init: bool,
21 pub num_restarts: usize,
23 pub seed: Option<u64>,
25}
26
27impl Default for TrainingConfig {
28 fn default() -> Self {
29 Self {
30 max_iterations: 100,
31 convergence_threshold: 1e-5,
32 batch_size: 4096,
33 learning_rate: 0.01,
34 use_kmeans_init: true,
35 num_restarts: 3,
36 seed: None,
37 }
38 }
39}
40
41pub struct CodebookTrainer {
43 config: TrainingConfig,
44}
45
46impl CodebookTrainer {
47 pub fn new(config: TrainingConfig) -> Self {
49 Self { config }
50 }
51
52 pub fn train(&self, codebook_config: CodebookConfig, data: &[f32]) -> Result<Codebook> {
54 let dim = codebook_config.centroid_dim;
55 let num_vectors = data.len() / dim;
56
57 if !data.len().is_multiple_of(dim) {
58 return Err(NeuralError::TrainingError(format!(
59 "Data length {} not divisible by centroid dim {}",
60 data.len(),
61 dim
62 )));
63 }
64
65 if num_vectors < codebook_config.num_centroids {
66 return Err(NeuralError::TrainingError(format!(
67 "Need at least {} vectors, got {}",
68 codebook_config.num_centroids, num_vectors
69 )));
70 }
71
72 let mut best_codebook = None;
73 let mut best_mse = f32::INFINITY;
74
75 for restart in 0..self.config.num_restarts {
76 let seed = self.config.seed.map(|s| s + restart as u64);
77 let result = self.train_single(&codebook_config, data, seed)?;
78
79 if result.1 < best_mse {
80 best_mse = result.1;
81 best_codebook = Some(result.0);
82 }
83 }
84
85 best_codebook.ok_or_else(|| NeuralError::TrainingError("Training failed".into()))
86 }
87
88 fn train_single(
90 &self,
91 config: &CodebookConfig,
92 data: &[f32],
93 seed: Option<u64>,
94 ) -> Result<(Codebook, f32)> {
95 let dim = config.centroid_dim;
96 let num_vectors = data.len() / dim;
97
98 let mut centroids = if self.config.use_kmeans_init {
100 self.kmeans_pp_init(config.num_centroids, data, dim, seed)
101 } else {
102 self.random_init(config.num_centroids, data, dim, seed)
103 };
104
105 let mut prev_mse = f32::INFINITY;
106 let mut iterations = 0;
107
108 for iter in 0..self.config.max_iterations {
110 iterations = iter + 1;
111
112 let assignments = self.assign_clusters(¢roids, data, dim);
114
115 let new_centroids =
117 self.update_centroids(&assignments, data, config.num_centroids, dim);
118 centroids = new_centroids;
119
120 let mse = self.compute_mse(¢roids, &assignments, data, dim);
122
123 if (prev_mse - mse).abs() < self.config.convergence_threshold {
125 break;
126 }
127 prev_mse = mse;
128 }
129
130 let assignments = self.assign_clusters(¢roids, data, dim);
132 let final_mse = self.compute_mse(¢roids, &assignments, data, dim);
133 let usage = self.compute_usage(&assignments, config.num_centroids);
134
135 let stats = CodebookStats {
136 mse: final_mse,
137 iterations,
138 usage_distribution: usage,
139 samples_seen: num_vectors,
140 };
141
142 let mut codebook = Codebook::from_centroids(config.clone(), centroids, "trained")?;
143 codebook.stats = stats;
144
145 Ok((codebook, final_mse))
146 }
147
148 fn kmeans_pp_init(&self, k: usize, data: &[f32], dim: usize, seed: Option<u64>) -> Vec<f32> {
150 use rand::prelude::*;
151
152 let mut rng = match seed {
153 Some(s) => StdRng::seed_from_u64(s),
154 None => StdRng::from_entropy(),
155 };
156
157 let num_vectors = data.len() / dim;
158 let mut centroids = Vec::with_capacity(k * dim);
159 let mut distances = vec![f32::INFINITY; num_vectors];
160
161 let first_idx = rng.gen_range(0..num_vectors);
163 centroids.extend_from_slice(&data[first_idx * dim..(first_idx + 1) * dim]);
164
165 for _ in 1..k {
167 for i in 0..num_vectors {
169 let vec = &data[i * dim..(i + 1) * dim];
170 let last_centroid = ¢roids[centroids.len() - dim..];
171 let dist = self.squared_distance(vec, last_centroid);
172 distances[i] = distances[i].min(dist);
173 }
174
175 let total: f32 = distances.iter().sum();
177 let threshold = rng.gen::<f32>() * total;
178
179 let mut cumsum = 0.0;
180 let mut chosen = 0;
181 for (i, &d) in distances.iter().enumerate() {
182 cumsum += d;
183 if cumsum >= threshold {
184 chosen = i;
185 break;
186 }
187 }
188
189 centroids.extend_from_slice(&data[chosen * dim..(chosen + 1) * dim]);
190 }
191
192 centroids
193 }
194
195 fn random_init(&self, k: usize, data: &[f32], dim: usize, seed: Option<u64>) -> Vec<f32> {
197 use rand::prelude::*;
198
199 let mut rng = match seed {
200 Some(s) => StdRng::seed_from_u64(s),
201 None => StdRng::from_entropy(),
202 };
203
204 let num_vectors = data.len() / dim;
205 let mut centroids = Vec::with_capacity(k * dim);
206
207 let indices: Vec<usize> = (0..num_vectors).choose_multiple(&mut rng, k);
208 for idx in indices {
209 centroids.extend_from_slice(&data[idx * dim..(idx + 1) * dim]);
210 }
211
212 centroids
213 }
214
215 fn assign_clusters(&self, centroids: &[f32], data: &[f32], dim: usize) -> Vec<usize> {
217 let num_vectors = data.len() / dim;
218 let num_centroids = centroids.len() / dim;
219
220 (0..num_vectors)
221 .map(|i| {
222 let vec = &data[i * dim..(i + 1) * dim];
223 let mut best_idx = 0;
224 let mut best_dist = f32::INFINITY;
225
226 for j in 0..num_centroids {
227 let centroid = ¢roids[j * dim..(j + 1) * dim];
228 let dist = self.squared_distance(vec, centroid);
229 if dist < best_dist {
230 best_dist = dist;
231 best_idx = j;
232 }
233 }
234
235 best_idx
236 })
237 .collect()
238 }
239
240 fn update_centroids(
242 &self,
243 assignments: &[usize],
244 data: &[f32],
245 k: usize,
246 dim: usize,
247 ) -> Vec<f32> {
248 let mut sums = vec![0.0f32; k * dim];
249 let mut counts = vec![0usize; k];
250
251 for (i, &cluster) in assignments.iter().enumerate() {
252 counts[cluster] += 1;
253 for d in 0..dim {
254 sums[cluster * dim + d] += data[i * dim + d];
255 }
256 }
257
258 for j in 0..k {
260 if counts[j] > 0 {
261 for d in 0..dim {
262 sums[j * dim + d] /= counts[j] as f32;
263 }
264 }
265 }
266
267 sums
268 }
269
270 fn compute_mse(
272 &self,
273 centroids: &[f32],
274 assignments: &[usize],
275 data: &[f32],
276 dim: usize,
277 ) -> f32 {
278 let total_dist: f32 = assignments
279 .iter()
280 .enumerate()
281 .map(|(i, &cluster)| {
282 let vec = &data[i * dim..(i + 1) * dim];
283 let centroid = ¢roids[cluster * dim..(cluster + 1) * dim];
284 self.squared_distance(vec, centroid)
285 })
286 .sum();
287
288 total_dist / assignments.len() as f32
289 }
290
291 fn compute_usage(&self, assignments: &[usize], k: usize) -> (f32, f32, f32) {
293 let mut counts = vec![0usize; k];
294 for &cluster in assignments {
295 counts[cluster] += 1;
296 }
297
298 let min = *counts.iter().min().unwrap_or(&0) as f32;
299 let max = *counts.iter().max().unwrap_or(&0) as f32;
300 let mean = counts.iter().sum::<usize>() as f32 / k as f32;
301
302 (min, max, mean)
303 }
304
305 fn squared_distance(&self, a: &[f32], b: &[f32]) -> f32 {
307 a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum()
308 }
309
310 pub fn train_all(
312 &self,
313 model_id: &str,
314 data_by_layer: &[(LayerType, Vec<f32>)],
315 ) -> Result<LayerCodebook> {
316 let mut layer_codebook = LayerCodebook::new(model_id);
317
318 for (layer_type, data) in data_by_layer {
319 let config = layer_type.default_config();
320 let codebook = self.train(config, data)?;
321 layer_codebook.add(*layer_type, codebook);
322 }
323
324 Ok(layer_codebook)
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_trainer() {
334 let config = TrainingConfig {
335 max_iterations: 10,
336 num_restarts: 1,
337 ..Default::default()
338 };
339
340 let trainer = CodebookTrainer::new(config);
341
342 let codebook_config = CodebookConfig {
344 num_centroids: 4,
345 centroid_dim: 2,
346 index_bits: 2,
347 product_quantization: false,
348 pq_subspaces: 1,
349 };
350
351 let data: Vec<f32> = vec![
353 0.0, 0.0, 0.1, 0.1, -0.1, 0.1, 1.0, 0.0, 1.1, 0.1, 0.9, -0.1, 0.0, 1.0, 0.1, 1.1, -0.1, 0.9, 1.0, 1.0, 1.1, 1.1, 0.9, 0.9,
358 ];
359
360 let codebook = trainer.train(codebook_config, &data).unwrap();
361
362 assert_eq!(codebook.config.num_centroids, 4);
363 assert!(codebook.stats.mse < 0.1);
364 }
365
366 #[test]
367 fn test_kmeans_pp() {
368 let trainer = CodebookTrainer::new(TrainingConfig::default());
369
370 let data: Vec<f32> = (0..200).map(|i| (i as f32 / 10.0).sin()).collect();
371
372 let centroids = trainer.kmeans_pp_init(10, &data, 2, Some(42));
373 assert_eq!(centroids.len(), 20); }
375}