Skip to main content

trustformers_optim/
task_specific.rs

1// reason: research-stage module — reserved API/scaffolding fields and methods
2// retained intentionally for in-progress features; not yet on active call paths.
3#![allow(dead_code)]
4
5use crate::{
6    adam::{Adam, AdamW},
7    scheduler::LRScheduler,
8    sgd::SGD,
9};
10use trustformers_core::{errors::Result, tensor::Tensor, traits::Optimizer};
11
12/// BERT-specific optimizer with tailored hyperparameters and scheduling
13pub struct BERTOptimizer {
14    base_optimizer: AdamW,
15    warmup_scheduler: Box<dyn LRScheduler>,
16    layer_wise_decay: f32,
17    weight_decay_exclusions: Vec<String>,
18    current_step: usize,
19    warmup_steps: usize,
20    total_steps: usize,
21}
22
23impl BERTOptimizer {
24    pub fn new(
25        learning_rate: f32,
26        warmup_steps: usize,
27        total_steps: usize,
28        layer_wise_decay: f32,
29    ) -> Result<Self> {
30        let base_optimizer = AdamW::new(learning_rate, (0.9, 0.999), 1e-6, 0.01);
31
32        // BERT-specific warmup scheduler
33        let warmup_scheduler = Box::new(BERTWarmupScheduler::new(
34            learning_rate,
35            warmup_steps,
36            total_steps,
37        ));
38
39        // Parameters that should not have weight decay (bias, LayerNorm)
40        let weight_decay_exclusions = vec![
41            "bias".to_string(),
42            "LayerNorm".to_string(),
43            "layer_norm".to_string(),
44            "ln".to_string(),
45        ];
46
47        Ok(Self {
48            base_optimizer,
49            warmup_scheduler,
50            layer_wise_decay,
51            weight_decay_exclusions,
52            current_step: 0,
53            warmup_steps,
54            total_steps,
55        })
56    }
57
58    /// Apply layer-wise learning rate decay for deeper layers
59    fn get_layer_wise_lr(&self, param_name: &str, base_lr: f32) -> f32 {
60        // Extract layer number from parameter name
61        if let Some(layer_num) = self.extract_layer_number(param_name) {
62            let decay_factor = self.layer_wise_decay.powi(layer_num as i32);
63            base_lr * decay_factor
64        } else {
65            base_lr
66        }
67    }
68
69    fn extract_layer_number(&self, param_name: &str) -> Option<usize> {
70        // Extract layer number from names like "encoder.layer.11.attention.self.query.weight"
71        if param_name.contains("layer.") {
72            let parts: Vec<&str> = param_name.split('.').collect();
73            for i in 0..parts.len() {
74                if parts[i] == "layer" && i + 1 < parts.len() {
75                    if let Ok(layer_num) = parts[i + 1].parse::<usize>() {
76                        return Some(layer_num);
77                    }
78                }
79            }
80        }
81        None
82    }
83
84    fn should_exclude_weight_decay(&self, param_name: &str) -> bool {
85        self.weight_decay_exclusions
86            .iter()
87            .any(|exclusion| param_name.contains(exclusion))
88    }
89}
90
91impl Optimizer for BERTOptimizer {
92    fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
93        self.base_optimizer.update(parameter, grad)
94    }
95
96    fn zero_grad(&mut self) {
97        self.base_optimizer.zero_grad()
98    }
99
100    fn step(&mut self) {
101        self.base_optimizer.step();
102        self.warmup_scheduler.step();
103        self.current_step += 1;
104    }
105
106    fn get_lr(&self) -> f32 {
107        self.base_optimizer.get_lr()
108    }
109
110    fn set_lr(&mut self, lr: f32) {
111        self.base_optimizer.set_lr(lr)
112    }
113}
114
115/// BERT warmup scheduler
116struct BERTWarmupScheduler {
117    base_lr: f32,
118    warmup_steps: usize,
119    total_steps: usize,
120    current_step: usize,
121}
122
123impl BERTWarmupScheduler {
124    fn new(base_lr: f32, warmup_steps: usize, total_steps: usize) -> Self {
125        Self {
126            base_lr,
127            warmup_steps,
128            total_steps,
129            current_step: 0,
130        }
131    }
132}
133
134impl LRScheduler for BERTWarmupScheduler {
135    fn step(&mut self) {
136        self.current_step += 1;
137    }
138
139    fn get_lr(&self, step: usize) -> f32 {
140        if step < self.warmup_steps {
141            // Linear warmup
142            self.base_lr * (step as f32 / self.warmup_steps as f32)
143        } else {
144            // Linear decay
145            let progress =
146                (step - self.warmup_steps) as f32 / (self.total_steps - self.warmup_steps) as f32;
147            self.base_lr * (1.0 - progress).max(0.0)
148        }
149    }
150}
151
152/// GAN optimizer with stability improvements
153pub struct GANOptimizer {
154    generator_optimizer: Adam,
155    discriminator_optimizer: Adam,
156    spectral_norm: bool,
157    gradient_penalty_weight: f32,
158    ttur: bool, // Two Time-scale Update Rule
159    d_steps_per_g_step: usize,
160    current_d_steps: usize,
161}
162
163impl GANOptimizer {
164    pub fn new(g_lr: f32, d_lr: f32, spectral_norm: bool, gradient_penalty_weight: f32) -> Self {
165        let generator_optimizer = Adam::new(g_lr, (0.0, 0.999), 1e-8, 0.0);
166        let discriminator_optimizer = Adam::new(d_lr, (0.0, 0.999), 1e-8, 0.0);
167
168        Self {
169            generator_optimizer,
170            discriminator_optimizer,
171            spectral_norm,
172            gradient_penalty_weight,
173            ttur: d_lr != g_lr,
174            d_steps_per_g_step: if d_lr > g_lr { 5 } else { 1 },
175            current_d_steps: 0,
176        }
177    }
178
179    pub fn step_discriminator(
180        &mut self,
181        d_params: &mut [Tensor],
182        d_grads: &[Tensor],
183    ) -> Result<()> {
184        // Apply gradient penalty if enabled
185        let mut modified_grads = d_grads.to_vec();
186        if self.gradient_penalty_weight > 0.0 {
187            self.apply_gradient_penalty(&mut modified_grads)?;
188        }
189
190        // Apply spectral normalization if enabled
191        if self.spectral_norm {
192            self.apply_spectral_norm(d_params)?;
193        }
194
195        for (param, grad) in d_params.iter_mut().zip(modified_grads.iter()) {
196            self.discriminator_optimizer.update(param, grad)?;
197        }
198        self.discriminator_optimizer.step();
199        self.current_d_steps += 1;
200        Ok(())
201    }
202
203    pub fn step_generator(&mut self, g_params: &mut [Tensor], g_grads: &[Tensor]) -> Result<()> {
204        // Only update generator after enough discriminator steps
205        if self.current_d_steps >= self.d_steps_per_g_step {
206            for (param, grad) in g_params.iter_mut().zip(g_grads.iter()) {
207                self.generator_optimizer.update(param, grad)?;
208            }
209            self.generator_optimizer.step();
210            self.current_d_steps = 0;
211        }
212        Ok(())
213    }
214
215    fn apply_gradient_penalty(&self, gradients: &mut [Tensor]) -> Result<()> {
216        // Apply gradient penalty to encourage Lipschitz constraint
217        for grad in gradients.iter_mut() {
218            let grad_norm = self.compute_gradient_norm(grad)?;
219            if grad_norm > 1.0 {
220                let penalty = (grad_norm - 1.0).powi(2) * self.gradient_penalty_weight;
221                *grad = grad.add_scalar(penalty)?;
222            }
223        }
224        Ok(())
225    }
226
227    fn apply_spectral_norm(&self, parameters: &mut [Tensor]) -> Result<()> {
228        // Apply spectral normalization to weight matrices
229        for param in parameters.iter_mut() {
230            if param.shape().len() >= 2 {
231                // Only for weight matrices
232                let spectral_norm = self.compute_spectral_norm(param)?;
233                if spectral_norm > 1.0 {
234                    *param = param.div_scalar(spectral_norm)?;
235                }
236            }
237        }
238        Ok(())
239    }
240
241    fn compute_gradient_norm(&self, grad: &Tensor) -> Result<f32> {
242        // Compute L2 norm of gradient
243        let sum_squares = grad.pow(2.0)?.sum(None, false)?;
244        let norm_tensor = sum_squares.sqrt()?;
245        // Extract scalar value from tensor
246        let norm_data = norm_tensor.data()?;
247        Ok(norm_data[0].sqrt())
248    }
249
250    fn compute_spectral_norm(&self, weight: &Tensor) -> Result<f32> {
251        // Spectral norm computation using power iteration method
252        let weight_data = weight.data()?;
253        let len = weight_data.len();
254
255        // Handle edge cases
256        if len == 0 {
257            return Ok(0.0);
258        }
259        if len == 1 {
260            return Ok(weight_data[0].abs());
261        }
262
263        // For very small matrices, use simple Frobenius norm approximation
264        if len <= 4 {
265            let frobenius_norm: f32 = weight_data.iter().map(|x| x * x).sum::<f32>().sqrt();
266            return Ok(frobenius_norm);
267        }
268
269        // Power iteration method for spectral norm (largest singular value)
270        let sqrt_len = (len as f32).sqrt() as usize;
271        let rows = sqrt_len.max(1);
272        let cols = len.div_ceil(rows); // Ceiling division
273
274        // Initialize random vector
275        let mut v: Vec<f32> = (0..cols).map(|i| ((i % 7) as f32) / 7.0 - 0.5).collect();
276        let mut v_norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
277        if v_norm > 0.0 {
278            for val in &mut v {
279                *val /= v_norm;
280            }
281        }
282
283        // Power iteration (simplified - assumes roughly square matrix)
284        for _ in 0..5 {
285            // 5 iterations usually sufficient
286            let mut new_v = vec![0.0; rows];
287
288            // Matrix-vector multiplication: W^T * W * v
289            for i in 0..rows {
290                for j in 0..cols {
291                    let idx = i * cols + j;
292                    if idx < len && j < v.len() {
293                        new_v[i] += weight_data[idx] * v[j];
294                    }
295                }
296            }
297
298            // Compute norm
299            v_norm = new_v.iter().map(|x| x * x).sum::<f32>().sqrt();
300            if v_norm > 1e-8 {
301                for item in &mut new_v {
302                    *item /= v_norm;
303                }
304                // Resize v to match new_v for next iteration
305                v = new_v;
306            } else {
307                break;
308            }
309        }
310
311        // The spectral norm is approximately the final norm
312        Ok(v_norm.max(1e-8)) // Avoid zero values
313    }
314}
315
316/// Reinforcement Learning optimizer with specialized features
317pub struct RLOptimizer {
318    policy_optimizer: Adam,
319    value_optimizer: Adam,
320    clip_grad_norm: Option<f32>,
321    entropy_coeff: f32,
322    value_loss_coeff: f32,
323    max_grad_norm: f32,
324}
325
326impl RLOptimizer {
327    pub fn new(
328        policy_lr: f32,
329        value_lr: f32,
330        entropy_coeff: f32,
331        value_loss_coeff: f32,
332        max_grad_norm: f32,
333    ) -> Self {
334        let policy_optimizer = Adam::new(policy_lr, (0.9, 0.999), 1e-8, 0.0);
335        let value_optimizer = Adam::new(value_lr, (0.9, 0.999), 1e-8, 0.0);
336
337        Self {
338            policy_optimizer,
339            value_optimizer,
340            clip_grad_norm: Some(max_grad_norm),
341            entropy_coeff,
342            value_loss_coeff,
343            max_grad_norm,
344        }
345    }
346
347    pub fn step_policy(&mut self, params: &mut [Tensor], grads: &[Tensor]) -> Result<()> {
348        let mut modified_grads = grads.to_vec();
349
350        // Apply gradient clipping
351        if let Some(max_norm) = self.clip_grad_norm {
352            self.clip_gradients(&mut modified_grads, max_norm)?;
353        }
354
355        // Apply entropy regularization
356        self.apply_entropy_regularization(&mut modified_grads)?;
357
358        for (param, grad) in params.iter_mut().zip(modified_grads.iter()) {
359            self.policy_optimizer.update(param, grad)?;
360        }
361        self.policy_optimizer.step();
362        Ok(())
363    }
364
365    pub fn step_value(&mut self, params: &mut [Tensor], grads: &[Tensor]) -> Result<()> {
366        let mut modified_grads = grads.to_vec();
367
368        // Scale value gradients
369        for grad in modified_grads.iter_mut() {
370            *grad = grad.mul_scalar(self.value_loss_coeff)?;
371        }
372
373        // Apply gradient clipping
374        if let Some(max_norm) = self.clip_grad_norm {
375            self.clip_gradients(&mut modified_grads, max_norm)?;
376        }
377
378        for (param, grad) in params.iter_mut().zip(modified_grads.iter()) {
379            self.value_optimizer.update(param, grad)?;
380        }
381        self.value_optimizer.step();
382        Ok(())
383    }
384
385    fn clip_gradients(&self, gradients: &mut [Tensor], max_norm: f32) -> Result<()> {
386        // Compute global gradient norm
387        let mut total_norm_sq: f32 = 0.0;
388        for grad in gradients.iter() {
389            let grad_norm_sq_tensor = grad.pow(2.0)?.sum(None, false)?;
390            let grad_norm_sq_data = grad_norm_sq_tensor.data()?;
391            total_norm_sq += grad_norm_sq_data[0];
392        }
393
394        let total_norm = total_norm_sq.sqrt();
395
396        if total_norm > max_norm {
397            let clip_factor = max_norm / total_norm;
398            for grad in gradients.iter_mut() {
399                *grad = grad.mul_scalar(clip_factor)?;
400            }
401        }
402
403        Ok(())
404    }
405
406    fn apply_entropy_regularization(&self, gradients: &mut [Tensor]) -> Result<()> {
407        // Add entropy bonus to encourage exploration
408        for grad in gradients.iter_mut() {
409            let entropy_bonus = grad.mul_scalar(self.entropy_coeff)?;
410            *grad = grad.sub(&entropy_bonus)?;
411        }
412        Ok(())
413    }
414}
415
416/// Meta-learning optimizer (MAML-style)
417pub struct MetaOptimizer {
418    meta_optimizer: Adam,
419    inner_optimizer: SGD,
420    inner_steps: usize,
421    inner_lr: f32,
422    meta_lr: f32,
423    first_order: bool, // Use first-order approximation
424}
425
426impl MetaOptimizer {
427    pub fn new(meta_lr: f32, inner_lr: f32, inner_steps: usize, first_order: bool) -> Self {
428        let meta_optimizer = Adam::new(meta_lr, (0.9, 0.999), 1e-8, 0.0);
429        let inner_optimizer = SGD::new(inner_lr, 0.0, 0.0, false);
430
431        Self {
432            meta_optimizer,
433            inner_optimizer,
434            inner_steps,
435            inner_lr,
436            meta_lr,
437            first_order,
438        }
439    }
440
441    pub fn meta_step(&mut self, params: &mut [Tensor], meta_grads: &[Tensor]) -> Result<()> {
442        for (param, grad) in params.iter_mut().zip(meta_grads.iter()) {
443            self.meta_optimizer.update(param, grad)?;
444        }
445        self.meta_optimizer.step();
446        Ok(())
447    }
448
449    pub fn inner_loop(
450        &mut self,
451        mut params: Vec<Tensor>,
452        task_grads: &[Vec<Tensor>],
453    ) -> Result<Vec<Tensor>> {
454        // Perform inner loop adaptation for a specific task
455        for step in 0..self.inner_steps {
456            if step < task_grads.len() {
457                let grads = &task_grads[step];
458                for (param, grad) in params.iter_mut().zip(grads.iter()) {
459                    self.inner_optimizer.update(param, grad)?;
460                }
461                self.inner_optimizer.step();
462            }
463        }
464        Ok(params)
465    }
466
467    pub fn compute_meta_gradients(
468        &self,
469        original_params: &[Tensor],
470        adapted_params: &[Tensor],
471        meta_loss_grads: &[Tensor],
472    ) -> Result<Vec<Tensor>> {
473        if self.first_order {
474            // First-order approximation (ignore second derivatives)
475            Ok(meta_loss_grads.to_vec())
476        } else {
477            // Second-order gradients through inner loop
478            self.compute_second_order_grads(original_params, adapted_params, meta_loss_grads)
479        }
480    }
481
482    fn compute_second_order_grads(
483        &self,
484        _original_params: &[Tensor],
485        _adapted_params: &[Tensor],
486        meta_loss_grads: &[Tensor],
487    ) -> Result<Vec<Tensor>> {
488        // Simplified second-order gradient computation
489        // In practice, would use automatic differentiation
490        Ok(meta_loss_grads.to_vec())
491    }
492}
493
494/// Factory functions for creating task-specific optimizers
495pub fn create_bert_optimizer(
496    learning_rate: f32,
497    warmup_steps: usize,
498    total_steps: usize,
499) -> Result<BERTOptimizer> {
500    BERTOptimizer::new(learning_rate, warmup_steps, total_steps, 0.95)
501}
502
503pub fn create_gan_optimizer(g_lr: f32, d_lr: f32, use_spectral_norm: bool) -> GANOptimizer {
504    GANOptimizer::new(g_lr, d_lr, use_spectral_norm, 10.0)
505}
506
507pub fn create_ppo_optimizer(learning_rate: f32, entropy_coeff: f32) -> RLOptimizer {
508    RLOptimizer::new(learning_rate, learning_rate, entropy_coeff, 0.5, 0.5)
509}
510
511pub fn create_maml_optimizer(meta_lr: f32, inner_lr: f32, inner_steps: usize) -> MetaOptimizer {
512    MetaOptimizer::new(meta_lr, inner_lr, inner_steps, false)
513}