burn_optim/lr_scheduler/
linear.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)]
16pub struct LinearLrSchedulerConfig {
17 initial_lr: LearningRate,
19 final_lr: LearningRate,
21 num_iters: usize,
23}
24
25impl LinearLrSchedulerConfig {
26 pub(crate) fn build(&self) -> Result<LinearLrScheduler, String> {
28 if self.initial_lr <= 0. || self.initial_lr > 1. {
29 return Err("Initial learning rate must be greater than 0 and at most 1".into());
30 }
31 if self.final_lr < 0. || self.final_lr > 1. {
32 return Err("Final learning rate must be at least 0 and at most 1".into());
33 }
34 if self.num_iters == 0 {
35 return Err("Number of iterations must be at least 1".into());
36 }
37
38 Ok(LinearLrScheduler {
39 final_lr: self.final_lr,
40 step_size: (self.final_lr - self.initial_lr) / self.num_iters as f64,
41 remaining_iters: self.num_iters + 1,
42 })
43 }
44
45 pub fn init(&self) -> Result<ModuleLrScheduler, String> {
55 self.build().map(|s| s.into())
56 }
57}
58
59#[derive(Clone, Copy, Debug)]
63pub struct LinearLrScheduler {
64 final_lr: LearningRate,
66 step_size: f64,
68 remaining_iters: usize,
70}
71
72impl LrScheduler for LinearLrScheduler {
73 fn step(&mut self) -> LearningRate {
74 self.remaining_iters -= (self.remaining_iters != 0) as usize;
75 self.final_lr - self.step_size * self.remaining_iters as f64
76 }
77
78 fn to_record(&self) -> LrSchedulerRecord {
79 LrSchedulerRecord::from_state(&LinearLrSchedulerState {
80 remaining_iters: self.remaining_iters,
81 })
82 }
83
84 fn load_record(&mut self, record: LrSchedulerRecord) {
85 if let Some(state) = record.into_state::<LinearLrSchedulerState>() {
86 self.remaining_iters = state.remaining_iters;
87 }
88 }
89}
90
91#[derive(RecordState, Clone, Debug)]
93pub struct LinearLrSchedulerState {
94 remaining_iters: usize,
95}
96
97#[cfg(test)]
98mod tests {
99 use super::super::test_utils;
100 use super::*;
101
102 #[test]
103 fn config_initial_lr_too_low() {
104 let r = LinearLrSchedulerConfig::new(0., 0.5, 100).build();
105 assert!(r.is_err(), "Should return an error");
106 assert_eq!(
107 r.unwrap_err(),
108 "Initial learning rate must be greater than 0 and at most 1",
109 "Error messages should match",
110 );
111 }
112
113 #[test]
114 fn config_initial_lr_too_high() {
115 let r = LinearLrSchedulerConfig::new(1.5, 0.5, 100).build();
116 assert!(r.is_err(), "Should return an error");
117 assert_eq!(
118 r.unwrap_err(),
119 "Initial learning rate must be greater than 0 and at most 1",
120 "Error messages should match",
121 );
122 }
123
124 #[test]
125 fn config_final_lr_too_low() {
126 let r = LinearLrSchedulerConfig::new(0.5, -0.5, 100).build();
127 assert!(r.is_err(), "Should return an error");
128 assert_eq!(
129 r.unwrap_err(),
130 "Final learning rate must be at least 0 and at most 1",
131 "Error messages should match",
132 );
133 }
134
135 #[test]
136 fn config_final_lr_too_high() {
137 let r = LinearLrSchedulerConfig::new(0.5, 1.5, 100).build();
138 assert!(r.is_err(), "Should return an error");
139 assert_eq!(
140 r.unwrap_err(),
141 "Final learning rate must be at least 0 and at most 1",
142 "Error messages should match",
143 );
144 }
145
146 #[test]
147 fn config_num_iters_too_low() {
148 let r = LinearLrSchedulerConfig::new(0.9, 0.1, 0).build();
149 assert!(r.is_err(), "Should return an error");
150 assert_eq!(
151 r.unwrap_err(),
152 "Number of iterations must be at least 1",
153 "Error messages should match",
154 );
155 }
156
157 #[test]
158 fn test_lr_decreasing() {
159 let scheduler = LinearLrSchedulerConfig::new(0.9, 0.5, 4).build().unwrap();
160 let expected_lrs = [0.9, 0.8, 0.7, 0.6, 0.5, 0.5];
161 test_utils::check_lr_sequence(scheduler, expected_lrs);
162 }
163
164 #[test]
165 fn test_lr_increasing() {
166 let scheduler = LinearLrSchedulerConfig::new(0.01, 0.04, 3).build().unwrap();
167 let expected_lrs = [0.01, 0.02, 0.03, 0.04, 0.04];
168 test_utils::check_lr_sequence(scheduler, expected_lrs);
169 }
170
171 #[test]
172 fn test_lr_unchanging() {
173 let scheduler = LinearLrSchedulerConfig::new(0.3, 0.3, 2).build().unwrap();
174 let expected_lrs = [0.3, 0.3, 0.3, 0.3];
175 test_utils::check_lr_sequence(scheduler, expected_lrs);
176 }
177
178 #[test]
179 fn test_save_and_load() {
180 const NUM_ITERS: usize = 6;
181 let scheduler = LinearLrSchedulerConfig::new(1.0, 0.01, NUM_ITERS)
182 .build()
183 .unwrap();
184 test_utils::check_save_load(scheduler, NUM_ITERS / 3 * 2);
185 }
186}