burn_optim/lr_scheduler/
exponential.rs1use burn_core as burn;
2
3use super::{LrScheduler, LrSchedulerRecord, String};
4use crate::LearningRate;
5use crate::RecordState;
6use crate::lr_scheduler::module_lr_scheduler::ModuleLrScheduler;
7use burn::config::Config;
8
9#[derive(Config, Debug)]
15pub struct ExponentialLrSchedulerConfig {
16 initial_lr: LearningRate,
18 gamma: f64,
20}
21
22impl ExponentialLrSchedulerConfig {
23 pub(crate) fn build(&self) -> Result<ExponentialLrScheduler, String> {
25 if self.initial_lr <= 0. || self.initial_lr > 1. {
26 return Err("Initial learning rate must be greater than 0 and at most 1".into());
27 }
28 if self.gamma <= 0. || self.gamma > 1. {
29 return Err("Gamma must be greater than 0 and at most 1".into());
30 }
31
32 Ok(ExponentialLrScheduler {
33 previous_lr: self.initial_lr / self.gamma,
36 gamma: self.gamma,
37 })
38 }
39
40 pub fn init(&self) -> Result<ModuleLrScheduler, String> {
49 self.build().map(|s| s.into())
50 }
51}
52
53#[derive(Clone, Copy, Debug)]
57pub struct ExponentialLrScheduler {
58 previous_lr: LearningRate,
60 gamma: f64,
62}
63
64impl LrScheduler for ExponentialLrScheduler {
65 fn step(&mut self) -> LearningRate {
66 self.previous_lr *= self.gamma;
67 self.previous_lr
68 }
69
70 fn to_record(&self) -> LrSchedulerRecord {
71 LrSchedulerRecord::from_state(&ExponentialLrSchedulerState {
72 previous_lr: self.previous_lr,
73 })
74 }
75
76 fn load_record(&mut self, record: LrSchedulerRecord) {
77 if let Some(state) = record.into_state::<ExponentialLrSchedulerState>() {
78 self.previous_lr = state.previous_lr;
79 }
80 }
81}
82
83#[derive(RecordState, Clone, Debug)]
85pub struct ExponentialLrSchedulerState {
86 previous_lr: f64,
88}
89
90#[cfg(test)]
91mod tests {
92 use super::super::test_utils;
93 use super::*;
94
95 #[test]
96 fn config_initial_lr_too_low() {
97 let r = ExponentialLrSchedulerConfig::new(0., 0.5).build();
98 assert!(r.is_err(), "Should return an error");
99 assert_eq!(
100 r.unwrap_err(),
101 "Initial learning rate must be greater than 0 and at most 1",
102 "Error messages should match",
103 );
104 }
105
106 #[test]
107 fn config_initial_lr_too_high() {
108 let r = ExponentialLrSchedulerConfig::new(1.5, 0.5).build();
109 assert!(r.is_err(), "Should return an error");
110 assert_eq!(
111 r.unwrap_err(),
112 "Initial learning rate must be greater than 0 and at most 1",
113 "Error messages should match",
114 );
115 }
116
117 #[test]
118 fn config_gamma_too_low() {
119 let r = ExponentialLrSchedulerConfig::new(0.5, 0.0).build();
120 assert!(r.is_err(), "Should return an error");
121 assert_eq!(
122 r.unwrap_err(),
123 "Gamma must be greater than 0 and at most 1",
124 "Error messages should match",
125 );
126 }
127
128 #[test]
129 fn config_gamma_too_high() {
130 let r = ExponentialLrSchedulerConfig::new(0.5, 1.5).build();
131 assert!(r.is_err(), "Should return an error");
132 assert_eq!(
133 r.unwrap_err(),
134 "Gamma must be greater than 0 and at most 1",
135 "Error messages should match",
136 );
137 }
138
139 #[test]
140 fn test_lr_change() {
141 let scheduler = ExponentialLrSchedulerConfig::new(0.8, 0.1).build().unwrap();
142 let expected_lrs = [0.8, 0.08, 0.008, 0.0008, 0.00008];
143 test_utils::check_lr_sequence(scheduler, expected_lrs);
144 }
145
146 #[test]
147 fn test_save_and_load() {
148 let scheduler = ExponentialLrSchedulerConfig::new(0.083, 0.3)
149 .build()
150 .unwrap();
151 test_utils::check_save_load(scheduler, 7);
152 }
153}