optirs_core/regularizers/
spectral_norm.rs1use scirs2_core::ndarray::{Array, Array2, Array4, Dimension, ScalarOperand};
8use scirs2_core::numeric::{Float, FromPrimitive};
9use scirs2_core::Random;
10use std::cell::RefCell;
11use std::fmt::Debug;
12
13use crate::error::{OptimError, Result};
14use crate::regularizers::Regularizer;
15
16#[derive(Debug)]
34pub struct SpectralNorm<A: Float> {
35 n_power_iterations: usize,
37 eps: A,
39 u: RefCell<Option<Array<A, scirs2_core::ndarray::Ix1>>>,
41 v: RefCell<Option<Array<A, scirs2_core::ndarray::Ix1>>>,
43 rng: RefCell<Random<scirs2_core::random::rngs::StdRng>>,
45}
46
47impl<A: Float + Debug + ScalarOperand + FromPrimitive + Send + Sync> SpectralNorm<A> {
48 pub fn new(n_poweriterations: usize) -> Self {
54 Self {
55 n_power_iterations: n_poweriterations,
56 eps: A::from_f64(1e-12).unwrap_or_else(|| A::epsilon()),
57 u: RefCell::new(None),
58 v: RefCell::new(None),
59 rng: RefCell::new(Random::seed(42)),
60 }
61 }
62
63 fn compute_spectral_norm(&self, weights: &Array2<A>) -> Result<A> {
65 let (m, n) = (weights.nrows(), weights.ncols());
66
67 {
69 let u_ref = self.u.borrow();
70 let needs_init = u_ref.is_none() || u_ref.as_ref().is_none_or(|arr| arr.len() != m);
71 drop(u_ref);
72 if needs_init {
73 let mut rng = self.rng.borrow_mut();
74 let new_u = Array::from_shape_fn((m,), |_| {
75 let val: f64 = rng.gen_range(0.0..1.0);
76 A::from_f64(val).unwrap_or_else(|| A::one())
77 });
78 *self.u.borrow_mut() = Some(new_u);
79 }
80 }
81
82 {
83 let v_ref = self.v.borrow();
84 let needs_init = v_ref.is_none() || v_ref.as_ref().is_none_or(|arr| arr.len() != n);
85 drop(v_ref);
86 if needs_init {
87 let mut rng = self.rng.borrow_mut();
88 let new_v = Array::from_shape_fn((n,), |_| {
89 let val: f64 = rng.gen_range(0.0..1.0);
90 A::from_f64(val).unwrap_or_else(|| A::one())
91 });
92 *self.v.borrow_mut() = Some(new_v);
93 }
94 }
95
96 let mut u = self
97 .u
98 .borrow()
99 .as_ref()
100 .ok_or_else(|| {
101 OptimError::InvalidParameter("Left singular vector not initialized".to_string())
102 })?
103 .clone();
104 let mut v = self
105 .v
106 .borrow()
107 .as_ref()
108 .ok_or_else(|| {
109 OptimError::InvalidParameter("Right singular vector not initialized".to_string())
110 })?
111 .clone();
112
113 for _ in 0..self.n_power_iterations {
115 let wt_u = weights.t().dot(&u);
117 let v_norm = (wt_u.dot(&wt_u) + self.eps).sqrt();
118 v = wt_u / v_norm;
119
120 let w_v = weights.dot(&v);
122 let u_norm = (w_v.dot(&w_v) + self.eps).sqrt();
123 u = w_v / u_norm;
124 }
125
126 *self.u.borrow_mut() = Some(u.clone());
128 *self.v.borrow_mut() = Some(v.clone());
129
130 let w_v = weights.dot(&v);
132 let spectral_norm = u.dot(&w_v);
133
134 Ok(spectral_norm)
135 }
136
137 pub fn normalize(&self, weights: &Array2<A>) -> Result<Array2<A>> {
139 let spectral_norm = self.compute_spectral_norm(weights)?;
140
141 if spectral_norm > self.eps {
142 Ok(weights / spectral_norm)
143 } else {
144 Ok(weights.clone())
145 }
146 }
147
148 pub fn normalize_conv4d(&self, weights: &Array4<A>) -> Result<Array4<A>> {
150 let shape = weights.shape();
152 let out_channels = shape[0];
153 let in_channels = shape[1];
154 let kernel_h = shape[2];
155 let kernel_w = shape[3];
156
157 let weights_2d = weights
158 .to_shape((out_channels, in_channels * kernel_h * kernel_w))
159 .map_err(|e| OptimError::InvalidConfig(format!("Cannot reshape weights: {}", e)))?;
160 let weights_2d_owned = weights_2d.to_owned();
161 let normalized_2d = self.normalize(&weights_2d_owned)?;
162
163 let normalized_4d = normalized_2d
165 .to_shape((out_channels, in_channels, kernel_h, kernel_w))
166 .map_err(|e| {
167 OptimError::InvalidConfig(format!("Cannot reshape normalized weights: {}", e))
168 })?;
169 Ok(normalized_4d.to_owned())
170 }
171}
172
173impl<A: Float + Debug + ScalarOperand + FromPrimitive, D: Dimension + Send + Sync> Regularizer<A, D>
175 for SpectralNorm<A>
176{
177 fn apply(&self, _params: &Array<A, D>, _gradients: &mut Array<A, D>) -> Result<A> {
178 Ok(A::zero())
181 }
182
183 fn penalty(&self, _params: &Array<A, D>) -> Result<A> {
184 Ok(A::zero())
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use approx::assert_relative_eq;
193 use scirs2_core::ndarray::array;
194
195 #[test]
196 fn test_spectral_norm_creation() {
197 let sn = SpectralNorm::<f64>::new(5);
198 assert_eq!(sn.n_power_iterations, 5);
199 }
200
201 #[test]
202 fn test_spectral_norm_2d() {
203 let sn = SpectralNorm::new(10);
204
205 let weights = array![[1.0, 0.0], [0.0, 2.0]];
208
209 let spectral_norm = sn
210 .compute_spectral_norm(&weights)
211 .expect("test: compute_spectral_norm failed");
212
213 assert_relative_eq!(spectral_norm, 2.0, epsilon = 0.1);
215 }
216
217 #[test]
218 fn test_normalize_2d() {
219 let sn = SpectralNorm::new(10);
220
221 let weights = array![[1.0, 2.0], [3.0, 4.0]];
222 let normalized = sn.normalize(&weights).expect("test: normalize failed");
223
224 let spec_norm = sn
226 .compute_spectral_norm(&normalized)
227 .expect("test: compute_spectral_norm failed");
228 assert_relative_eq!(spec_norm, 1.0, epsilon = 0.1);
229 }
230
231 #[test]
232 fn test_conv4d_normalization() {
233 let sn = SpectralNorm::new(5);
234
235 let weights = Array::from_shape_fn((2, 3, 3, 3), |(o, i, h, w)| {
237 (o * 27 + i * 9 + h * 3 + w) as f64
238 });
239
240 let normalized = sn
241 .normalize_conv4d(&weights)
242 .expect("test: normalize_conv4d failed");
243
244 assert_eq!(normalized.shape(), weights.shape());
246 }
247
248 #[test]
249 fn test_invalid_conv4d() {
250 let sn = SpectralNorm::<f64>::new(5);
251
252 let weights = Array::zeros((2, 3, 4, 4));
254
255 assert!(sn.normalize_conv4d(&weights).is_ok());
257 }
258
259 #[test]
260 fn test_regularizer_trait() {
261 let sn = SpectralNorm::new(5);
262 let params = array![[1.0, 2.0], [3.0, 4.0]];
263 let mut gradient = array![[0.1, 0.2], [0.3, 0.4]];
264
265 let penalty = sn.penalty(¶ms).expect("test: penalty failed");
267 assert_eq!(penalty, 0.0);
268
269 let apply_result = sn
270 .apply(¶ms, &mut gradient)
271 .expect("test: apply failed");
272 assert_eq!(apply_result, 0.0);
273
274 assert_eq!(gradient, array![[0.1, 0.2], [0.3, 0.4]]);
276 }
277}