trustformers_optim/second_order/
lbfgs.rs1use anyhow::Result;
2use std::collections::{HashMap, VecDeque};
3use trustformers_core::tensor::Tensor;
4
5#[derive(Debug)]
11pub struct LBFGS {
12 pub learning_rate: f32,
13 pub history_size: usize,
14 pub line_search_fn: Option<LineSearchMethod>,
15 pub max_iter: usize,
16 pub tolerance_grad: f32,
17 pub tolerance_change: f32,
18
19 pub step: usize,
21 pub s_history: VecDeque<HashMap<String, Vec<f32>>>, pub y_history: VecDeque<HashMap<String, Vec<f32>>>, pub rho_history: VecDeque<f32>, pub prev_params: HashMap<String, Vec<f32>>,
25 pub prev_grads: HashMap<String, Vec<f32>>,
26}
27
28#[derive(Debug, Clone)]
29pub enum LineSearchMethod {
30 None,
31 StrongWolfe,
32 Backtracking,
33}
34
35impl Default for LBFGS {
36 fn default() -> Self {
37 Self {
38 learning_rate: 1.0,
39 history_size: 10,
40 line_search_fn: Some(LineSearchMethod::StrongWolfe),
41 max_iter: 20,
42 tolerance_grad: 1e-7,
43 tolerance_change: 1e-9,
44 step: 0,
45 s_history: VecDeque::new(),
46 y_history: VecDeque::new(),
47 rho_history: VecDeque::new(),
48 prev_params: HashMap::new(),
49 prev_grads: HashMap::new(),
50 }
51 }
52}
53
54impl LBFGS {
55 pub fn new(learning_rate: f32) -> Self {
56 Self {
57 learning_rate,
58 ..Default::default()
59 }
60 }
61
62 pub fn with_config(
63 learning_rate: f32,
64 history_size: usize,
65 line_search_fn: Option<LineSearchMethod>,
66 max_iter: usize,
67 ) -> Self {
68 Self {
69 learning_rate,
70 history_size,
71 line_search_fn,
72 max_iter,
73 ..Default::default()
74 }
75 }
76
77 pub fn step(
78 &mut self,
79 parameters: &mut HashMap<String, Tensor>,
80 gradients: &HashMap<String, Tensor>,
81 ) -> Result<()> {
82 if self.step == 0 {
84 for (name, param) in parameters.iter() {
85 self.prev_params.insert(name.clone(), param.data()?);
86 }
87 for (name, grad) in gradients.iter() {
88 self.prev_grads.insert(name.clone(), grad.data()?);
89 }
90
91 for (name, param) in parameters.iter_mut() {
93 let grad = gradients
94 .get(name)
95 .ok_or_else(|| anyhow::anyhow!("Missing gradient for parameter: {}", name))?;
96 let mut param_data = param.data()?;
97 let grad_data = grad.data()?;
98
99 for i in 0..param_data.len() {
100 param_data[i] -= self.learning_rate * grad_data[i];
101 }
102
103 *param = Tensor::new(param_data)?;
104 }
105
106 self.step += 1;
107 return Ok(());
108 }
109
110 let mut s_k = HashMap::new();
112 let mut y_k = HashMap::new();
113
114 for (name, param) in parameters.iter() {
116 let param_data = param.data()?;
117 let prev_param = self
118 .prev_params
119 .get(name)
120 .ok_or_else(|| anyhow::anyhow!("prev_params must exist for name"))?;
121
122 let s: Vec<f32> =
123 param_data.iter().zip(prev_param.iter()).map(|(p, prev_p)| p - prev_p).collect();
124 s_k.insert(name.clone(), s);
125 }
126
127 for (name, grad) in gradients.iter() {
128 let grad_data = grad.data()?;
129 let prev_grad = self
130 .prev_grads
131 .get(name)
132 .ok_or_else(|| anyhow::anyhow!("prev_grads must exist for name"))?;
133
134 let y: Vec<f32> =
135 grad_data.iter().zip(prev_grad.iter()).map(|(g, prev_g)| g - prev_g).collect();
136 y_k.insert(name.clone(), y);
137 }
138
139 let mut rho = 0.0;
141 for name in parameters.keys() {
142 let s = s_k.get(name).ok_or_else(|| anyhow::anyhow!("s_k must exist for name"))?;
143 let y = y_k.get(name).ok_or_else(|| anyhow::anyhow!("y_k must exist for name"))?;
144
145 rho += s.iter().zip(y.iter()).map(|(s_i, y_i)| s_i * y_i).sum::<f32>();
146 }
147
148 if rho.abs() < 1e-10 {
149 self.step += 1;
151 return Ok(());
152 }
153
154 rho = 1.0 / rho;
155
156 self.s_history.push_back(s_k);
158 self.y_history.push_back(y_k);
159 self.rho_history.push_back(rho);
160
161 if self.s_history.len() > self.history_size {
163 self.s_history.pop_front();
164 self.y_history.pop_front();
165 self.rho_history.pop_front();
166 }
167
168 let search_direction = self.compute_search_direction(gradients)?;
170
171 for (name, param) in parameters.iter_mut() {
173 let direction = search_direction
174 .get(name)
175 .ok_or_else(|| anyhow::anyhow!("search_direction must exist for name"))?;
176 let mut param_data = param.data()?;
177
178 for i in 0..param_data.len() {
179 param_data[i] -= self.learning_rate * direction[i];
180 }
181
182 *param = Tensor::new(param_data)?;
183 }
184
185 for (name, param) in parameters.iter() {
187 self.prev_params.insert(name.clone(), param.data()?);
188 }
189 for (name, grad) in gradients.iter() {
190 self.prev_grads.insert(name.clone(), grad.data()?);
191 }
192
193 self.step += 1;
194 Ok(())
195 }
196
197 fn compute_search_direction(
198 &self,
199 gradients: &HashMap<String, Tensor>,
200 ) -> Result<HashMap<String, Vec<f32>>> {
201 let mut q: HashMap<String, Vec<f32>> = HashMap::new();
202
203 for (name, grad) in gradients.iter() {
205 q.insert(name.clone(), grad.data()?);
206 }
207
208 let history_len = self.s_history.len();
209 let mut alpha = vec![0.0; history_len];
210
211 for i in (0..history_len).rev() {
213 let rho_i = self.rho_history[i];
214 let s_i = &self.s_history[i];
215
216 let mut alpha_i = 0.0;
217 for name in gradients.keys() {
218 let s_i_param =
219 s_i.get(name).ok_or_else(|| anyhow::anyhow!("s_i must exist for name"))?;
220 let q_param =
221 q.get(name).ok_or_else(|| anyhow::anyhow!("q must exist for name"))?;
222
223 alpha_i +=
224 s_i_param.iter().zip(q_param.iter()).map(|(s, q_val)| s * q_val).sum::<f32>();
225 }
226 alpha_i *= rho_i;
227 alpha[i] = alpha_i;
228
229 for name in gradients.keys() {
231 let y_i_param = self.y_history[i]
232 .get(name)
233 .ok_or_else(|| anyhow::anyhow!("y_history must have all gradient keys"))?;
234 let q_param =
235 q.get_mut(name).ok_or_else(|| anyhow::anyhow!("q must exist for name"))?;
236
237 for j in 0..q_param.len() {
238 q_param[j] -= alpha_i * y_i_param[j];
239 }
240 }
241 }
242
243 if !self.s_history.is_empty() {
245 let recent_idx = self.s_history.len() - 1;
246 let recent_s = &self.s_history[recent_idx];
247 let recent_y = &self.y_history[recent_idx];
248
249 let mut s_dot_y = 0.0;
250 let mut y_dot_y = 0.0;
251
252 for name in gradients.keys() {
253 let s_param = recent_s
254 .get(name)
255 .ok_or_else(|| anyhow::anyhow!("recent_s must exist for name"))?;
256 let y_param = recent_y
257 .get(name)
258 .ok_or_else(|| anyhow::anyhow!("recent_y must exist for name"))?;
259
260 s_dot_y += s_param.iter().zip(y_param.iter()).map(|(s, y)| s * y).sum::<f32>();
261 y_dot_y += y_param.iter().map(|y| y * y).sum::<f32>();
262 }
263
264 if y_dot_y > 1e-10 {
265 let gamma = s_dot_y / y_dot_y;
266 for (_, q_param) in q.iter_mut() {
267 for val in q_param.iter_mut() {
268 *val *= gamma;
269 }
270 }
271 }
272 }
273
274 for i in 0..history_len {
276 let rho_i = self.rho_history[i];
277 let y_i = &self.y_history[i];
278
279 let mut beta = 0.0;
280 for name in gradients.keys() {
281 let y_i_param =
282 y_i.get(name).ok_or_else(|| anyhow::anyhow!("y_i must exist for name"))?;
283 let q_param =
284 q.get(name).ok_or_else(|| anyhow::anyhow!("q must exist for name"))?;
285
286 beta +=
287 y_i_param.iter().zip(q_param.iter()).map(|(y, q_val)| y * q_val).sum::<f32>();
288 }
289 beta *= rho_i;
290
291 let correction = alpha[i] - beta;
292
293 for name in gradients.keys() {
295 let s_i_param = self.s_history[i]
296 .get(name)
297 .ok_or_else(|| anyhow::anyhow!("s_history must have all gradient keys"))?;
298 let q_param =
299 q.get_mut(name).ok_or_else(|| anyhow::anyhow!("q must exist for name"))?;
300
301 for j in 0..q_param.len() {
302 q_param[j] += correction * s_i_param[j];
303 }
304 }
305 }
306
307 Ok(q)
308 }
309
310 pub fn reset(&mut self) {
311 self.step = 0;
312 self.s_history.clear();
313 self.y_history.clear();
314 self.rho_history.clear();
315 self.prev_params.clear();
316 self.prev_grads.clear();
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_lbfgs_creation() {
326 let optimizer = LBFGS::new(0.01);
327 assert_eq!(optimizer.learning_rate, 0.01);
328 assert_eq!(optimizer.history_size, 10);
329 assert_eq!(optimizer.step, 0);
330 }
331
332 #[test]
333 fn test_lbfgs_with_config() {
334 let optimizer = LBFGS::with_config(0.1, 5, None, 10);
335 assert_eq!(optimizer.learning_rate, 0.1);
336 assert_eq!(optimizer.history_size, 5);
337 assert_eq!(optimizer.max_iter, 10);
338 }
339
340 #[test]
341 fn test_lbfgs_reset() {
342 let mut optimizer = LBFGS::new(0.01);
343 optimizer.step = 5;
344 optimizer.reset();
345 assert_eq!(optimizer.step, 0);
346 assert!(optimizer.s_history.is_empty());
347 assert!(optimizer.y_history.is_empty());
348 assert!(optimizer.rho_history.is_empty());
349 }
350
351 #[test]
352 fn test_lbfgs_first_step() -> Result<(), Box<dyn std::error::Error>> {
353 let mut optimizer = LBFGS::new(0.01);
354 let mut parameters = HashMap::new();
355 let mut gradients = HashMap::new();
356
357 let param_data = vec![1.0, 2.0, 3.0];
358 let grad_data = vec![0.1, 0.2, 0.3];
359
360 parameters.insert(
361 "param1".to_string(),
362 Tensor::new(param_data.clone()).expect("Failed to create tensor"),
363 );
364 gradients.insert(
365 "param1".to_string(),
366 Tensor::new(grad_data.clone()).expect("Failed to create tensor"),
367 );
368
369 optimizer.step(&mut parameters, &gradients).expect("Step failed");
370
371 assert_eq!(optimizer.step, 1);
372
373 let updated_data = parameters.get("param1").expect("Key not found").data()?;
374 for i in 0..updated_data.len() {
375 let expected = param_data[i] - 0.01 * grad_data[i];
376 assert!((updated_data[i] - expected).abs() < 1e-6);
377 }
378 Ok(())
379 }
380}