Skip to main content

tenflowers_dataset/dataloader/
samplers.rs

1//! Sampling Strategies and Implementations
2//!
3//! This module provides various sampling strategies for data loading including
4//! sequential, random, distributed, stratified, and importance-based sampling.
5
6use std::collections::HashMap;
7use tenflowers_core::{Result, TensorError};
8
9/// Trait for sampling strategies
10pub trait Sampler: Send + Sync {
11    /// Generate an iterator over sample indices
12    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send>;
13
14    /// Check if this sampler produces indices in random order
15    fn is_random(&self) -> bool;
16
17    /// Set random seed if applicable
18    fn set_seed(&mut self, _seed: Option<u64>) {}
19}
20
21/// Sequential sampler that iterates through indices in order
22#[derive(Debug, Clone)]
23pub struct SequentialSampler {
24    start: usize,
25    end: Option<usize>,
26}
27
28impl SequentialSampler {
29    pub fn new() -> Self {
30        Self {
31            start: 0,
32            end: None,
33        }
34    }
35
36    pub fn with_range(start: usize, end: usize) -> Self {
37        Self {
38            start,
39            end: Some(end),
40        }
41    }
42}
43
44impl Default for SequentialSampler {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl Sampler for SequentialSampler {
51    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send> {
52        let end = self.end.unwrap_or(len).min(len);
53        Box::new(self.start..end)
54    }
55
56    fn is_random(&self) -> bool {
57        false
58    }
59}
60
61/// Random sampler that generates random indices
62#[derive(Debug, Clone)]
63pub struct RandomSampler {
64    replacement: bool,
65    seed: Option<u64>,
66}
67
68impl RandomSampler {
69    pub fn new() -> Self {
70        Self {
71            replacement: false,
72            seed: None,
73        }
74    }
75
76    pub fn with_replacement() -> Self {
77        Self {
78            replacement: true,
79            seed: None,
80        }
81    }
82
83    pub fn with_seed(seed: u64) -> Self {
84        Self {
85            replacement: false,
86            seed: Some(seed),
87        }
88    }
89}
90
91impl Default for RandomSampler {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl Sampler for RandomSampler {
98    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send> {
99        // Simple random implementation using system time as seed
100        let seed = self.seed.unwrap_or_else(|| {
101            std::time::SystemTime::now()
102                .duration_since(std::time::UNIX_EPOCH)
103                .expect("system time before UNIX_EPOCH")
104                .as_secs()
105        });
106
107        if self.replacement {
108            // With replacement - can repeat indices
109            let mut indices = Vec::with_capacity(len);
110            let mut state = seed;
111            for _ in 0..len {
112                // Simple LCG random number generator
113                state = state.wrapping_mul(1103515245).wrapping_add(12345);
114                indices.push((state as usize) % len);
115            }
116            Box::new(indices.into_iter())
117        } else {
118            // Without replacement - shuffle indices
119            let mut indices: Vec<usize> = (0..len).collect();
120            let mut state = seed;
121
122            // Fisher-Yates shuffle
123            for i in (1..indices.len()).rev() {
124                state = state.wrapping_mul(1103515245).wrapping_add(12345);
125                let j = (state as usize) % (i + 1);
126                indices.swap(i, j);
127            }
128
129            Box::new(indices.into_iter())
130        }
131    }
132
133    fn is_random(&self) -> bool {
134        true
135    }
136
137    fn set_seed(&mut self, seed: Option<u64>) {
138        self.seed = seed;
139    }
140}
141
142/// Distributed sampler for multi-node training
143#[derive(Debug, Clone)]
144pub struct DistributedSampler {
145    /// Number of distributed processes (world size)
146    num_replicas: usize,
147    /// Rank of current process
148    rank: usize,
149    /// Number of epochs (for shuffling consistency)
150    epoch: usize,
151    /// Whether to shuffle the data
152    shuffle: bool,
153    /// Random seed for shuffling
154    seed: Option<u64>,
155    /// Whether to drop last incomplete batch
156    drop_last: bool,
157}
158
159impl DistributedSampler {
160    /// Create a new distributed sampler
161    pub fn new(num_replicas: usize, rank: usize) -> Result<Self> {
162        if rank >= num_replicas {
163            return Err(TensorError::invalid_argument(format!(
164                "Rank {rank} must be less than num_replicas {num_replicas}"
165            )));
166        }
167
168        Ok(Self {
169            num_replicas,
170            rank,
171            epoch: 0,
172            shuffle: true,
173            seed: None,
174            drop_last: false,
175        })
176    }
177
178    /// Set whether to shuffle the data (default: true)
179    pub fn with_shuffle(mut self, shuffle: bool) -> Self {
180        self.shuffle = shuffle;
181        self
182    }
183
184    /// Set random seed for shuffling
185    pub fn with_seed(mut self, seed: u64) -> Self {
186        self.seed = Some(seed);
187        self
188    }
189
190    /// Set whether to drop the last incomplete batch
191    pub fn with_drop_last(mut self, drop_last: bool) -> Self {
192        self.drop_last = drop_last;
193        self
194    }
195
196    /// Set the current epoch (affects shuffling for deterministic behavior)
197    pub fn set_epoch(&mut self, epoch: usize) {
198        self.epoch = epoch;
199    }
200
201    /// Get the current epoch
202    pub fn epoch(&self) -> usize {
203        self.epoch
204    }
205
206    /// Get the rank of this sampler
207    pub fn rank(&self) -> usize {
208        self.rank
209    }
210
211    /// Get the number of replicas
212    pub fn num_replicas(&self) -> usize {
213        self.num_replicas
214    }
215
216    /// Calculate the number of samples per replica
217    fn samples_per_replica(&self, total_size: usize) -> usize {
218        if self.drop_last {
219            total_size / self.num_replicas
220        } else {
221            (total_size + self.num_replicas - 1) / self.num_replicas
222        }
223    }
224
225    /// Calculate the total size after padding (if needed)
226    fn padded_size(&self, total_size: usize) -> usize {
227        if self.drop_last {
228            (total_size / self.num_replicas) * self.num_replicas
229        } else {
230            self.samples_per_replica(total_size) * self.num_replicas
231        }
232    }
233}
234
235impl Sampler for DistributedSampler {
236    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send> {
237        let mut indices: Vec<usize> = (0..len).collect();
238
239        // Shuffle indices if requested
240        if self.shuffle {
241            let seed = self.seed.unwrap_or_else(|| {
242                std::time::SystemTime::now()
243                    .duration_since(std::time::UNIX_EPOCH)
244                    .expect("system time before UNIX_EPOCH")
245                    .as_secs()
246            });
247
248            // Use epoch in seed for deterministic shuffling across epochs
249            let effective_seed = seed.wrapping_add(self.epoch as u64);
250            let mut state = effective_seed;
251
252            // Fisher-Yates shuffle
253            for i in (1..indices.len()).rev() {
254                state = state.wrapping_mul(1103515245).wrapping_add(12345);
255                let j = (state as usize) % (i + 1);
256                indices.swap(i, j);
257            }
258        }
259
260        let samples_per_replica = self.samples_per_replica(len);
261        let padded_size = self.padded_size(len);
262
263        // Pad indices if necessary (replicate indices to ensure even distribution)
264        if !self.drop_last && padded_size > len {
265            let padding_needed = padded_size - len;
266            for i in 0..padding_needed {
267                indices.push(indices[i % len]);
268            }
269        }
270
271        // Subsample for this rank
272        let start_idx = self.rank * samples_per_replica;
273        let end_idx = ((self.rank + 1) * samples_per_replica).min(indices.len());
274
275        let rank_indices = if start_idx < indices.len() {
276            indices[start_idx..end_idx].to_vec()
277        } else {
278            Vec::new()
279        };
280
281        Box::new(rank_indices.into_iter())
282    }
283
284    fn is_random(&self) -> bool {
285        self.shuffle
286    }
287
288    fn set_seed(&mut self, seed: Option<u64>) {
289        self.seed = seed;
290    }
291}
292
293/// Stratified sampler that maintains class balance during sampling
294#[derive(Debug, Clone)]
295pub struct StratifiedSampler {
296    /// Class labels for each sample in the dataset
297    class_labels: Vec<usize>,
298    /// Number of samples to draw from each class
299    samples_per_class: Option<usize>,
300    /// Whether to sample with replacement
301    replacement: bool,
302    /// Random seed for reproducible sampling
303    seed: Option<u64>,
304    /// Whether to shuffle within each class
305    shuffle: bool,
306}
307
308impl StratifiedSampler {
309    /// Create a new stratified sampler
310    pub fn new(class_labels: Vec<usize>) -> Self {
311        Self {
312            class_labels,
313            samples_per_class: None,
314            replacement: false,
315            seed: None,
316            shuffle: true,
317        }
318    }
319
320    /// Set the number of samples to draw from each class
321    pub fn with_samples_per_class(mut self, samples_per_class: usize) -> Self {
322        self.samples_per_class = Some(samples_per_class);
323        self
324    }
325
326    /// Enable sampling with replacement
327    pub fn with_replacement(mut self) -> Self {
328        self.replacement = true;
329        self
330    }
331
332    /// Set random seed for reproducible sampling
333    pub fn with_seed(mut self, seed: u64) -> Self {
334        self.seed = Some(seed);
335        self
336    }
337
338    /// Set whether to shuffle samples within each class
339    pub fn with_shuffle(mut self, shuffle: bool) -> Self {
340        self.shuffle = shuffle;
341        self
342    }
343
344    /// Get class distribution
345    pub fn class_distribution(&self) -> HashMap<usize, usize> {
346        let mut counts = HashMap::new();
347        for &label in &self.class_labels {
348            *counts.entry(label).or_insert(0) += 1;
349        }
350        counts
351    }
352
353    /// Get unique classes
354    pub fn num_classes(&self) -> usize {
355        self.class_distribution().len()
356    }
357}
358
359impl Sampler for StratifiedSampler {
360    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send> {
361        if self.class_labels.len() != len {
362            // If class labels don't match dataset length, fall back to sequential
363            return Box::new((0..len).collect::<Vec<_>>().into_iter());
364        }
365
366        // Group indices by class
367        let mut class_indices: HashMap<usize, Vec<usize>> = HashMap::new();
368        for (idx, &class_label) in self.class_labels.iter().enumerate() {
369            class_indices.entry(class_label).or_default().push(idx);
370        }
371
372        let seed = self.seed.unwrap_or_else(|| {
373            std::time::SystemTime::now()
374                .duration_since(std::time::UNIX_EPOCH)
375                .expect("system time before UNIX_EPOCH")
376                .as_secs()
377        });
378
379        let mut result_indices = Vec::new();
380        let mut rng_state = seed;
381
382        // Determine samples per class
383        let samples_per_class = if let Some(spc) = self.samples_per_class {
384            spc
385        } else {
386            // Use minimum class size to ensure balanced sampling
387            class_indices
388                .values()
389                .map(|indices| indices.len())
390                .min()
391                .unwrap_or(0)
392        };
393
394        // Sample from each class
395        for (_, mut indices) in class_indices {
396            // Shuffle indices within class if requested
397            if self.shuffle {
398                // Fisher-Yates shuffle
399                for i in (1..indices.len()).rev() {
400                    rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
401                    let j = (rng_state as usize) % (i + 1);
402                    indices.swap(i, j);
403                }
404            }
405
406            if self.replacement {
407                // Sample with replacement
408                for _ in 0..samples_per_class {
409                    rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
410                    let idx = (rng_state as usize) % indices.len();
411                    result_indices.push(indices[idx]);
412                }
413            } else {
414                // Sample without replacement
415                let sample_count = samples_per_class.min(indices.len());
416                result_indices.extend_from_slice(&indices[..sample_count]);
417            }
418        }
419
420        // Final shuffle of all selected indices
421        if self.shuffle {
422            for i in (1..result_indices.len()).rev() {
423                rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
424                let j = (rng_state as usize) % (i + 1);
425                result_indices.swap(i, j);
426            }
427        }
428
429        Box::new(result_indices.into_iter())
430    }
431
432    fn is_random(&self) -> bool {
433        self.shuffle
434    }
435
436    fn set_seed(&mut self, seed: Option<u64>) {
437        self.seed = seed;
438    }
439}
440
441/// Importance sampler for priority-based sampling with dynamic weight updates
442#[derive(Debug, Clone)]
443pub struct ImportanceSampler {
444    /// Importance weights for each sample
445    weights: Vec<f64>,
446    /// Whether to normalize weights to probabilities
447    normalize: bool,
448    /// Random seed for reproducible sampling
449    seed: Option<u64>,
450    /// Temperature for weight softening (higher = more uniform)
451    temperature: f64,
452}
453
454impl ImportanceSampler {
455    /// Create a new importance sampler with uniform weights
456    pub fn new(dataset_size: usize) -> Self {
457        Self {
458            weights: vec![1.0; dataset_size],
459            normalize: true,
460            seed: None,
461            temperature: 1.0,
462        }
463    }
464
465    /// Create importance sampler with initial weights
466    pub fn with_weights(weights: Vec<f64>) -> Self {
467        Self {
468            weights,
469            normalize: true,
470            seed: None,
471            temperature: 1.0,
472        }
473    }
474
475    /// Set whether to normalize weights to probabilities
476    pub fn with_normalize(mut self, normalize: bool) -> Self {
477        self.normalize = normalize;
478        self
479    }
480
481    /// Set random seed for reproducible sampling
482    pub fn with_seed(mut self, seed: u64) -> Self {
483        self.seed = Some(seed);
484        self
485    }
486
487    /// Set temperature for weight softening
488    pub fn with_temperature(mut self, temperature: f64) -> Self {
489        self.temperature = temperature;
490        self
491    }
492
493    /// Update importance weight for a specific sample
494    pub fn update_weight(&mut self, index: usize, weight: f64) {
495        if index < self.weights.len() {
496            self.weights[index] = weight;
497        }
498    }
499
500    /// Update multiple weights at once
501    pub fn update_weights(&mut self, updates: &[(usize, f64)]) {
502        for &(index, weight) in updates {
503            self.update_weight(index, weight);
504        }
505    }
506
507    /// Get current weights
508    pub fn weights(&self) -> &[f64] {
509        &self.weights
510    }
511
512    /// Compute effective probabilities after temperature scaling
513    fn compute_probabilities(&self) -> Vec<f64> {
514        let mut probs: Vec<f64> = self
515            .weights
516            .iter()
517            .map(|&w| (w / self.temperature).exp())
518            .collect();
519
520        if self.normalize {
521            let sum: f64 = probs.iter().sum();
522            if sum > 0.0 {
523                for p in &mut probs {
524                    *p /= sum;
525                }
526            } else {
527                // All weights are zero, use uniform distribution
528                let uniform_prob = 1.0 / probs.len() as f64;
529                probs.fill(uniform_prob);
530            }
531        }
532
533        probs
534    }
535}
536
537impl Sampler for ImportanceSampler {
538    fn sample_indices(&self, len: usize) -> Box<dyn Iterator<Item = usize> + Send> {
539        if self.weights.len() != len {
540            // If weights don't match dataset length, fall back to sequential
541            return Box::new((0..len).collect::<Vec<_>>().into_iter());
542        }
543
544        let probabilities = self.compute_probabilities();
545
546        // Convert probabilities to cumulative distribution
547        let mut cumulative = Vec::with_capacity(probabilities.len());
548        let mut sum = 0.0;
549        for &prob in &probabilities {
550            sum += prob;
551            cumulative.push(sum);
552        }
553
554        let seed = self.seed.unwrap_or_else(|| {
555            std::time::SystemTime::now()
556                .duration_since(std::time::UNIX_EPOCH)
557                .expect("system time before UNIX_EPOCH")
558                .as_secs()
559        });
560
561        let mut indices = Vec::with_capacity(len);
562        let mut rng_state = seed;
563
564        // Sample according to importance weights
565        for _ in 0..len {
566            rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
567            let random_val = (rng_state as f64) / (u64::MAX as f64);
568
569            // Binary search to find the index
570            let index = cumulative
571                .binary_search_by(|&x| {
572                    if x < random_val {
573                        std::cmp::Ordering::Less
574                    } else {
575                        std::cmp::Ordering::Greater
576                    }
577                })
578                .unwrap_or_else(|i| i);
579
580            indices.push(index.min(len - 1));
581        }
582
583        Box::new(indices.into_iter())
584    }
585
586    fn is_random(&self) -> bool {
587        true
588    }
589
590    fn set_seed(&mut self, seed: Option<u64>) {
591        self.seed = seed;
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[test]
600    fn test_sequential_sampler() {
601        let sampler = SequentialSampler::new();
602        let indices: Vec<usize> = sampler.sample_indices(5).collect();
603        assert_eq!(indices, vec![0, 1, 2, 3, 4]);
604        assert!(!sampler.is_random());
605    }
606
607    #[test]
608    fn test_sequential_sampler_with_range() {
609        let sampler = SequentialSampler::with_range(2, 5);
610        let indices: Vec<usize> = sampler.sample_indices(10).collect();
611        assert_eq!(indices, vec![2, 3, 4]);
612    }
613
614    #[test]
615    fn test_random_sampler() {
616        let sampler = RandomSampler::with_seed(42);
617        let indices: Vec<usize> = sampler.sample_indices(5).collect();
618        assert_eq!(indices.len(), 5);
619        assert!(sampler.is_random());
620
621        // Test reproducibility with same seed
622        let sampler2 = RandomSampler::with_seed(42);
623        let indices2: Vec<usize> = sampler2.sample_indices(5).collect();
624        assert_eq!(indices, indices2);
625    }
626
627    #[test]
628    fn test_random_sampler_with_replacement() {
629        let sampler = RandomSampler::with_replacement();
630        let indices: Vec<usize> = sampler.sample_indices(3).collect();
631        assert_eq!(indices.len(), 3);
632        // With replacement, indices can repeat
633    }
634
635    #[test]
636    fn test_distributed_sampler() {
637        let sampler = DistributedSampler::new(2, 0).expect("test: operation should succeed");
638        let indices: Vec<usize> = sampler.sample_indices(10).collect();
639        // Should get roughly half the indices for rank 0
640        assert!(indices.len() >= 4 && indices.len() <= 6);
641    }
642
643    #[test]
644    fn test_distributed_sampler_invalid_rank() {
645        let result = DistributedSampler::new(2, 2);
646        assert!(result.is_err());
647    }
648
649    #[test]
650    fn test_stratified_sampler() {
651        let class_labels = vec![0, 0, 1, 1, 2, 2];
652        let sampler = StratifiedSampler::new(class_labels.clone());
653
654        assert_eq!(sampler.num_classes(), 3);
655
656        let distribution = sampler.class_distribution();
657        assert_eq!(distribution[&0], 2);
658        assert_eq!(distribution[&1], 2);
659        assert_eq!(distribution[&2], 2);
660    }
661
662    #[test]
663    fn test_stratified_sampler_with_samples_per_class() {
664        let class_labels = vec![0, 0, 0, 1, 1, 1];
665        let sampler = StratifiedSampler::new(class_labels)
666            .with_samples_per_class(1)
667            .with_seed(42);
668
669        let indices: Vec<usize> = sampler.sample_indices(6).collect();
670        // Should get 1 sample from each class = 2 total samples
671        assert_eq!(indices.len(), 2);
672    }
673
674    #[test]
675    fn test_importance_sampler() {
676        let sampler = ImportanceSampler::new(5);
677        assert_eq!(sampler.weights().len(), 5);
678        assert!(sampler.weights().iter().all(|&w| w == 1.0));
679    }
680
681    #[test]
682    fn test_importance_sampler_with_weights() {
683        let weights = vec![1.0, 2.0, 3.0];
684        let sampler = ImportanceSampler::with_weights(weights.clone());
685        assert_eq!(sampler.weights(), &weights);
686    }
687
688    #[test]
689    fn test_importance_sampler_update_weight() {
690        let mut sampler = ImportanceSampler::new(3);
691        sampler.update_weight(1, 5.0);
692        assert_eq!(sampler.weights()[1], 5.0);
693    }
694
695    #[test]
696    fn test_importance_sampler_update_weights() {
697        let mut sampler = ImportanceSampler::new(3);
698        sampler.update_weights(&[(0, 2.0), (2, 4.0)]);
699        assert_eq!(sampler.weights()[0], 2.0);
700        assert_eq!(sampler.weights()[2], 4.0);
701    }
702}