sklears_decomposition/dictionary_learning/
dictionary_metrics.rs1use scirs2_core::ndarray::Array2;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use sklears_core::{error::Result, types::Float};
7
8#[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#[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#[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#[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#[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; 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 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, condition_number: 1.0, })
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 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}