burn_optim/lr_scheduler/
step.rs1use burn_core as burn;
2
3use burn::config::Config;
4
5use super::{LrScheduler, LrSchedulerRecord, String};
6use crate::lr_scheduler::module_lr_scheduler::ModuleLrScheduler;
7use crate::{LearningRate, RecordState};
8
9#[derive(Config, Debug)]
23pub struct StepLrSchedulerConfig {
24 initial_lr: LearningRate,
26 step_size: usize,
29 #[config(default = 0.1)]
31 gamma: f64,
32}
33
34impl StepLrSchedulerConfig {
35 pub(crate) fn build(&self) -> Result<StepLrScheduler, String> {
37 if self.step_size == 0 {
38 return Err("Step size must be greater than 0".into());
39 }
40
41 if self.initial_lr <= 0.0 {
44 log::warn!(
45 "Initial learning rate value of {} is not a positive number. Ignore this warning \
46 if it is intended.",
47 self.initial_lr
48 );
49 }
50 if self.gamma <= 0.0 || self.gamma >= 1.0 {
51 log::warn!(
52 "Gamma value of {} is out of range (0.0, 1.0). Ignore this warning if it is \
53 intended.",
54 self.gamma
55 );
56 }
57
58 Ok(StepLrScheduler {
59 init_lr: self.initial_lr,
60 step_size: self.step_size,
61 gamma: self.gamma,
62 iter_idx: -1,
63 })
64 }
65
66 pub fn init(&self) -> Result<ModuleLrScheduler, String> {
72 self.build().map(|s| s.into())
73 }
74}
75
76#[derive(Clone, Debug)]
78pub struct StepLrScheduler {
79 init_lr: LearningRate,
80 step_size: usize,
81 gamma: f64,
82 iter_idx: i32,
85}
86
87impl LrScheduler for StepLrScheduler {
88 fn step(&mut self) -> LearningRate {
89 self.iter_idx = self
90 .iter_idx
91 .checked_add(1)
92 .expect("`.step()` should be called no more than `i32::MAX + 1` times");
93 self.init_lr
95 * self
96 .gamma
97 .powi((self.iter_idx as usize / self.step_size) as i32)
98 }
99
100 fn to_record(&self) -> LrSchedulerRecord {
101 LrSchedulerRecord::from_state(&StepLrSchedulerState {
102 iter_idx: self.iter_idx,
103 })
104 }
105
106 fn load_record(&mut self, record: LrSchedulerRecord) {
107 if let Some(state) = record.into_state::<StepLrSchedulerState>() {
108 self.iter_idx = state.iter_idx;
109 }
110 }
111}
112
113#[derive(RecordState, Clone, Debug)]
115pub struct StepLrSchedulerState {
116 iter_idx: i32,
117}
118
119#[cfg(test)]
120mod tests {
121 use super::super::test_utils;
122 use super::*;
123
124 #[test]
143 fn test_config_step_size_zero() {
144 let r = StepLrSchedulerConfig::new(1.0, 0).build();
145 assert!(r.is_err(), "Should return an error");
146 }
147
148 #[test]
149 fn test_config_step_size_nonzero() {
150 let r = StepLrSchedulerConfig::new(1.0, 1).build();
151 assert!(r.is_ok(), "Should return a success value");
152 }
153
154 #[test]
155 fn test_config_default_gamma() {
156 const INIT_LR: LearningRate = 0.4;
157 const STEP_SIZE: usize = 2;
158
159 let mut default = StepLrSchedulerConfig::new(INIT_LR, STEP_SIZE)
160 .build()
161 .unwrap();
162 let mut explicit = StepLrSchedulerConfig::new(INIT_LR, STEP_SIZE)
163 .with_gamma(0.1)
164 .build()
165 .unwrap();
166 test_utils::compare_steps(&mut default, &mut explicit, 3 * STEP_SIZE);
167 }
168
169 #[test]
170 fn test_lr_decreasing() {
171 let scheduler = StepLrSchedulerConfig::new(0.5, 3)
172 .with_gamma(0.1)
173 .build()
174 .unwrap();
175 let expected_lrs = [0.5, 0.5, 0.5, 0.05, 0.05, 0.05, 0.005, 0.005, 0.005];
176 test_utils::check_lr_sequence(scheduler, expected_lrs);
177 }
178
179 #[test]
180 fn test_lr_increasing() {
181 let scheduler = StepLrSchedulerConfig::new(0.1, 2)
182 .with_gamma(2.0)
183 .build()
184 .unwrap();
185 let expected_lrs = [0.1, 0.1, 0.2, 0.2, 0.4, 0.4];
186 test_utils::check_lr_sequence(scheduler, expected_lrs);
187 }
188
189 #[test]
190 fn test_lr_unchanging() {
191 let scheduler = StepLrSchedulerConfig::new(3.1, 1)
192 .with_gamma(1.0)
193 .build()
194 .unwrap();
195 let expected_lrs = [3.1, 3.1, 3.1];
196 test_utils::check_lr_sequence(scheduler, expected_lrs);
197 }
198
199 #[test]
200 fn test_save_and_load() {
201 const STEP_SIZE: usize = 10;
202
203 let scheduler = StepLrSchedulerConfig::new(0.007, STEP_SIZE)
204 .with_gamma(0.03)
205 .build()
206 .unwrap();
207 test_utils::check_save_load(scheduler, 3 * STEP_SIZE / 2);
208 }
209
210 #[test]
213 fn test_number_of_calls_within_limit() {
214 let mut scheduler = StepLrSchedulerConfig::new(0.1, 2).build().unwrap();
216 scheduler.load_record(LrSchedulerRecord::from_state(&StepLrSchedulerState {
217 iter_idx: i32::MAX - 1,
218 }));
219 scheduler.step();
220 }
221
222 #[test]
223 #[should_panic = "i32::MAX"]
224 fn test_number_of_calls_over_limit() {
225 let mut scheduler = StepLrSchedulerConfig::new(0.1, 2).build().unwrap();
227 scheduler.load_record(LrSchedulerRecord::from_state(&StepLrSchedulerState {
228 iter_idx: i32::MAX - 1,
229 }));
230 scheduler.step();
231 scheduler.step();
232 }
233}