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<()> {
347 if let Some(clipnorm) = self.config.clipnorm {
348 for grad in gradients.iter_mut() {
350 let norm = grad.norm()?;
351 if norm > clipnorm as f32 && norm > 0.0 {
352 *grad = grad.mul_scalar((clipnorm as f32) / norm)?;
353 }
354 }
355 }
356
357 if let Some(clipvalue) = self.config.clipvalue {
358 for grad in gradients.iter_mut() {
360 *grad = grad.clamp(-clipvalue as f32, clipvalue as f32)?;
361 }
362 }
363
364 if let Some(global_clipnorm) = self.config.global_clipnorm {
365 let mut sum_sq = 0.0_f64;
367 for grad in gradients.iter() {
368 let norm = grad.norm()? as f64;
369 sum_sq += norm * norm;
370 }
371 let global_norm = sum_sq.sqrt();
372
373 if global_norm > global_clipnorm && global_norm > 0.0 {
374 let scale = global_clipnorm / global_norm;
375 for grad in gradients.iter_mut() {
376 *grad = grad.mul_scalar(scale as f32)?;
377 }
378 }
379 }
380
381 Ok(())
382 }
383}
384
385impl TensorFlowOptimizer for TensorFlowAdam {
386 fn apply_gradients(
387 &mut self,
388 grads_and_vars: &[(Tensor, String)],
389 global_step: Option<i64>,
390 ) -> Result<()> {
391 if let Some(step) = global_step {
392 self.global_step = step;
393 } else {
394 self.global_step += 1;
395 }
396
397 self.update_learning_rate()?;
399
400 let mut gradients: Vec<Tensor> = grads_and_vars.iter().map(|(g, _)| g.clone()).collect();
401
402 self.clip_gradients(&mut gradients)?;
404
405 let mut variables = self.variables.lock().map_err(|_| {
407 TrustformersError::lock_error(
408 "tensorflow optimizer variables mutex poisoned".to_string(),
409 )
410 })?;
411 for (clipped_grad, (_, var_name)) in gradients.iter().zip(grads_and_vars.iter()) {
413 if let Some(var) = variables.get_mut(var_name) {
414 self.inner.update_named(var_name, var, clipped_grad)?;
415 }
416 }
417 self.inner.step();
418
419 Ok(())
420 }
421
422 fn minimize(
423 &mut self,
424 loss_fn: Box<dyn Fn() -> Result<Tensor>>,
425 var_list: &[String],
426 global_step: Option<i64>,
427 ) -> Result<Tensor> {
428 let loss = loss_fn()?;
429
430 let mut grads_and_vars = Vec::new();
432 {
433 let mut variables = self.variables.lock().map_err(|_| {
434 TrustformersError::lock_error(
435 "tensorflow optimizer variables mutex poisoned".to_string(),
436 )
437 })?;
438
439 for var_name in var_list {
440 if let Some(var) = variables.get_mut(var_name) {
441 let grad = self.compute_numerical_gradient(loss_fn.as_ref(), var, var_name)?;
443 grads_and_vars.push((grad, var_name.clone()));
444 }
445 }
446 } self.apply_gradients(&grads_and_vars, global_step)?;
449 Ok(loss)
450 }
451
452 fn get_config(&self) -> TensorFlowOptimizerConfig {
453 self.config.clone()
454 }
455
456 fn variables(&self) -> Vec<String> {
457 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
458 variables.keys().cloned().collect()
459 }
460
461 fn get_weights(&self) -> Vec<Tensor> {
462 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
463 variables.values().cloned().collect()
464 }
465
466 fn set_weights(&mut self, weights: Vec<Tensor>) -> Result<()> {
467 let mut variables = self.variables.lock().map_err(|_| {
468 TrustformersError::lock_error(
469 "tensorflow optimizer variables mutex poisoned".to_string(),
470 )
471 })?;
472 let var_names: Vec<String> = variables.keys().cloned().collect();
473
474 if weights.len() != var_names.len() {
475 return Err(TrustformersError::invalid_argument(
476 "Number of weights must match number of variables".to_string(),
477 ));
478 }
479
480 for (weight, var_name) in weights.into_iter().zip(var_names) {
481 variables.insert(var_name, weight);
482 }
483
484 Ok(())
485 }
486
487 fn get_learning_rate(&self) -> f64 {
488 self.config.learning_rate
489 }
490
491 fn set_learning_rate(&mut self, lr: f64) -> Result<()> {
492 self.config.learning_rate = lr;
493
494 self.inner.set_lr(lr as f32);
496
497 Ok(())
498 }
499
500 fn get_name(&self) -> &str {
501 self.config.name.as_deref().unwrap_or("Adam")
502 }
503}
504
505impl TensorFlowAdam {
506 fn compute_numerical_gradient(
508 &self,
509 loss_fn: &dyn Fn() -> Result<Tensor>,
510 var: &mut Tensor,
511 _var_name: &str,
512 ) -> Result<Tensor> {
513 const EPSILON: f32 = 1e-4;
514
515 let original_loss = loss_fn()?;
516
517 let var_data = var.data()?;
519 let mut grad_data = vec![0.0; var_data.len()];
520
521 for i in 0..var_data.len() {
522 let mut var_plus = var_data.clone();
524 var_plus[i] += EPSILON;
525 *var = Tensor::from_vec(var_plus, &var.shape())?;
526
527 let loss_plus = loss_fn()?;
528 let loss_plus_scalar = loss_plus.data()?[0];
529 let original_loss_scalar = original_loss.data()?[0];
530
531 grad_data[i] = (loss_plus_scalar - original_loss_scalar) / EPSILON;
532
533 let var_original = var_data.clone();
535 *var = Tensor::from_vec(var_original, &var.shape())?;
536 }
537
538 let grad = Tensor::from_vec(grad_data, &var.shape())?;
539 Ok(grad)
540 }
541}
542
543pub struct TensorFlowAdamW {
545 inner: AdamW,
546 config: TensorFlowOptimizerConfig,
547 variables: Arc<Mutex<HashMap<String, Tensor>>>,
548 lr_schedule: Option<Box<dyn TensorFlowLearningRateSchedule>>,
549 global_step: i64,
550}
551
552impl TensorFlowAdamW {
553 pub fn new(
555 learning_rate: f64,
556 beta_1: f64,
557 beta_2: f64,
558 epsilon: f64,
559 weight_decay: f64,
560 clipnorm: Option<f64>,
561 clipvalue: Option<f64>,
562 global_clipnorm: Option<f64>,
563 use_ema: bool,
564 ema_momentum: f64,
565 jit_compile: bool,
566 name: Option<String>,
567 ) -> Result<Self> {
568 let config = TensorFlowOptimizerConfig {
569 optimizer_type: "AdamW".to_string(),
570 learning_rate,
571 beta_1: Some(beta_1),
572 beta_2: Some(beta_2),
573 epsilon: Some(epsilon),
574 weight_decay: Some(weight_decay),
575 clipnorm,
576 clipvalue,
577 global_clipnorm,
578 use_ema: Some(use_ema),
579 ema_momentum: Some(ema_momentum),
580 ema_overwrite_frequency: None,
581 jit_compile: Some(jit_compile),
582 name,
583 parameters: HashMap::new(),
584 };
585
586 let _optimizer_config = TensorFlowOptimizerConfig {
587 learning_rate,
588 beta_1: Some(beta_1),
589 beta_2: Some(beta_2),
590 epsilon: Some(epsilon),
591 weight_decay: Some(weight_decay),
592 ..Default::default()
593 };
594
595 let inner = AdamW::new(
596 learning_rate as f32,
597 (beta_1 as f32, beta_2 as f32),
598 epsilon as f32,
599 weight_decay as f32,
600 );
601
602 Ok(Self {
603 inner,
604 config,
605 variables: Arc::new(Mutex::new(HashMap::new())),
606 lr_schedule: None,
607 global_step: 0,
608 })
609 }
610
611 pub fn with_defaults() -> Result<Self> {
613 Self::new(
614 0.001,
615 0.9,
616 0.999,
617 1e-7,
618 0.01,
619 None,
620 None,
621 None,
622 false,
623 0.99,
624 true,
625 Some("AdamW".to_string()),
626 )
627 }
628
629 pub fn with_schedule(
631 schedule: Box<dyn TensorFlowLearningRateSchedule>,
632 beta_1: f64,
633 beta_2: f64,
634 epsilon: f64,
635 weight_decay: f64,
636 clipnorm: Option<f64>,
637 clipvalue: Option<f64>,
638 global_clipnorm: Option<f64>,
639 use_ema: bool,
640 ema_momentum: f64,
641 jit_compile: bool,
642 name: Option<String>,
643 ) -> Result<Self> {
644 let mut optimizer = Self::new(
645 schedule.get_lr(0),
646 beta_1,
647 beta_2,
648 epsilon,
649 weight_decay,
650 clipnorm,
651 clipvalue,
652 global_clipnorm,
653 use_ema,
654 ema_momentum,
655 jit_compile,
656 name,
657 )?;
658
659 optimizer.lr_schedule = Some(schedule);
660 Ok(optimizer)
661 }
662
663 pub fn add_variable(&mut self, name: String, var: Tensor) -> Result<()> {
665 let mut variables = self.variables.lock().map_err(|_| {
666 TrustformersError::lock_error(
667 "tensorflow optimizer variables mutex poisoned".to_string(),
668 )
669 })?;
670 variables.insert(name, var);
671 Ok(())
672 }
673
674 fn update_learning_rate(&mut self) -> Result<()> {
676 if let Some(ref schedule) = self.lr_schedule {
677 let new_lr = schedule.get_lr(self.global_step);
678 self.config.learning_rate = new_lr;
679
680 self.inner.set_lr(new_lr as f32);
682 }
683 Ok(())
684 }
685
686 fn clip_gradients(&self, gradients: &mut [Tensor]) -> Result<()> {
692 if let Some(clipnorm) = self.config.clipnorm {
693 for grad in gradients.iter_mut() {
695 let norm = grad.norm()?;
696 if norm > clipnorm as f32 && norm > 0.0 {
697 *grad = grad.mul_scalar((clipnorm as f32) / norm)?;
698 }
699 }
700 }
701
702 if let Some(clipvalue) = self.config.clipvalue {
703 for grad in gradients.iter_mut() {
705 *grad = grad.clamp(-clipvalue as f32, clipvalue as f32)?;
706 }
707 }
708
709 if let Some(global_clipnorm) = self.config.global_clipnorm {
710 let mut sum_sq = 0.0_f64;
712 for grad in gradients.iter() {
713 let norm = grad.norm()? as f64;
714 sum_sq += norm * norm;
715 }
716 let global_norm = sum_sq.sqrt();
717
718 if global_norm > global_clipnorm && global_norm > 0.0 {
719 let scale = global_clipnorm / global_norm;
720 for grad in gradients.iter_mut() {
721 *grad = grad.mul_scalar(scale as f32)?;
722 }
723 }
724 }
725
726 Ok(())
727 }
728}
729
730impl TensorFlowOptimizer for TensorFlowAdamW {
731 fn apply_gradients(
732 &mut self,
733 grads_and_vars: &[(Tensor, String)],
734 global_step: Option<i64>,
735 ) -> Result<()> {
736 if let Some(step) = global_step {
737 self.global_step = step;
738 } else {
739 self.global_step += 1;
740 }
741
742 self.update_learning_rate()?;
744
745 let mut gradients: Vec<Tensor> = grads_and_vars.iter().map(|(g, _)| g.clone()).collect();
746
747 self.clip_gradients(&mut gradients)?;
749
750 let mut variables = self.variables.lock().map_err(|_| {
752 TrustformersError::lock_error(
753 "tensorflow optimizer variables mutex poisoned".to_string(),
754 )
755 })?;
756 for (clipped_grad, (_, var_name)) in gradients.iter().zip(grads_and_vars.iter()) {
758 if let Some(var) = variables.get_mut(var_name) {
759 self.inner.update_named(var_name, var, clipped_grad)?;
760 }
761 }
762 self.inner.step();
763
764 Ok(())
765 }
766
767 fn minimize(
768 &mut self,
769 loss_fn: Box<dyn Fn() -> Result<Tensor>>,
770 var_list: &[String],
771 global_step: Option<i64>,
772 ) -> Result<Tensor> {
773 let loss = loss_fn()?;
774
775 let mut grads_and_vars = Vec::new();
777 {
778 let mut variables = self.variables.lock().map_err(|_| {
779 TrustformersError::lock_error(
780 "tensorflow optimizer variables mutex poisoned".to_string(),
781 )
782 })?;
783
784 for var_name in var_list {
785 if let Some(var) = variables.get_mut(var_name) {
786 let grad = self.compute_numerical_gradient(loss_fn.as_ref(), var, var_name)?;
788 grads_and_vars.push((grad, var_name.clone()));
789 }
790 }
791 } self.apply_gradients(&grads_and_vars, global_step)?;
794 Ok(loss)
795 }
796
797 fn get_config(&self) -> TensorFlowOptimizerConfig {
798 self.config.clone()
799 }
800
801 fn variables(&self) -> Vec<String> {
802 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
803 variables.keys().cloned().collect()
804 }
805
806 fn get_weights(&self) -> Vec<Tensor> {
807 let variables = self.variables.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
808 variables.values().cloned().collect()
809 }
810
811 fn set_weights(&mut self, weights: Vec<Tensor>) -> Result<()> {
812 let mut variables = self.variables.lock().map_err(|_| {
813 TrustformersError::lock_error(
814 "tensorflow optimizer variables mutex poisoned".to_string(),
815 )
816 })?;
817 let var_names: Vec<String> = variables.keys().cloned().collect();
818
819 if weights.len() != var_names.len() {
820 return Err(TrustformersError::invalid_argument(
821 "Number of weights must match number of variables".to_string(),
822 ));
823 }
824
825 for (weight, var_name) in weights.into_iter().zip(var_names) {
826 variables.insert(var_name, weight);
827 }
828
829 Ok(())
830 }
831
832 fn get_learning_rate(&self) -> f64 {
833 self.config.learning_rate
834 }
835
836 fn set_learning_rate(&mut self, lr: f64) -> Result<()> {
837 self.config.learning_rate = lr;
838
839 self.inner.set_lr(lr as f32);
841
842 Ok(())
843 }
844
845 fn get_name(&self) -> &str {
846 self.config.name.as_deref().unwrap_or("AdamW")
847 }
848}
849
850impl TensorFlowAdamW {
851 fn compute_numerical_gradient(
853 &self,
854 loss_fn: &dyn Fn() -> Result<Tensor>,
855 var: &mut Tensor,
856 _var_name: &str,
857 ) -> Result<Tensor> {
858 const EPSILON: f32 = 1e-4;
859
860 let original_loss = loss_fn()?;
861
862 let var_data = var.data()?;
864 let mut grad_data = vec![0.0; var_data.len()];
865
866 for i in 0..var_data.len() {
867 let mut var_plus = var_data.clone();
869 var_plus[i] += EPSILON;
870 *var = Tensor::from_vec(var_plus, &var.shape())?;
871
872 let loss_plus = loss_fn()?;
873 let loss_plus_scalar = loss_plus.data()?[0];
874 let original_loss_scalar = original_loss.data()?[0];
875
876 grad_data[i] = (loss_plus_scalar - original_loss_scalar) / EPSILON;
877
878 let var_original = var_data.clone();
880 *var = Tensor::from_vec(var_original, &var.shape())?;
881 }
882
883 let grad = Tensor::from_vec(grad_data, &var.shape())?;
884 Ok(grad)
885 }
886}
887
888pub struct TensorFlowOptimizerFactory;
890
891impl TensorFlowOptimizerFactory {
892 pub fn adam(
894 learning_rate: f64,
895 beta_1: f64,
896 beta_2: f64,
897 epsilon: f64,
898 weight_decay: Option<f64>,
899 clipnorm: Option<f64>,
900 clipvalue: Option<f64>,
901 global_clipnorm: Option<f64>,
902 use_ema: bool,
903 ema_momentum: f64,
904 jit_compile: bool,
905 name: Option<String>,
906 ) -> Result<TensorFlowAdam> {
907 TensorFlowAdam::new(
908 learning_rate,
909 beta_1,
910 beta_2,
911 epsilon,
912 weight_decay,
913 clipnorm,
914 clipvalue,
915 global_clipnorm,
916 use_ema,
917 ema_momentum,
918 jit_compile,
919 name,
920 )
921 }
922
923 pub fn adamw(
925 learning_rate: f64,
926 beta_1: f64,
927 beta_2: f64,
928 epsilon: f64,
929 weight_decay: f64,
930 clipnorm: Option<f64>,
931 clipvalue: Option<f64>,
932 global_clipnorm: Option<f64>,
933 use_ema: bool,
934 ema_momentum: f64,
935 jit_compile: bool,
936 name: Option<String>,
937 ) -> Result<TensorFlowAdamW> {
938 TensorFlowAdamW::new(
939 learning_rate,
940 beta_1,
941 beta_2,
942 epsilon,
943 weight_decay,
944 clipnorm,
945 clipvalue,
946 global_clipnorm,
947 use_ema,
948 ema_momentum,
949 jit_compile,
950 name,
951 )
952 }
953
954 pub fn exponential_decay(
956 initial_learning_rate: f64,
957 decay_steps: i64,
958 decay_rate: f64,
959 staircase: bool,
960 ) -> TensorFlowExponentialDecay {
961 TensorFlowExponentialDecay::new(initial_learning_rate, decay_steps, decay_rate, staircase)
962 }
963
964 pub fn cosine_decay(
966 initial_learning_rate: f64,
967 decay_steps: i64,
968 alpha: f64,
969 ) -> TensorFlowCosineDecay {
970 TensorFlowCosineDecay::new(initial_learning_rate, decay_steps, alpha)
971 }
972}
973
974#[cfg(test)]
975mod tests {
976 use super::*;
977 use trustformers_core::Tensor;
978
979 #[test]
980 fn test_tensorflow_adam_creation() {
981 let optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
982 assert_eq!(optimizer.get_learning_rate(), 0.001);
983 assert_eq!(optimizer.get_name(), "Adam");
984 }
985
986 #[test]
987 fn test_tensorflow_adamw_creation() {
988 let optimizer = TensorFlowAdamW::with_defaults().expect("Operation failed in test");
989 assert_eq!(optimizer.get_learning_rate(), 0.001);
990 assert_eq!(optimizer.get_name(), "AdamW");
991 }
992
993 #[test]
994 fn test_tensorflow_exponential_decay() {
995 let schedule = TensorFlowExponentialDecay::new(0.1, 100, 0.96, false);
996 assert_eq!(schedule.get_lr(0), 0.1);
997 assert!(schedule.get_lr(100) < 0.1);
998 }
999
1000 #[test]
1001 fn test_tensorflow_cosine_decay() {
1002 let schedule = TensorFlowCosineDecay::new(0.1, 100, 0.0);
1003 assert_eq!(schedule.get_lr(0), 0.1);
1004 assert!(schedule.get_lr(50) < 0.1);
1005 assert!(schedule.get_lr(100) < 0.1);
1006 }
1007
1008 #[test]
1009 fn test_tensorflow_optimizer_factory() {
1010 let adam = TensorFlowOptimizerFactory::adam(
1011 0.001,
1012 0.9,
1013 0.999,
1014 1e-7,
1015 None,
1016 None,
1017 None,
1018 None,
1019 false,
1020 0.99,
1021 true,
1022 Some("TestAdam".to_string()),
1023 )
1024 .expect("Operation failed in test");
1025 assert_eq!(adam.get_name(), "TestAdam");
1026
1027 let adamw = TensorFlowOptimizerFactory::adamw(
1028 0.001,
1029 0.9,
1030 0.999,
1031 1e-7,
1032 0.01,
1033 None,
1034 None,
1035 None,
1036 false,
1037 0.99,
1038 true,
1039 Some("TestAdamW".to_string()),
1040 )
1041 .expect("Operation failed in test");
1042 assert_eq!(adamw.get_name(), "TestAdamW");
1043 }
1044
1045 #[test]
1046 fn test_learning_rate_schedule_with_optimizer() {
1047 let schedule = Box::new(TensorFlowExponentialDecay::new(0.1, 100, 0.96, false));
1048 let optimizer = TensorFlowAdam::with_schedule(
1049 schedule,
1050 0.9,
1051 0.999,
1052 1e-7,
1053 None,
1054 None,
1055 None,
1056 None,
1057 false,
1058 0.99,
1059 true,
1060 Some("ScheduledAdam".to_string()),
1061 )
1062 .expect("Operation failed in test");
1063
1064 assert_eq!(optimizer.get_learning_rate(), 0.1);
1065 }
1066
1067 #[test]
1068 fn test_variable_management() {
1069 let mut optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1070
1071 let var1 = Tensor::zeros(&[10, 10]).expect("Failed to create tensor");
1072 let var2 = Tensor::zeros(&[5, 5]).expect("Failed to create tensor");
1073
1074 optimizer
1075 .add_variable("var1".to_string(), var1)
1076 .expect("Operation failed in test");
1077 optimizer
1078 .add_variable("var2".to_string(), var2)
1079 .expect("Operation failed in test");
1080
1081 let variables = optimizer.variables();
1082 assert_eq!(variables.len(), 2);
1083 assert!(variables.contains(&"var1".to_string()));
1084 assert!(variables.contains(&"var2".to_string()));
1085 }
1086
1087 #[test]
1088 fn test_learning_rate_updates() {
1089 let mut optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1090 assert_eq!(optimizer.get_learning_rate(), 0.001);
1091
1092 optimizer.set_learning_rate(0.01).expect("Operation failed in test");
1093 assert_eq!(optimizer.get_learning_rate(), 0.01);
1094 }
1095
1096 #[test]
1097 fn test_config_serialization() {
1098 let optimizer = TensorFlowAdam::with_defaults().expect("Operation failed in test");
1099 let config = optimizer.get_config();
1100
1101 assert_eq!(config.learning_rate, 0.001);
1102 assert_eq!(config.beta_1, Some(0.9));
1103 assert_eq!(config.beta_2, Some(0.999));
1104 assert_eq!(config.epsilon, Some(1e-7));
1105 }
1106}