1use burn_core as burn;
2
3use crate::RecordState;
4
5use super::{
6 Optimizer,
7 decay::{WeightDecay, WeightDecayConfig},
8 module_optimizer::ModuleOptimizer,
9};
10use crate::{LearningRate, grad_clipping::GradientClippingConfig};
11
12use burn::config::Config;
13use burn::tensor::{Device, Tensor};
14
15#[derive(Config, Debug)]
17pub struct RmsPropConfig {
18 #[config(default = 0.99)]
20 alpha: f32,
21 #[config(default = 0.9)]
23 momentum: f32,
24 #[config(default = 1e-5)]
26 epsilon: f32,
27 #[config(default = false)]
29 centered: bool,
30 weight_decay: Option<WeightDecayConfig>,
32 grad_clipping: Option<GradientClippingConfig>,
34}
35
36impl RmsPropConfig {
37 pub fn build(&self) -> RmsProp {
44 let weight_decay = self.weight_decay.as_ref().map(WeightDecay::new);
45 RmsProp {
46 alpha: self.alpha,
47 centered: self.centered,
48 weight_decay,
49 momentum: RmsPropMomentum {
50 momentum: self.momentum,
51 epsilon: self.epsilon,
52 },
53 }
54 }
55
56 pub fn init(&self) -> ModuleOptimizer {
62 let mut optim = ModuleOptimizer::from(self.build());
63 if let Some(config) = &self.grad_clipping {
64 optim = optim.with_grad_clipping(config.init());
65 }
66
67 optim
68 }
69}
70
71#[derive(Clone)]
74pub struct RmsProp {
75 alpha: f32,
76 centered: bool,
78 momentum: RmsPropMomentum,
80 weight_decay: Option<WeightDecay>,
81}
82
83impl Optimizer for RmsProp {
84 type State<const D: usize> = RmsPropState<D>;
85
86 fn step<const D: usize>(
87 &self,
88 lr: LearningRate,
89 tensor: Tensor<D>,
90 mut grad: Tensor<D>,
91 state: Option<Self::State<D>>,
92 ) -> (Tensor<D>, Option<Self::State<D>>) {
93 let mut state_square_avg = None;
95 let mut state_centered = None;
96 let mut state_momentum = None;
97 if let Some(state) = state {
98 state_square_avg = Some(state.square_avg);
99 state_centered = Some(state.centered);
100 state_momentum = state.momentum;
101 }
102
103 if let Some(weight_decay) = &self.weight_decay {
105 grad = weight_decay.transform(grad, tensor.clone());
106 }
107
108 let (grad, state_square_avg) =
110 SquareAvgState::transform(self.alpha, grad, state_square_avg);
111
112 let (grad, state_square_avg, state_centered) = CenteredState::transform(
114 self.alpha,
115 self.centered,
116 grad,
117 state_square_avg,
118 state_centered,
119 );
120
121 let (grad, state_centered, state_momentum) =
123 self.momentum
124 .transform(grad, state_centered, state_momentum);
125
126 let state = RmsPropState::new(state_square_avg, state_centered, state_momentum);
128
129 let delta = grad.mul_scalar(lr);
131 (tensor - delta, Some(state))
132 }
133
134 fn to_device<const D: usize>(mut state: Self::State<D>, device: &Device) -> Self::State<D> {
135 state.square_avg = state.square_avg.to_device(device);
136 state.centered = state.centered.to_device(device);
137 state.momentum = state.momentum.map(|momentum| momentum.to_device(device));
138 state
139 }
140}
141
142#[derive(RecordState, Clone, new)]
144pub struct RmsPropState<const D: usize> {
145 pub square_avg: SquareAvgState<D>,
147 pub centered: CenteredState<D>,
149 pub momentum: Option<RmsPropMomentumState<D>>,
151}
152
153#[derive(RecordState, Clone, new)]
155pub struct SquareAvgState<const D: usize> {
156 pub square_avg: Tensor<D>,
158}
159
160impl<const D: usize> SquareAvgState<D> {
161 fn transform(alpha: f32, grad: Tensor<D>, state: Option<Self>) -> (Tensor<D>, Self) {
163 match state {
164 Some(state) => {
165 let square_avg = state
166 .square_avg
167 .mul_scalar(alpha)
168 .add(grad.clone().square().mul_scalar(1. - alpha));
169 (grad, Self { square_avg })
170 }
171 _ => {
172 let square_avg = grad.clone().square().mul_scalar(1. - alpha);
173 (grad, Self { square_avg })
174 }
175 }
176 }
177
178 pub fn to_device(mut self, device: &Device) -> Self {
188 self.square_avg = self.square_avg.to_device(device);
189 self
190 }
191}
192
193#[derive(RecordState, Clone, new)]
195pub struct CenteredState<const D: usize> {
196 pub grad_avg: Option<Tensor<D>>,
198 pub avg: Tensor<D>,
200}
201
202impl<const D: usize> CenteredState<D> {
203 fn transform(
205 alpha: f32,
206 centered: bool,
207 grad: Tensor<D>,
208 square_avg_state: SquareAvgState<D>,
209 centered_state: Option<Self>,
210 ) -> (Tensor<D>, SquareAvgState<D>, Self) {
211 if centered {
212 let grad_avg_constant = grad.clone().mul_scalar(1. - alpha);
213 let grad_avg = match centered_state {
214 Some(state) => state
215 .grad_avg
216 .map_or(grad_avg_constant.clone(), move |grad_avg| {
217 grad_avg.mul_scalar(alpha).add(grad_avg_constant)
218 }),
219 _ => grad_avg_constant,
220 };
221 let avg = square_avg_state
222 .square_avg
223 .clone()
224 .sub(grad_avg.clone().square());
225
226 (
227 grad,
228 square_avg_state,
229 Self {
230 grad_avg: Some(grad_avg),
231 avg,
232 },
233 )
234 } else {
235 (
236 grad,
237 square_avg_state.clone(),
238 Self {
239 grad_avg: None,
240 avg: square_avg_state.square_avg,
241 },
242 )
243 }
244 }
245
246 pub fn to_device(mut self, device: &Device) -> Self {
256 self.grad_avg = self.grad_avg.map(|grad_avg| grad_avg.to_device(device));
257 self.avg = self.avg.to_device(device);
258 self
259 }
260}
261
262#[derive(Clone)]
265pub struct RmsPropMomentum {
266 momentum: f32,
267 epsilon: f32,
268}
269
270impl RmsPropMomentum {
271 fn transform<const D: usize>(
273 &self,
274 grad: Tensor<D>,
275 centered_state: CenteredState<D>,
276 momentum_state: Option<RmsPropMomentumState<D>>,
277 ) -> (Tensor<D>, CenteredState<D>, Option<RmsPropMomentumState<D>>) {
278 let grad = grad.div(centered_state.avg.clone().sqrt().add_scalar(self.epsilon));
279
280 if self.momentum > 0. {
281 let buf = match momentum_state {
282 Some(state) => state.buf.mul_scalar(self.momentum).add(grad),
283 _ => grad,
284 };
285 (
286 buf.clone(),
287 centered_state,
288 Some(RmsPropMomentumState { buf }),
289 )
290 } else {
291 (grad, centered_state, None)
292 }
293 }
294}
295
296#[derive(RecordState, Clone, new)]
298pub struct RmsPropMomentumState<const D: usize> {
299 buf: Tensor<D>,
300}
301
302impl<const D: usize> RmsPropMomentumState<D> {
303 pub fn to_device(mut self, device: &Device) -> Self {
313 self.buf = self.buf.to_device(device);
314 self
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use burn::tensor::Tolerance;
321
322 use super::*;
323 use crate::optim::GradientsParams;
324 use burn::module::Param;
325 use burn::tensor::{Distribution, Tensor, TensorData};
326 use burn_nn::{Linear, LinearConfig};
327
328 type FT = f32;
329
330 const LEARNING_RATE: LearningRate = 0.01;
331
332 #[test]
333 fn test_rmsprop_optimizer_save_load_state() {
334 let device = Device::default().autodiff();
335 let linear = LinearConfig::new(6, 6).init(&device);
336 let x = Tensor::<2>::random([2, 6], Distribution::Default, &device);
337 let mut optimizer = create_rmsprop();
338 let grads = linear.forward(x).backward();
339 let grads = GradientsParams::from_grads(grads, &linear);
340 let _linear = optimizer.step(LEARNING_RATE, linear, grads);
341
342 let bytes = optimizer.into_bytes().unwrap();
343 assert!(!bytes.is_empty());
344
345 #[cfg(feature = "std")]
346 optimizer
347 .save(std::env::temp_dir().as_path().join("test_optim_rmsprop"))
348 .unwrap();
349
350 let state_optim_before = optimizer.to_record();
351 let optimizer = create_rmsprop().from_bytes(bytes).unwrap();
352 let state_optim_after = optimizer.to_record();
353
354 assert_eq!(state_optim_before.len(), state_optim_after.len());
355 }
356
357 #[test]
359 fn test_rmsprop_optimizer_with_numbers_basic() {
360 let device = Device::default().autodiff();
361 let linear = given_linear_layer(
362 TensorData::from([
363 [1., 1., 1., 1., 1., 1.],
364 [1., 1., 1., 1., 1., 1.],
365 [1., 1., 1., 1., 1., 1.],
366 [1., 1., 1., 1., 1., 1.],
367 [1., 1., 1., 1., 1., 1.],
368 [1., 1., 1., 1., 1., 1.],
369 ]),
370 TensorData::from([0.5, 0.5, 0.5, 0.5, 0.5, 0.5]),
371 &device,
372 );
373 let x_1 = Tensor::<2>::from_floats(
374 [
375 [0.6294, 0.0940, 0.8176, 0.8824, 0.5228, 0.4310],
376 [0.7152, 0.9559, 0.7893, 0.5684, 0.5939, 0.8883],
377 ],
378 &device,
379 )
380 .require_grad();
381 let x_2 = Tensor::<2>::from_floats(
382 [
383 [0.8491, 0.2108, 0.8939, 0.4433, 0.5527, 0.2528],
384 [0.3270, 0.0412, 0.5538, 0.9605, 0.3195, 0.9085],
385 ],
386 &device,
387 )
388 .require_grad();
389
390 let mut optimizer = RmsPropConfig::new()
391 .with_alpha(0.99)
392 .with_epsilon(1e-8)
393 .with_weight_decay(WeightDecayConfig::new(0.05).into())
394 .with_momentum(0.9)
395 .with_centered(false)
396 .init();
397
398 let grads = linear.forward(x_1).backward();
400 let grads = GradientsParams::from_grads(grads, &linear);
401 let linear = optimizer.step(LEARNING_RATE, linear, grads);
402
403 let grads = linear.forward(x_2).backward();
405 let grads = GradientsParams::from_grads(grads, &linear);
406 let linear = optimizer.step(LEARNING_RATE, linear, grads);
407
408 let state_updated = linear;
410
411 let (weight_updated, bias_updated) = (
412 state_updated.weight.to_data(),
413 state_updated.bias.unwrap().to_data(),
414 );
415
416 let weights_expected = TensorData::from([
420 [0.743937, 0.743937, 0.743937, 0.743937, 0.743937, 0.743937],
421 [0.783809, 0.783809, 0.783809, 0.783809, 0.783809, 0.783809],
422 [0.742881, 0.742881, 0.742881, 0.742881, 0.742881, 0.742881],
423 [0.740366, 0.740366, 0.740366, 0.740366, 0.740366, 0.740366],
424 [0.748005, 0.748005, 0.748005, 0.748005, 0.748005, 0.748005],
425 [0.743710, 0.743710, 0.743710, 0.743710, 0.743710, 0.743710],
426 ]);
427 let bias_expected =
428 TensorData::from([0.239199, 0.239199, 0.239199, 0.239199, 0.239199, 0.239199]);
429
430 let tolerance = Tolerance::absolute(1e-6);
431 bias_updated.assert_approx_eq::<FT>(&bias_expected, tolerance);
432 weight_updated.assert_approx_eq::<FT>(&weights_expected, tolerance);
433 }
434
435 #[test]
436 fn test_rmsprop_optimizer_with_numbers() {
437 let device = Device::default().autodiff();
438 let linear = given_linear_layer(
439 TensorData::from([
440 [-0.3206, 0.1374, 0.4043, 0.3200, 0.0859, 0.0671],
441 [0.0777, -0.0185, -0.3667, 0.2550, 0.1955, -0.2922],
442 [-0.0190, 0.0346, -0.2962, 0.2484, -0.2780, 0.3130],
443 [-0.2980, -0.2214, -0.3715, -0.2981, -0.0761, 0.1626],
444 [0.3300, -0.2182, 0.3717, -0.1729, 0.3796, -0.0304],
445 [-0.0159, -0.0120, 0.1258, 0.1921, 0.0293, 0.3833],
446 ]),
447 TensorData::from([-0.3905, 0.0884, -0.0970, 0.1176, 0.1366, 0.0130]),
448 &device,
449 );
450 let x_1 = Tensor::<2>::from_floats(
451 [
452 [0.6294, 0.0940, 0.8176, 0.8824, 0.5228, 0.4310],
453 [0.7152, 0.9559, 0.7893, 0.5684, 0.5939, 0.8883],
454 ],
455 &device,
456 )
457 .require_grad();
458 let x_2 = Tensor::<2>::from_floats(
459 [
460 [0.8491, 0.2108, 0.8939, 0.4433, 0.5527, 0.2528],
461 [0.3270, 0.0412, 0.5538, 0.9605, 0.3195, 0.9085],
462 ],
463 &device,
464 )
465 .require_grad();
466
467 let mut optimizer = RmsPropConfig::new()
468 .with_alpha(0.99)
469 .with_epsilon(1e-8)
470 .with_weight_decay(WeightDecayConfig::new(0.05).into())
471 .with_momentum(0.9)
472 .with_centered(false)
473 .init();
474
475 let grads = linear.forward(x_1).backward();
476 let grads = GradientsParams::from_grads(grads, &linear);
477 let linear = optimizer.step(LEARNING_RATE, linear, grads);
478
479 let grads = linear.forward(x_2).backward();
480 let grads = GradientsParams::from_grads(grads, &linear);
481 let linear = optimizer.step(LEARNING_RATE, linear, grads);
482
483 let state_updated = linear;
484 let weights_expected = TensorData::from([
485 [
486 -0.576399, -0.118494, 0.148353, 0.064070, -0.169983, -0.188779,
487 ],
488 [
489 -0.135571, -0.231448, -0.578445, 0.041143, -0.018162, -0.504207,
490 ],
491 [
492 -0.275990, -0.222397, -0.553153, -0.008625, -0.534956, 0.055967,
493 ],
494 [
495 -0.557575, -0.480979, -0.631072, -0.557675, -0.335686, -0.096997,
496 ],
497 [
498 0.078313, -0.469618, 0.119993, -0.424341, 0.127890, -0.281912,
499 ],
500 [
501 -0.271996, -0.268097, -0.130324, -0.064037, -0.226805, 0.127126,
502 ],
503 ]);
504 let bias_expected = TensorData::from([
505 -0.651299, -0.172400, -0.357800, -0.143200, -0.124200, -0.247800,
506 ]);
507
508 let (weight_updated, bias_updated) = (
509 state_updated.weight.to_data(),
510 state_updated.bias.unwrap().to_data(),
511 );
512
513 let tolerance = Tolerance::absolute(1e-6);
517 bias_updated.assert_approx_eq::<FT>(&bias_expected, tolerance);
518 weight_updated.assert_approx_eq::<FT>(&weights_expected, tolerance);
519 }
520
521 fn given_linear_layer(weight: TensorData, bias: TensorData, device: &Device) -> Linear {
522 Linear {
523 weight: Param::from_data(weight, device),
524 bias: Some(Param::from_data(bias, device)),
525 }
526 }
527
528 fn create_rmsprop() -> ModuleOptimizer {
529 RmsPropConfig {
530 alpha: 0.99,
531 epsilon: 1e-9,
532 centered: false,
533 weight_decay: Some(WeightDecayConfig { penalty: 0.05 }),
534 momentum: 0.9,
535 grad_clipping: None,
536 }
537 .init()
538 }
539}