1use scirs2_core::ndarray::{Array, Dimension, IxDyn, ScalarOperand, Zip};
4use scirs2_core::numeric::Float;
5use std::fmt::Debug;
6
7use crate::error::{OptimError, Result};
8use crate::optimizers::Optimizer;
9
10#[derive(Debug, Clone)]
39pub struct Adam<A: Float + ScalarOperand + Debug> {
40 learning_rate: A,
42 beta1: A,
44 beta2: A,
46 epsilon: A,
48 weight_decay: A,
50 m: Option<Vec<Array<A, IxDyn>>>,
52 v: Option<Vec<Array<A, IxDyn>>>,
54 t: Vec<usize>,
59}
60
61impl<A: Float + ScalarOperand + Debug + Send + Sync> Adam<A> {
62 pub fn new(learning_rate: A) -> Self {
68 Self {
69 learning_rate,
70 beta1: A::from(0.9)
71 .expect("Adam: default beta1 (0.9) must be representable in A (f32/f64)"),
72 beta2: A::from(0.999)
73 .expect("Adam: default beta2 (0.999) must be representable in A (f32/f64)"),
74 epsilon: A::from(1e-8)
75 .expect("Adam: default epsilon (1e-8) must be representable in A (f32/f64)"),
76 weight_decay: A::zero(),
77 m: None,
78 v: None,
79 t: Vec::new(),
80 }
81 }
82
83 pub fn new_with_config(
93 learning_rate: A,
94 beta1: A,
95 beta2: A,
96 epsilon: A,
97 weight_decay: A,
98 ) -> Self {
99 Self {
100 learning_rate,
101 beta1,
102 beta2,
103 epsilon,
104 weight_decay,
105 m: None,
106 v: None,
107 t: Vec::new(),
108 }
109 }
110
111 pub fn set_beta1(&mut self, beta1: A) -> &mut Self {
113 self.beta1 = beta1;
114 self
115 }
116
117 pub fn with_beta1(mut self, beta1: A) -> Self {
119 self.beta1 = beta1;
120 self
121 }
122
123 pub fn get_beta1(&self) -> A {
125 self.beta1
126 }
127
128 pub fn set_beta2(&mut self, beta2: A) -> &mut Self {
130 self.beta2 = beta2;
131 self
132 }
133
134 pub fn with_beta2(mut self, beta2: A) -> Self {
136 self.beta2 = beta2;
137 self
138 }
139
140 pub fn get_beta2(&self) -> A {
142 self.beta2
143 }
144
145 pub fn set_epsilon(&mut self, epsilon: A) -> &mut Self {
147 self.epsilon = epsilon;
148 self
149 }
150
151 pub fn with_epsilon(mut self, epsilon: A) -> Self {
153 self.epsilon = epsilon;
154 self
155 }
156
157 pub fn get_epsilon(&self) -> A {
159 self.epsilon
160 }
161
162 pub fn set_weight_decay(&mut self, weight_decay: A) -> &mut Self {
164 self.weight_decay = weight_decay;
165 self
166 }
167
168 pub fn with_weight_decay(mut self, weight_decay: A) -> Self {
170 self.weight_decay = weight_decay;
171 self
172 }
173
174 pub fn get_weight_decay(&self) -> A {
176 self.weight_decay
177 }
178
179 pub fn learning_rate(&self) -> A {
181 self.learning_rate
182 }
183
184 pub fn set_lr(&mut self, lr: A) {
186 self.learning_rate = lr;
187 }
188
189 pub fn reset(&mut self) {
191 self.m = None;
192 self.v = None;
193 self.t.clear();
194 }
195
196 pub fn timestep(&self, index: usize) -> usize {
200 self.t.get(index).copied().unwrap_or(0)
201 }
202
203 fn advance_state(&mut self, index: usize, dim: &IxDyn) -> Result<usize> {
207 let m = self.m.get_or_insert_with(Vec::new);
208 let v = self.v.get_or_insert_with(Vec::new);
209 while m.len() <= index {
210 m.push(Array::zeros(dim.clone()));
211 }
212 while v.len() <= index {
213 v.push(Array::zeros(dim.clone()));
214 }
215 while self.t.len() <= index {
216 self.t.push(0);
217 }
218
219 if m[index].raw_dim() != *dim || v[index].raw_dim() != *dim {
221 m[index] = Array::zeros(dim.clone());
222 v[index] = Array::zeros(dim.clone());
223 self.t[index] = 0;
224 }
225
226 let next = self.t[index].checked_add(1).ok_or_else(|| {
227 OptimError::InvalidConfig(
228 "Timestep counter overflow - too many optimization steps".to_string(),
229 )
230 })?;
231 self.t[index] = next;
232 Ok(next)
233 }
234
235 pub fn step_inplace_indexed<D: Dimension>(
241 &mut self,
242 index: usize,
243 params: &mut Array<A, D>,
244 gradients: &Array<A, D>,
245 ) -> Result<()> {
246 if params.shape() != gradients.shape() {
247 return Err(OptimError::DimensionMismatch(format!(
248 "Incompatible shapes: parameters have shape {:?}, gradients have shape {:?}",
249 params.shape(),
250 gradients.shape()
251 )));
252 }
253
254 let dim = params.raw_dim().into_dyn();
255 let t = self.advance_state(index, &dim)?;
256
257 let exp = i32::try_from(t).map_err(|_| {
258 OptimError::InvalidConfig(
259 "Timestep too large for bias correction calculation".to_string(),
260 )
261 })?;
262
263 let beta1 = self.beta1;
264 let beta2 = self.beta2;
265 let lr = self.learning_rate;
266 let eps = self.epsilon;
267 let weight_decay = self.weight_decay;
268 let one = A::one();
269 let bias_correction1 = one - beta1.powi(exp);
270 let bias_correction2 = one - beta2.powi(exp);
271 let use_weight_decay = weight_decay > A::zero();
272
273 let m = self
274 .m
275 .as_mut()
276 .ok_or_else(|| OptimError::InvalidConfig("Adam state not initialized".to_string()))?;
277 let v = self
278 .v
279 .as_mut()
280 .ok_or_else(|| OptimError::InvalidConfig("Adam state not initialized".to_string()))?;
281
282 let mut params_view = params.view_mut().into_dyn();
283 let gradients_view = gradients.view().into_dyn();
284
285 Zip::from(&mut params_view)
286 .and(&gradients_view)
287 .and(&mut m[index])
288 .and(&mut v[index])
289 .for_each(|p, &g, m_i, v_i| {
290 let grad = if use_weight_decay {
291 g + weight_decay * *p
292 } else {
293 g
294 };
295 *m_i = *m_i * beta1 + grad * (one - beta1);
296 *v_i = *v_i * beta2 + grad * grad * (one - beta2);
297 let m_hat = *m_i / bias_correction1;
298 let v_hat = *v_i / bias_correction2;
299 *p = *p - lr * m_hat / (v_hat.sqrt() + eps);
300 });
301
302 Ok(())
303 }
304
305 pub fn step_inplace<D: Dimension>(
307 &mut self,
308 params: &mut Array<A, D>,
309 gradients: &Array<A, D>,
310 ) -> Result<()> {
311 self.step_inplace_indexed(0, params, gradients)
312 }
313
314 pub fn step_indexed<D: Dimension>(
320 &mut self,
321 index: usize,
322 params: &Array<A, D>,
323 gradients: &Array<A, D>,
324 ) -> Result<Array<A, D>> {
325 let mut updated = params.to_owned();
326 self.step_inplace_indexed(index, &mut updated, gradients)?;
327 Ok(updated)
328 }
329}
330
331impl<A, D> Optimizer<A, D> for Adam<A>
332where
333 A: Float + ScalarOperand + Debug + Send + Sync,
334 D: Dimension,
335{
336 fn step(&mut self, params: &Array<A, D>, gradients: &Array<A, D>) -> Result<Array<A, D>> {
337 self.step_indexed(0, params, gradients)
338 }
339
340 fn step_list(
341 &mut self,
342 params_list: &[&Array<A, D>],
343 gradients_list: &[&Array<A, D>],
344 ) -> Result<Vec<Array<A, D>>> {
345 if params_list.len() != gradients_list.len() {
346 return Err(OptimError::InvalidConfig(format!(
347 "Number of parameter arrays ({}) does not match number of gradient arrays ({})",
348 params_list.len(),
349 gradients_list.len()
350 )));
351 }
352
353 let mut results = Vec::with_capacity(params_list.len());
354 for (index, (params, grads)) in params_list.iter().zip(gradients_list.iter()).enumerate() {
355 results.push(self.step_indexed(index, params, grads)?);
356 }
357 Ok(results)
358 }
359
360 fn get_learning_rate(&self) -> A {
361 self.learning_rate
362 }
363
364 fn set_learning_rate(&mut self, learning_rate: A) {
365 self.learning_rate = learning_rate;
366 }
367}