1use crate::{Adam, AdamW};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11use trustformers_core::errors::{Result, TrustformersError};
12use trustformers_core::traits::Optimizer;
13use trustformers_core::Tensor;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct TensorFlowOptimizerConfig {
18 pub optimizer_type: String,
19 pub learning_rate: f64,
20 pub beta_1: Option<f64>,
21 pub beta_2: Option<f64>,
22 pub epsilon: Option<f64>,
23 pub weight_decay: Option<f64>,
24 pub clipnorm: Option<f64>,
25 pub clipvalue: Option<f64>,
26 pub global_clipnorm: Option<f64>,
27 pub use_ema: Option<bool>,
28 pub ema_momentum: Option<f64>,
29 pub ema_overwrite_frequency: Option<i32>,
30 pub jit_compile: Option<bool>,
31 pub name: Option<String>,
32 pub parameters: HashMap<String, serde_json::Value>,
33}
34
35impl Default for TensorFlowOptimizerConfig {
36 fn default() -> Self {
37 Self {
38 optimizer_type: "Adam".to_string(),
39 learning_rate: 0.001,
40 beta_1: Some(0.9),
41 beta_2: Some(0.999),
42 epsilon: Some(1e-7),
43 weight_decay: None,
44 clipnorm: None,
45 clipvalue: None,
46 global_clipnorm: None,
47 use_ema: Some(false),
48 ema_momentum: Some(0.99),
49 ema_overwrite_frequency: None,
50 jit_compile: Some(true),
51 name: None,
52 parameters: HashMap::new(),
53 }
54 }
55}
56
57pub trait TensorFlowLearningRateSchedule: Send + Sync {
59 fn get_lr(&self, step: i64) -> f64;
61
62 fn get_config(&self) -> serde_json::Value;
64}
65
66#[derive(Debug, Clone)]
68pub struct TensorFlowExponentialDecay {
69 initial_learning_rate: f64,
70 decay_steps: i64,
71 decay_rate: f64,
72 staircase: bool,
73}
74
75impl TensorFlowExponentialDecay {
76 pub fn new(
77 initial_learning_rate: f64,
78 decay_steps: i64,
79 decay_rate: f64,
80 staircase: bool,
81 ) -> Self {
82 Self {
83 initial_learning_rate,
84 decay_steps,
85 decay_rate,
86 staircase,
87 }
88 }
89}
90
91impl TensorFlowLearningRateSchedule for TensorFlowExponentialDecay {
92 fn get_lr(&self, step: i64) -> f64 {
93 let decay_factor = if self.staircase {
94 (step / self.decay_steps) as f64
95 } else {
96 step as f64 / self.decay_steps as f64
97 };
98
99 self.initial_learning_rate * self.decay_rate.powf(decay_factor)
100 }
101
102 fn get_config(&self) -> serde_json::Value {
103 serde_json::json!({
104 "initial_learning_rate": self.initial_learning_rate,
105 "decay_steps": self.decay_steps,
106 "decay_rate": self.decay_rate,
107 "staircase": self.staircase,
108 })
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct TensorFlowCosineDecay {
115 initial_learning_rate: f64,
116 decay_steps: i64,
117 alpha: f64,
118}
119
120impl TensorFlowCosineDecay {
121 pub fn new(initial_learning_rate: f64, decay_steps: i64, alpha: f64) -> Self {
122 Self {
123 initial_learning_rate,
124 decay_steps,
125 alpha,
126 }
127 }
128}
129
130impl TensorFlowLearningRateSchedule for TensorFlowCosineDecay {
131 fn get_lr(&self, step: i64) -> f64 {
132 let completed_fraction = (step.min(self.decay_steps) as f64) / (self.decay_steps as f64);
133 let cosine_decayed = 0.5 * (1.0 + (std::f64::consts::PI * completed_fraction).cos());
134 let decayed = (1.0 - self.alpha) * cosine_decayed + self.alpha;
135
136 self.initial_learning_rate * decayed
137 }
138
139 fn get_config(&self) -> serde_json::Value {
140 serde_json::json!({
141 "initial_learning_rate": self.initial_learning_rate,
142 "decay_steps": self.decay_steps,
143 "alpha": self.alpha,
144 })
145 }
146}
147
148pub trait TensorFlowOptimizer: Send + Sync {
150 fn apply_gradients(
152 &mut self,
153 grads_and_vars: &[(Tensor, String)],
154 global_step: Option<i64>,
155 ) -> Result<()>;
156
157 fn minimize(
159 &mut self,
160 loss_fn: Box<dyn Fn() -> Result<Tensor>>,
161 var_list: &[String],
162 global_step: Option<i64>,
163 ) -> Result<Tensor>;
164
165 fn get_config(&self) -> TensorFlowOptimizerConfig;
167
168 fn variables(&self) -> Vec<String>;
170
171 fn get_weights(&self) -> Vec<Tensor>;
173
174 fn set_weights(&mut self, weights: Vec<Tensor>) -> Result<()>;
176
177 fn get_learning_rate(&self) -> f64;
179
180 fn set_learning_rate(&mut self, lr: f64) -> Result<()>;
182
183 fn get_name(&self) -> &str;
185}
186
187pub struct TensorFlowAdam {
189 inner: Adam,
190 config: TensorFlowOptimizerConfig,
191 variables: Arc<Mutex<HashMap<String, Tensor>>>,
192 lr_schedule: Option<Box<dyn TensorFlowLearningRateSchedule>>,
193 global_step: i64,
194}
195
196impl TensorFlowAdam {
197 pub fn new(
199 learning_rate: f64,
200 beta_1: f64,
201 beta_2: f64,
202 epsilon: f64,
203 weight_decay: Option<f64>,
204 clipnorm: Option<f64>,
205 clipvalue: Option<f64>,
206 global_clipnorm: Option<f64>,
207 use_ema: bool,
208 ema_momentum: f64,
209 jit_compile: bool,
210 name: Option<String>,
211 ) -> Result<Self> {
212 let config = TensorFlowOptimizerConfig {
213 optimizer_type: "Adam".to_string(),
214 learning_rate,
215 beta_1: Some(beta_1),
216 beta_2: Some(beta_2),
217 epsilon: Some(epsilon),
218 weight_decay,
219 clipnorm,
220 clipvalue,
221 global_clipnorm,
222 use_ema: Some(use_ema),
223 ema_momentum: Some(ema_momentum),
224 ema_overwrite_frequency: None,
225 jit_compile: Some(jit_compile),
226 name,
227 parameters: HashMap::new(),
228 };
229
230 let inner = Adam::new(
233 learning_rate as f32,
234 (beta_1 as f32, beta_2 as f32),
235 epsilon as f32,
236 weight_decay.unwrap_or(0.0) as f32,
237 );
238
239 Ok(Self {
240 inner,
241 config,
242 variables: Arc::new(Mutex::new(HashMap::new())),
243 lr_schedule: None,
244 global_step: 0,
245 })
246 }
247
248 pub fn with_defaults() -> Result<Self> {
250 Self::new(
251 0.001,
252 0.9,
253 0.999,
254 1e-7,
255 None,
256 None,
257 None,
258 None,
259 false,
260 0.99,
261 true,
262 Some("Adam".to_string()),
263 )
264 }
265
266 pub fn from_config(config: TensorFlowOptimizerConfig) -> Result<Self> {
268 Self::new(
269 config.learning_rate,
270 config.beta_1.unwrap_or(0.9),
271 config.beta_2.unwrap_or(0.999),
272 config.epsilon.unwrap_or(1e-7),
273 config.weight_decay,
274 config.clipnorm,
275 config.clipvalue,
276 config.global_clipnorm,
277 config.use_ema.unwrap_or(false),
278 config.ema_momentum.unwrap_or(0.99),
279 config.jit_compile.unwrap_or(true),
280 config.name,
281 )
282 }
283
284 pub fn with_schedule(
286 schedule: Box<dyn TensorFlowLearningRateSchedule>,
287 beta_1: f64,
288 beta_2: f64,
289 epsilon: f64,
290 weight_decay: Option<f64>,
291 clipnorm: Option<f64>,
292 clipvalue: Option<f64>,
293 global_clipnorm: Option<f64>,
294 use_ema: bool,
295 ema_momentum: f64,
296 jit_compile: bool,
297 name: Option<String>,
298 ) -> Result<Self> {
299 let mut optimizer = Self::new(
300 schedule.get_lr(0),
301 beta_1,
302 beta_2,
303 epsilon,
304 weight_decay,
305 clipnorm,
306 clipvalue,
307 global_clipnorm,
308 use_ema,
309 ema_momentum,
310 jit_compile,
311 name,
312 )?;
313
314 optimizer.lr_schedule = Some(schedule);
315 Ok(optimizer)
316 }
317
318 pub fn add_variable(&mut self, name: String, var: Tensor) -> Result<()> {
320 let mut variables = self.variables.lock().map_err(|_| {
321 TrustformersError::lock_error(
322 "tensorflow optimizer variables mutex poisoned".to_string(),
323 )
324 })?;
325 variables.insert(name, var);
326 Ok(())
327 }
328
329 fn update_learning_rate(&mut self) -> Result<()> {
331 if let Some(ref schedule) = self.lr_schedule {
332 let new_lr = schedule.get_lr(self.global_step);
333 self.config.learning_rate = new_lr;
334
335 self.inner.set_lr(new_lr as f32);
337 }
338 Ok(())
339 }
340
341 fn clip_gradients(&self, gradients: &mut [Tensor]) -> Result<()> {
343 if let Some(clipnorm) = self.config.clipnorm {
344 for grad in gradients.iter_mut() {
346 let norm = grad.norm()?;
347 if norm > clipnorm as f32 {
348 grad.mul_scalar((clipnorm as f32) / norm)?;
349 }
350 }
351 }
352
353 if let Some(clipvalue) = self.config.clipvalue {
354 for grad in gradients.iter_mut() {
356 grad.clamp(-clipvalue as f32, clipvalue as f32)?;
357 }
358 }
359
360 if let Some(global_clipnorm) = self.config.global_clipnorm {
361 let global_norm: f64 = gradients
363 .iter()
364 .map(|g| g.norm().unwrap_or(0.0).powi(2) as f64)
365 .sum::<f64>()
366 .sqrt();
367
368 if global_norm > global_clipnorm {
369 let scale = global_clipnorm / global_norm;
370 for grad in gradients.iter_mut() {
371 grad.mul_scalar(scale as f32)?;
372 }
373 }
374 }
375
376 Ok(())
377 }
378}
379
380impl TensorFlowOptimizer for TensorFlowAdam {
381 fn apply_gradients(
382 &mut self,
383 grads_and_vars: &[(Tensor, String)],
384 global_step: Option<i64>,
385 ) -> Result<()> {
386 if let Some(step) = global_step {
387 self.global_step = step;
388 } else {
389 self.global_step += 1;
390 }
391
392 self.update_learning_rate()?;
394
395 let mut gradients: Vec<Tensor> = grads_and_vars.iter().map(|(g, _)| g.clone()).collect();
396
397 self.clip_gradients(&mut gradients)?;
399
400 let mut variables = self.variables.lock().map_err(|_| {
402 TrustformersError::lock_error(
403 "tensorflow optimizer variables mutex poisoned".to_string(),
404 )
405 })?;
406 for (grad, var_name) in grads_and_vars {
407 if let Some(var) = variables.get_mut(var_name) {
408 self.inner.update(var, grad)?;
409 }
410 }
411 self.inner.step();
412
413 Ok(())
414 }
415
416 fn minimize(
417 &mut self,
418 loss_fn: Box<dyn Fn() -> Result<Tensor>>,
419 var_list: &[String],
420 global_step: Option<i64>,
421 ) -> Result<Tensor> {
422 let loss = loss_fn()?;
423
424 let mut grads_and_vars = Vec::new();
426 {
427 let mut variables = self.variables.lock().map_err(|_| {
428 TrustformersError::lock_error(
429 "tensorflow optimizer variables mutex poisoned".to_string(),
430 )
431 })?;
432
433 for var_name in var_list {
434 if let Some(var) = variables.get_mut(var_name) {
435 let grad = self.compute_numerical_gradient(loss_fn.as_ref(), var, var_name)?;
437 grads_and_vars.push((grad, var_name.clone()));
438 }
439 }
440 } self.apply_gradients(&grads_and_vars, global_step)?;
443 Ok(loss)
444 }
445
446 fn get_config(&self) -> TensorFlowOptimizerConfig {
447 self.config.clone()
448 }
449
450 fn variables(&self) -> Vec<String> {
451 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
452 variables.keys().cloned().collect()
453 }
454
455 fn get_weights(&self) -> Vec<Tensor> {
456 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
457 variables.values().cloned().collect()
458 }
459
460 fn set_weights(&mut self, weights: Vec<Tensor>) -> Result<()> {
461 let mut variables = self.variables.lock().map_err(|_| {
462 TrustformersError::lock_error(
463 "tensorflow optimizer variables mutex poisoned".to_string(),
464 )
465 })?;
466 let var_names: Vec<String> = variables.keys().cloned().collect();
467
468 if weights.len() != var_names.len() {
469 return Err(TrustformersError::invalid_argument(
470 "Number of weights must match number of variables".to_string(),
471 ));
472 }
473
474 for (weight, var_name) in weights.into_iter().zip(var_names) {
475 variables.insert(var_name, weight);
476 }
477
478 Ok(())
479 }
480
481 fn get_learning_rate(&self) -> f64 {
482 self.config.learning_rate
483 }
484
485 fn set_learning_rate(&mut self, lr: f64) -> Result<()> {
486 self.config.learning_rate = lr;
487
488 self.inner.set_lr(lr as f32);
490
491 Ok(())
492 }
493
494 fn get_name(&self) -> &str {
495 self.config.name.as_deref().unwrap_or("Adam")
496 }
497}
498
499impl TensorFlowAdam {
500 fn compute_numerical_gradient(
502 &self,
503 loss_fn: &dyn Fn() -> Result<Tensor>,
504 var: &mut Tensor,
505 _var_name: &str,
506 ) -> Result<Tensor> {
507 const EPSILON: f32 = 1e-4;
508
509 let original_loss = loss_fn()?;
510
511 let var_data = var.data()?;
513 let mut grad_data = vec![0.0; var_data.len()];
514
515 for i in 0..var_data.len() {
516 let mut var_plus = var_data.clone();
518 var_plus[i] += EPSILON;
519 *var = Tensor::from_vec(var_plus, &var.shape())?;
520
521 let loss_plus = loss_fn()?;
522 let loss_plus_scalar = loss_plus.data()?[0];
523 let original_loss_scalar = original_loss.data()?[0];
524
525 grad_data[i] = (loss_plus_scalar - original_loss_scalar) / EPSILON;
526
527 let var_original = var_data.clone();
529 *var = Tensor::from_vec(var_original, &var.shape())?;
530 }
531
532 let grad = Tensor::from_vec(grad_data, &var.shape())?;
533 Ok(grad)
534 }
535}
536
537pub struct TensorFlowAdamW {
539 inner: AdamW,
540 config: TensorFlowOptimizerConfig,
541 variables: Arc<Mutex<HashMap<String, Tensor>>>,
542 lr_schedule: Option<Box<dyn TensorFlowLearningRateSchedule>>,
543 global_step: i64,
544}
545
546impl TensorFlowAdamW {
547 pub fn new(
549 learning_rate: f64,
550 beta_1: f64,
551 beta_2: f64,
552 epsilon: f64,
553 weight_decay: f64,
554 clipnorm: Option<f64>,
555 clipvalue: Option<f64>,
556 global_clipnorm: Option<f64>,
557 use_ema: bool,
558 ema_momentum: f64,
559 jit_compile: bool,
560 name: Option<String>,
561 ) -> Result<Self> {
562 let config = TensorFlowOptimizerConfig {
563 optimizer_type: "AdamW".to_string(),
564 learning_rate,
565 beta_1: Some(beta_1),
566 beta_2: Some(beta_2),
567 epsilon: Some(epsilon),
568 weight_decay: Some(weight_decay),
569 clipnorm,
570 clipvalue,
571 global_clipnorm,
572 use_ema: Some(use_ema),
573 ema_momentum: Some(ema_momentum),
574 ema_overwrite_frequency: None,
575 jit_compile: Some(jit_compile),
576 name,
577 parameters: HashMap::new(),
578 };
579
580 let _optimizer_config = TensorFlowOptimizerConfig {
581 learning_rate,
582 beta_1: Some(beta_1),
583 beta_2: Some(beta_2),
584 epsilon: Some(epsilon),
585 weight_decay: Some(weight_decay),
586 ..Default::default()
587 };
588
589 let inner = AdamW::new(
590 learning_rate as f32,
591 (beta_1 as f32, beta_2 as f32),
592 epsilon as f32,
593 weight_decay as f32,
594 );
595
596 Ok(Self {
597 inner,
598 config,
599 variables: Arc::new(Mutex::new(HashMap::new())),
600 lr_schedule: None,
601 global_step: 0,
602 })
603 }
604
605 pub fn with_defaults() -> Result<Self> {
607 Self::new(
608 0.001,
609 0.9,
610 0.999,
611 1e-7,
612 0.01,
613 None,
614 None,
615 None,
616 false,
617 0.99,
618 true,
619 Some("AdamW".to_string()),
620 )
621 }
622
623 pub fn with_schedule(
625 schedule: Box<dyn TensorFlowLearningRateSchedule>,
626 beta_1: f64,
627 beta_2: f64,
628 epsilon: f64,
629 weight_decay: f64,
630 clipnorm: Option<f64>,
631 clipvalue: Option<f64>,
632 global_clipnorm: Option<f64>,
633 use_ema: bool,
634 ema_momentum: f64,
635 jit_compile: bool,
636 name: Option<String>,
637 ) -> Result<Self> {
638 let mut optimizer = Self::new(
639 schedule.get_lr(0),
640 beta_1,
641 beta_2,
642 epsilon,
643 weight_decay,
644 clipnorm,
645 clipvalue,
646 global_clipnorm,
647 use_ema,
648 ema_momentum,
649 jit_compile,
650 name,
651 )?;
652
653 optimizer.lr_schedule = Some(schedule);
654 Ok(optimizer)
655 }
656
657 pub fn add_variable(&mut self, name: String, var: Tensor) -> Result<()> {
659 let mut variables = self.variables.lock().map_err(|_| {
660 TrustformersError::lock_error(
661 "tensorflow optimizer variables mutex poisoned".to_string(),
662 )
663 })?;
664 variables.insert(name, var);
665 Ok(())
666 }
667
668 fn update_learning_rate(&mut self) -> Result<()> {
670 if let Some(ref schedule) = self.lr_schedule {
671 let new_lr = schedule.get_lr(self.global_step);
672 self.config.learning_rate = new_lr;
673
674 self.inner.set_lr(new_lr as f32);
676 }
677 Ok(())
678 }
679
680 fn clip_gradients(&self, gradients: &mut [Tensor]) -> Result<()> {
682 if let Some(clipnorm) = self.config.clipnorm {
683 for grad in gradients.iter_mut() {
685 let norm = grad.norm()?;
686 if norm > clipnorm as f32 {
687 grad.mul_scalar((clipnorm as f32) / norm)?;
688 }
689 }
690 }
691
692 if let Some(clipvalue) = self.config.clipvalue {
693 for grad in gradients.iter_mut() {
695 grad.clamp(-clipvalue as f32, clipvalue as f32)?;
696 }
697 }
698
699 if let Some(global_clipnorm) = self.config.global_clipnorm {
700 let global_norm: f64 = gradients
702 .iter()
703 .map(|g| g.norm().unwrap_or(0.0).powi(2) as f64)
704 .sum::<f64>()
705 .sqrt();
706
707 if global_norm > global_clipnorm {
708 let scale = global_clipnorm / global_norm;
709 for grad in gradients.iter_mut() {
710 grad.mul_scalar(scale as f32)?;
711 }
712 }
713 }
714
715 Ok(())
716 }
717}
718
719impl TensorFlowOptimizer for TensorFlowAdamW {
720 fn apply_gradients(
721 &mut self,
722 grads_and_vars: &[(Tensor, String)],
723 global_step: Option<i64>,
724 ) -> Result<()> {
725 if let Some(step) = global_step {
726 self.global_step = step;
727 } else {
728 self.global_step += 1;
729 }
730
731 self.update_learning_rate()?;
733
734 let mut gradients: Vec<Tensor> = grads_and_vars.iter().map(|(g, _)| g.clone()).collect();
735
736 self.clip_gradients(&mut gradients)?;
738
739 let mut variables = self.variables.lock().map_err(|_| {
741 TrustformersError::lock_error(
742 "tensorflow optimizer variables mutex poisoned".to_string(),
743 )
744 })?;
745 for (grad, var_name) in grads_and_vars {
746 if let Some(var) = variables.get_mut(var_name) {
747 self.inner.update(var, grad)?;
748 }
749 }
750 self.inner.step();
751
752 Ok(())
753 }
754
755 fn minimize(
756 &mut self,
757 loss_fn: Box<dyn Fn() -> Result<Tensor>>,
758 var_list: &[String],
759 global_step: Option<i64>,
760 ) -> Result<Tensor> {
761 let loss = loss_fn()?;
762
763 let mut grads_and_vars = Vec::new();
765 {
766 let mut variables = self.variables.lock().map_err(|_| {
767 TrustformersError::lock_error(
768 "tensorflow optimizer variables mutex poisoned".to_string(),
769 )
770 })?;
771
772 for var_name in var_list {
773 if let Some(var) = variables.get_mut(var_name) {
774 let grad = self.compute_numerical_gradient(loss_fn.as_ref(), var, var_name)?;
776 grads_and_vars.push((grad, var_name.clone()));
777 }
778 }
779 } self.apply_gradients(&grads_and_vars, global_step)?;
782 Ok(loss)
783 }
784
785 fn get_config(&self) -> TensorFlowOptimizerConfig {
786 self.config.clone()
787 }
788
789 fn variables(&self) -> Vec<String> {
790 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
791 variables.keys().cloned().collect()
792 }
793
794 fn get_weights(&self) -> Vec<Tensor> {
795 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
796 variables.values().cloned().collect()
797 }
798
799 fn set_weights(&mut self, weights: Vec<Tensor>) -> Result<()> {
800 let mut variables = self.variables.lock().map_err(|_| {
801 TrustformersError::lock_error(
802 "tensorflow optimizer variables mutex poisoned".to_string(),
803 )
804 })?;
805 let var_names: Vec<String> = variables.keys().cloned().collect();
806
807 if weights.len() != var_names.len() {
808 return Err(TrustformersError::invalid_argument(
809 "Number of weights must match number of variables".to_string(),
810 ));
811 }
812
813 for (weight, var_name) in weights.into_iter().zip(var_names) {
814 variables.insert(var_name, weight);
815 }
816
817 Ok(())
818 }
819
820 fn get_learning_rate(&self) -> f64 {
821 self.config.learning_rate
822 }
823
824 fn set_learning_rate(&mut self, lr: f64) -> Result<()> {
825 self.config.learning_rate = lr;
826
827 self.inner.set_lr(lr as f32);
829
830 Ok(())
831 }
832
833 fn get_name(&self) -> &str {
834 self.config.name.as_deref().unwrap_or("AdamW")
835 }
836}
837
838impl TensorFlowAdamW {
839 fn compute_numerical_gradient(
841 &self,
842 loss_fn: &dyn Fn() -> Result<Tensor>,
843 var: &mut Tensor,
844 _var_name: &str,
845 ) -> Result<Tensor> {
846 const EPSILON: f32 = 1e-4;
847
848 let original_loss = loss_fn()?;
849
850 let var_data = var.data()?;
852 let mut grad_data = vec![0.0; var_data.len()];
853
854 for i in 0..var_data.len() {
855 let mut var_plus = var_data.clone();
857 var_plus[i] += EPSILON;
858 *var = Tensor::from_vec(var_plus, &var.shape())?;
859
860 let loss_plus = loss_fn()?;
861 let loss_plus_scalar = loss_plus.data()?[0];
862 let original_loss_scalar = original_loss.data()?[0];
863
864 grad_data[i] = (loss_plus_scalar - original_loss_scalar) / EPSILON;
865
866 let var_original = var_data.clone();
868 *var = Tensor::from_vec(var_original, &var.shape())?;
869 }
870
871 let grad = Tensor::from_vec(grad_data, &var.shape())?;
872 Ok(grad)
873 }
874}
875
876pub struct TensorFlowOptimizerFactory;
878
879impl TensorFlowOptimizerFactory {
880 pub fn adam(
882 learning_rate: f64,
883 beta_1: f64,
884 beta_2: f64,
885 epsilon: f64,
886 weight_decay: Option<f64>,
887 clipnorm: Option<f64>,
888 clipvalue: Option<f64>,
889 global_clipnorm: Option<f64>,
890 use_ema: bool,
891 ema_momentum: f64,
892 jit_compile: bool,
893 name: Option<String>,
894 ) -> Result<TensorFlowAdam> {
895 TensorFlowAdam::new(
896 learning_rate,
897 beta_1,
898 beta_2,
899 epsilon,
900 weight_decay,
901 clipnorm,
902 clipvalue,
903 global_clipnorm,
904 use_ema,
905 ema_momentum,
906 jit_compile,
907 name,
908 )
909 }
910
911 pub fn adamw(
913 learning_rate: f64,
914 beta_1: f64,
915 beta_2: f64,
916 epsilon: f64,
917 weight_decay: f64,
918 clipnorm: Option<f64>,
919 clipvalue: Option<f64>,
920 global_clipnorm: Option<f64>,
921 use_ema: bool,
922 ema_momentum: f64,
923 jit_compile: bool,
924 name: Option<String>,
925 ) -> Result<TensorFlowAdamW> {
926 TensorFlowAdamW::new(
927 learning_rate,
928 beta_1,
929 beta_2,
930 epsilon,
931 weight_decay,
932 clipnorm,
933 clipvalue,
934 global_clipnorm,
935 use_ema,
936 ema_momentum,
937 jit_compile,
938 name,
939 )
940 }
941
942 pub fn exponential_decay(
944 initial_learning_rate: f64,
945 decay_steps: i64,
946 decay_rate: f64,
947 staircase: bool,
948 ) -> TensorFlowExponentialDecay {
949 TensorFlowExponentialDecay::new(initial_learning_rate, decay_steps, decay_rate, staircase)
950 }
951
952 pub fn cosine_decay(
954 initial_learning_rate: f64,
955 decay_steps: i64,
956 alpha: f64,
957 ) -> TensorFlowCosineDecay {
958 TensorFlowCosineDecay::new(initial_learning_rate, decay_steps, alpha)
959 }
960}
961
962#[cfg(test)]
963mod tests {
964 use super::*;
965 use trustformers_core::Tensor;
966
967 #[test]
968 fn test_tensorflow_adam_creation() {
969 let optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
970 assert_eq!(optimizer.get_learning_rate(), 0.001);
971 assert_eq!(optimizer.get_name(), "Adam");
972 }
973
974 #[test]
975 fn test_tensorflow_adamw_creation() {
976 let optimizer = TensorFlowAdamW::with_defaults().expect("Operation failed in test");
977 assert_eq!(optimizer.get_learning_rate(), 0.001);
978 assert_eq!(optimizer.get_name(), "AdamW");
979 }
980
981 #[test]
982 fn test_tensorflow_exponential_decay() {
983 let schedule = TensorFlowExponentialDecay::new(0.1, 100, 0.96, false);
984 assert_eq!(schedule.get_lr(0), 0.1);
985 assert!(schedule.get_lr(100) < 0.1);
986 }
987
988 #[test]
989 fn test_tensorflow_cosine_decay() {
990 let schedule = TensorFlowCosineDecay::new(0.1, 100, 0.0);
991 assert_eq!(schedule.get_lr(0), 0.1);
992 assert!(schedule.get_lr(50) < 0.1);
993 assert!(schedule.get_lr(100) < 0.1);
994 }
995
996 #[test]
997 fn test_tensorflow_optimizer_factory() {
998 let adam = TensorFlowOptimizerFactory::adam(
999 0.001,
1000 0.9,
1001 0.999,
1002 1e-7,
1003 None,
1004 None,
1005 None,
1006 None,
1007 false,
1008 0.99,
1009 true,
1010 Some("TestAdam".to_string()),
1011 )
1012 .expect("Operation failed in test");
1013 assert_eq!(adam.get_name(), "TestAdam");
1014
1015 let adamw = TensorFlowOptimizerFactory::adamw(
1016 0.001,
1017 0.9,
1018 0.999,
1019 1e-7,
1020 0.01,
1021 None,
1022 None,
1023 None,
1024 false,
1025 0.99,
1026 true,
1027 Some("TestAdamW".to_string()),
1028 )
1029 .expect("Operation failed in test");
1030 assert_eq!(adamw.get_name(), "TestAdamW");
1031 }
1032
1033 #[test]
1034 fn test_learning_rate_schedule_with_optimizer() {
1035 let schedule = Box::new(TensorFlowExponentialDecay::new(0.1, 100, 0.96, false));
1036 let optimizer = TensorFlowAdam::with_schedule(
1037 schedule,
1038 0.9,
1039 0.999,
1040 1e-7,
1041 None,
1042 None,
1043 None,
1044 None,
1045 false,
1046 0.99,
1047 true,
1048 Some("ScheduledAdam".to_string()),
1049 )
1050 .expect("Operation failed in test");
1051
1052 assert_eq!(optimizer.get_learning_rate(), 0.1);
1053 }
1054
1055 #[test]
1056 fn test_variable_management() {
1057 let mut optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1058
1059 let var1 = Tensor::zeros(&[10, 10]).expect("Failed to create tensor");
1060 let var2 = Tensor::zeros(&[5, 5]).expect("Failed to create tensor");
1061
1062 optimizer
1063 .add_variable("var1".to_string(), var1)
1064 .expect("Operation failed in test");
1065 optimizer
1066 .add_variable("var2".to_string(), var2)
1067 .expect("Operation failed in test");
1068
1069 let variables = optimizer.variables();
1070 assert_eq!(variables.len(), 2);
1071 assert!(variables.contains(&"var1".to_string()));
1072 assert!(variables.contains(&"var2".to_string()));
1073 }
1074
1075 #[test]
1076 fn test_learning_rate_updates() {
1077 let mut optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1078 assert_eq!(optimizer.get_learning_rate(), 0.001);
1079
1080 optimizer.set_learning_rate(0.01).expect("Operation failed in test");
1081 assert_eq!(optimizer.get_learning_rate(), 0.01);
1082 }
1083
1084 #[test]
1085 fn test_config_serialization() {
1086 let optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1087 let config = optimizer.get_config();
1088
1089 assert_eq!(config.learning_rate, 0.001);
1090 assert_eq!(config.beta_1, Some(0.9));
1091 assert_eq!(config.beta_2, Some(0.999));
1092 assert_eq!(config.epsilon, Some(1e-7));
1093 }
1094}