Skip to main content

sklears_decomposition/dictionary_learning/
dictionary_metrics.rs

1//! Evaluation and metrics for dictionary learning
2
3use scirs2_core::ndarray::Array2;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use sklears_core::{error::Result, types::Float};
7
8/// Reconstruction error metrics
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct ReconstructionError {
12    pub mse: Float,
13    pub rmse: Float,
14    pub mae: Float,
15    pub relative_error: Float,
16}
17
18/// Sparsity metrics
19#[derive(Debug, Clone)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21pub struct SparsityMetrics {
22    pub l0_norm: Float,
23    pub l1_norm: Float,
24    pub sparsity_ratio: Float,
25    pub gini_coefficient: Float,
26}
27
28/// Coherence metrics for dictionary quality
29#[derive(Debug, Clone)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31pub struct CoherenceMetrics {
32    pub mutual_coherence: Float,
33    pub spark: usize,
34    pub condition_number: Float,
35}
36
37/// Dictionary quality assessment
38#[derive(Debug, Clone)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub struct DictionaryQuality {
41    pub reconstruction_error: ReconstructionError,
42    pub sparsity_metrics: SparsityMetrics,
43    pub coherence_metrics: CoherenceMetrics,
44}
45
46/// Dictionary metrics calculator
47#[derive(Debug, Clone)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49pub struct DictionaryMetrics;
50
51impl DictionaryMetrics {
52    pub fn new() -> Self {
53        Self
54    }
55
56    pub fn compute_reconstruction_error(
57        &self,
58        original: &Array2<Float>,
59        reconstructed: &Array2<Float>,
60    ) -> Result<ReconstructionError> {
61        let diff = original - reconstructed;
62        let squared_diff = diff.mapv(|x| x * x);
63        let mse = squared_diff.mean().unwrap_or(0.0);
64        let rmse = mse.sqrt();
65
66        let abs_diff = diff.mapv(|x| x.abs());
67        let mae = abs_diff.mean().unwrap_or(0.0);
68
69        let original_norm = original.mapv(|x| x * x).sum().sqrt();
70        let relative_error = if original_norm > 0.0 {
71            diff.mapv(|x| x * x).sum().sqrt() / original_norm
72        } else {
73            0.0
74        };
75
76        Ok(ReconstructionError {
77            mse,
78            rmse,
79            mae,
80            relative_error,
81        })
82    }
83
84    pub fn compute_sparsity_metrics(&self, codes: &Array2<Float>) -> Result<SparsityMetrics> {
85        let total_elements = codes.len() as Float;
86        let non_zero_count = codes.iter().filter(|&&x| x.abs() > 1e-10).count() as Float;
87
88        let l0_norm = non_zero_count;
89        let l1_norm = codes.mapv(|x| x.abs()).sum();
90        let sparsity_ratio = 1.0 - (non_zero_count / total_elements);
91        let gini_coefficient = 0.0; // Simplified placeholder
92
93        Ok(SparsityMetrics {
94            l0_norm,
95            l1_norm,
96            sparsity_ratio,
97            gini_coefficient,
98        })
99    }
100
101    pub fn compute_coherence_metrics(
102        &self,
103        dictionary: &Array2<Float>,
104    ) -> Result<CoherenceMetrics> {
105        let n_atoms = dictionary.nrows();
106
107        // Compute mutual coherence (simplified)
108        let mut max_coherence: Float = 0.0;
109        for i in 0..n_atoms {
110            for j in (i + 1)..n_atoms {
111                let atom_i = dictionary.row(i);
112                let atom_j = dictionary.row(j);
113                let coherence = atom_i.dot(&atom_j).abs();
114                max_coherence = max_coherence.max(coherence);
115            }
116        }
117
118        Ok(CoherenceMetrics {
119            mutual_coherence: max_coherence,
120            spark: n_atoms,        // Simplified
121            condition_number: 1.0, // Placeholder
122        })
123    }
124
125    pub fn compute_quality(
126        &self,
127        original: &Array2<Float>,
128        dictionary: &Array2<Float>,
129        codes: &Array2<Float>,
130    ) -> Result<DictionaryQuality> {
131        // Reconstruct data
132        let reconstructed = codes.dot(dictionary);
133
134        let reconstruction_error = self.compute_reconstruction_error(original, &reconstructed)?;
135        let sparsity_metrics = self.compute_sparsity_metrics(codes)?;
136        let coherence_metrics = self.compute_coherence_metrics(dictionary)?;
137
138        Ok(DictionaryQuality {
139            reconstruction_error,
140            sparsity_metrics,
141            coherence_metrics,
142        })
143    }
144}
145
146impl Default for DictionaryMetrics {
147    fn default() -> Self {
148        Self::new()
149    }
150}