Skip to main content

torsh_optim/
adagrad.rs

1//! AdaGrad optimizer
2
3use crate::{
4    optimizer::BaseOptimizer, Optimizer, OptimizerError, OptimizerResult, OptimizerState,
5    ParamGroup,
6};
7use parking_lot::RwLock;
8use std::collections::HashMap;
9use std::ops::Add;
10use std::sync::Arc;
11use torsh_core::error::Result;
12use torsh_tensor::Tensor;
13
14/// AdaGrad optimizer
15pub struct AdaGrad {
16    base: BaseOptimizer,
17    #[allow(dead_code)]
18    lr_decay: f32,
19    #[allow(dead_code)]
20    weight_decay: f32,
21    #[allow(dead_code)]
22    initial_accumulator_value: f32,
23    #[allow(dead_code)]
24    eps: f32,
25}
26
27impl AdaGrad {
28    /// Create a new AdaGrad optimizer
29    pub fn new(
30        params: Vec<Arc<RwLock<Tensor>>>,
31        lr: Option<f32>,
32        lr_decay: Option<f32>,
33        weight_decay: Option<f32>,
34        initial_accumulator_value: Option<f32>,
35        eps: Option<f32>,
36    ) -> Self {
37        let lr = lr.unwrap_or(1e-2);
38        let lr_decay = lr_decay.unwrap_or(0.0);
39        let weight_decay = weight_decay.unwrap_or(0.0);
40        let initial_accumulator_value = initial_accumulator_value.unwrap_or(0.0);
41        let eps = eps.unwrap_or(1e-10);
42
43        let mut defaults = HashMap::new();
44        defaults.insert("lr".to_string(), lr);
45        defaults.insert("lr_decay".to_string(), lr_decay);
46        defaults.insert("weight_decay".to_string(), weight_decay);
47        defaults.insert(
48            "initial_accumulator_value".to_string(),
49            initial_accumulator_value,
50        );
51        defaults.insert("eps".to_string(), eps);
52
53        let param_group = ParamGroup::new(params, lr);
54
55        let base = BaseOptimizer {
56            param_groups: vec![param_group],
57            state: HashMap::new(),
58            optimizer_type: "AdaGrad".to_string(),
59            defaults,
60        };
61
62        Self {
63            base,
64            lr_decay,
65            weight_decay,
66            initial_accumulator_value,
67            eps,
68        }
69    }
70}
71
72impl Optimizer for AdaGrad {
73    fn step(&mut self) -> OptimizerResult<()> {
74        for group in &mut self.base.param_groups {
75            for param_arc in &group.params {
76                let mut param = param_arc.write();
77
78                // Check if parameter has gradients
79                if !param.has_grad() {
80                    continue;
81                }
82
83                let grad = param
84                    .grad()
85                    .expect("gradient should exist after has_grad check");
86                let param_id = format!("{:p}", param_arc.as_ref());
87
88                // Apply weight decay to gradient if specified
89                let mut grad = grad;
90                if self.weight_decay != 0.0 {
91                    let weight_decay_term = param
92                        .mul_scalar(self.weight_decay)
93                        .map_err(OptimizerError::TensorError)?;
94                    grad = grad
95                        .add(&weight_decay_term)
96                        .map_err(OptimizerError::TensorError)?;
97                }
98
99                // Get or initialize optimizer state
100                let needs_init = !self.base.state.contains_key(&param_id);
101                let state = self
102                    .base
103                    .state
104                    .entry(param_id.clone())
105                    .or_insert_with(HashMap::new);
106
107                if needs_init {
108                    // Initialize sum of squares with initial_accumulator_value
109                    let mut sum_of_squares = torsh_tensor::creation::zeros_like(&param)?;
110                    if self.initial_accumulator_value != 0.0 {
111                        sum_of_squares
112                            .add_scalar_(self.initial_accumulator_value)
113                            .expect("adding initial accumulator value should succeed");
114                    }
115                    state.insert("sum_of_squares".to_string(), sum_of_squares);
116                    state.insert(
117                        "step".to_string(),
118                        torsh_tensor::creation::zeros_like(&param)?,
119                    );
120                }
121
122                let mut sum_of_squares = state
123                    .get("sum_of_squares")
124                    .expect("sum_of_squares state should exist")
125                    .clone();
126                let mut step_tensor = state.get("step").expect("step state should exist").clone();
127
128                // Increment step count
129                step_tensor
130                    .add_scalar_(1.0)
131                    .map_err(OptimizerError::TensorError)?;
132                let step = step_tensor.to_vec().map_err(OptimizerError::TensorError)?[0] as f32;
133
134                // Update sum of squares: sum_of_squares = sum_of_squares + grad^2
135                // `add` is non-mutating; reassign so the accumulator actually grows.
136                let grad_squared = grad.mul_op(&grad).map_err(OptimizerError::TensorError)?;
137                sum_of_squares = sum_of_squares
138                    .add(&grad_squared)
139                    .map_err(OptimizerError::TensorError)?;
140
141                // Compute learning rate with decay: clr = lr / (1 + (step - 1) * lr_decay)
142                let clr = if self.lr_decay != 0.0 {
143                    group.lr / (1.0 + (step - 1.0) * self.lr_decay)
144                } else {
145                    group.lr
146                };
147
148                // Compute standard deviation: std = sqrt(sum_of_squares) + eps
149                let std = sum_of_squares
150                    .sqrt()
151                    .map_err(OptimizerError::TensorError)?
152                    .add_scalar(self.eps)
153                    .map_err(OptimizerError::TensorError)?;
154
155                // Apply update: param = param - clr * grad / std
156                // `sub` is non-mutating; write the new tensor back into `*param`.
157                let update = grad
158                    .div(&std)
159                    .map_err(OptimizerError::TensorError)?
160                    .mul_scalar(clr)
161                    .map_err(OptimizerError::TensorError)?;
162                crate::param_update::sub_assign(&mut param, &update)
163                    .map_err(OptimizerError::TensorError)?;
164
165                // Update state
166                state.insert("sum_of_squares".to_string(), sum_of_squares);
167                state.insert("step".to_string(), step_tensor);
168            }
169        }
170
171        Ok(())
172    }
173
174    fn zero_grad(&mut self) {
175        self.base.zero_grad();
176    }
177
178    fn get_lr(&self) -> Vec<f32> {
179        self.base.get_lr()
180    }
181
182    fn set_lr(&mut self, lr: f32) {
183        self.base.set_lr(lr);
184    }
185
186    fn set_lrs(&mut self, lrs: &[f32]) {
187        self.base.set_lrs(lrs);
188    }
189
190    fn add_param_group(&mut self, params: Vec<Arc<RwLock<Tensor>>>, options: HashMap<String, f32>) {
191        self.base.add_param_group(params, options);
192    }
193
194    fn parameters(&self) -> Vec<Arc<RwLock<Tensor>>> {
195        self.base.parameters()
196    }
197
198    fn state_dict(&self) -> OptimizerResult<OptimizerState> {
199        self.base.state_dict()
200    }
201
202    fn load_state_dict(&mut self, state: OptimizerState) -> OptimizerResult<()> {
203        self.base.load_state_dict(state)
204    }
205}
206
207/// Builder for AdaGrad optimizer
208pub struct AdaGradBuilder {
209    lr: f32,
210    lr_decay: f32,
211    weight_decay: f32,
212    initial_accumulator_value: f32,
213    eps: f32,
214}
215
216impl AdaGradBuilder {
217    pub fn new() -> Self {
218        Self {
219            lr: 1e-2,
220            lr_decay: 0.0,
221            weight_decay: 0.0,
222            initial_accumulator_value: 0.0,
223            eps: 1e-10,
224        }
225    }
226
227    pub fn lr(mut self, lr: f32) -> Self {
228        self.lr = lr;
229        self
230    }
231
232    pub fn lr_decay(mut self, lr_decay: f32) -> Self {
233        self.lr_decay = lr_decay;
234        self
235    }
236
237    pub fn weight_decay(mut self, weight_decay: f32) -> Self {
238        self.weight_decay = weight_decay;
239        self
240    }
241
242    pub fn initial_accumulator_value(mut self, value: f32) -> Self {
243        self.initial_accumulator_value = value;
244        self
245    }
246
247    pub fn eps(mut self, eps: f32) -> Self {
248        self.eps = eps;
249        self
250    }
251
252    pub fn build(self, params: Vec<Arc<RwLock<Tensor>>>) -> AdaGrad {
253        AdaGrad::new(
254            params,
255            Some(self.lr),
256            Some(self.lr_decay),
257            Some(self.weight_decay),
258            Some(self.initial_accumulator_value),
259            Some(self.eps),
260        )
261    }
262}
263
264impl Default for AdaGradBuilder {
265    fn default() -> Self {
266        Self::new()
267    }
268}