vexus 4.0.0

A neural network builder and trainer struct to make your own AI models.
Documentation
// use crate::NeuralNetwork;
// use rand::prelude::SliceRandom;
// use rand::{Rng, RngCore};
// use statrs::distribution::{Categorical, Discrete};
// use statrs::prec;
// use statrs::statistics::Distribution;

// pub struct PPOAgent {
//     pub policy_network: NeuralNetwork,
//     pub value_network: NeuralNetwork,
//     pub old_policy_network: NeuralNetwork,

//     // Hyperparameters
//     pub policy_learning_rate: f32,
//     pub value_learning_rate: f32,
//     pub clip_ratio: f32,
//     pub value_coefficient: f32,
//     pub entropy_coefficient: f32,
//     pub gamma: f32,
//     pub gae_lambda: f32,
//     pub num_epochs: usize,
//     pub batch_size: usize,
//     pub discount_factor: f32,
//     // State for optimization (we'll need to manage gradients manually or use another library)
//     // This is a placeholder - vexus doesn't directly handle gradients.
// }

// impl PPOAgent {
//     pub fn new(
//         policy_network: NeuralNetwork,
//         value_network: NeuralNetwork,
//         policy_learning_rate: f32,
//         value_learning_rate: f32,
//         clip_ratio: f32,
//         value_coefficient: f32,
//         entropy_coefficient: f32,
//         gamma: f32,
//         gae_lambda: f32,
//         num_epochs: usize,
//         batch_size: usize,
//         discount_factor: f32,
//     ) -> Self {
//         let old_policy_network = policy_network.clone();

//         PPOAgent {
//             policy_network,
//             value_network,
//             old_policy_network,
//             policy_learning_rate,
//             value_learning_rate,
//             clip_ratio,
//             value_coefficient,
//             entropy_coefficient,
//             gamma,
//             gae_lambda,
//             num_epochs,
//             batch_size,
//             discount_factor,
//         }
//     }

//     pub fn select_action<R: Rng + ?Sized>(&mut self, state: &[f32], rng: &mut R) -> (usize, f32) {
//         self.policy_network.forward(state.into());
//         let logits = self.policy_network.get_outputs();

//         // Convert logits to probabilities using softmax
//         let probs = softmax(&logits);

//         // Sample an action based on the probabilities
//         let action = sample_from_distribution(&probs, rng);
//         let log_prob = probs[action].ln();

//         (action, log_prob)
//     }

//     pub fn get_value(&mut self, state: &[f32]) -> f32 {
//         self.value_network.forward(state.into());
//         let output = self.value_network.get_outputs();
//         // Assuming the output is a single value
//         output.get(0).copied().unwrap_or(0.0)
//     }

//     pub fn update(&mut self, experiences: &[Experience]) {
//         let num_steps = experiences.len();
//         if (num_steps == 0) {
//             return;
//         }

//         // 1. Calculate Advantages and Returns
//         let mut advantages = vec![0.0; num_steps];
//         let mut returns = vec![0.0; num_steps];
//         let mut last_advantage = 0.0;

//         for t in (0..num_steps).rev() {
//             let next_value = if t == num_steps - 1 || experiences[t].done {
//                 0.0
//             } else {
//                 self.get_value(&experiences[t + 1].state)
//             };
//             let delta = experiences[t].reward
//                 + self.gamma * next_value * if !experiences[t].done { 1.0 } else { 0.0 }
//                 - experiences[t].value;
//             advantages[t] = delta
//                 + self.gamma
//                     * self.gae_lambda
//                     * last_advantage
//                     * if !experiences[t].done { 1.0 } else { 0.0 };
//             last_advantage = advantages[t];
//             returns[t] = advantages[t] + experiences[t].value;
//         }

//         let mut indices: Vec<usize> = (0..num_steps).collect();

//         for _ in 0..self.num_epochs {
//             indices.shuffle(&mut rand::thread_rng());
//             for start in (0..num_steps).step_by(self.batch_size) {
//                 let end = (start + self.batch_size).min(num_steps);
//                 let batch_indices = &indices[start..end];

//                 let batch_states: Vec<Vec<f32>> = batch_indices
//                     .iter()
//                     .map(|&i| experiences[i].state.clone())
//                     .collect();
//                 let batch_actions: Vec<usize> = batch_indices
//                     .iter()
//                     .map(|&i| experiences[i].action)
//                     .collect();
//                 let batch_old_log_probs: Vec<f32> = batch_indices
//                     .iter()
//                     .map(|&i| experiences[i].log_prob)
//                     .collect();
//                 let batch_advantages: Vec<f32> =
//                     batch_indices.iter().map(|&i| advantages[i]).collect();
//                 let batch_returns: Vec<f32> = batch_indices.iter().map(|&i| returns[i]).collect();

//                 // 2. Forward pass old policy on batch states (already have log_probs)
//                 // 3. Forward pass current policy on batch states to get new action probabilities.
//                 let mut new_log_probs = Vec::new();
//                 for state in &batch_states {
//                     self.policy_network.forward(state.clone());
//                     let logits = self.policy_network.get_outputs();
//                     let probs = softmax(&logits);
//                     let log_probs: Vec<f32> = probs.iter().map(|&p| p.ln()).collect();
//                     new_log_probs.push(log_probs);
//                 }

//                 // 4. Calculate new log probabilities for the taken actions.
//                 let mut new_log_probs_for_actions = Vec::new();
//                 for (log_probs, &action) in new_log_probs.iter().zip(&batch_actions) {
//                     new_log_probs_for_actions.push(log_probs[action]);
//                 }

//                 // 5. Calculate the ratio of new and old probabilities.
//                 let ratios: Vec<f32> = new_log_probs_for_actions
//                     .iter()
//                     .zip(&batch_old_log_probs)
//                     .map(|(&new_log_prob, &old_log_prob)| (new_log_prob - old_log_prob).exp())
//                     .collect();

//                 // 6. Calculate the clipped surrogate objective.
//                 let mut policy_loss = 0.0;
//                 for (&ratio, &advantage) in ratios.iter().zip(&batch_advantages) {
//                     let surr1 = ratio * advantage;
//                     let surr2 =
//                         ratio.clamp(1.0 - self.clip_ratio, 1.0 + self.clip_ratio) * advantage;
//                     policy_loss += -surr1.min(surr2);
//                 }
//                 policy_loss /= batch_states.len() as f32;

//                 // 7. Forward pass value network on batch states.
//                 let mut values = Vec::new();
//                 for state in &batch_states {
//                     self.value_network.forward(state.clone());
//                     let value = self.value_network.get_outputs()[0];
//                     values.push(value);
//                 }

//                 // 8. Calculate the value function loss.
//                 let mut value_loss = 0.0;
//                 for (&value, &ret) in values.iter().zip(&batch_returns) {
//                     value_loss += (value - ret).powi(2);
//                 }
//                 value_loss /= batch_states.len() as f32;

//                 // 9. (Optional) Calculate the entropy of the new policy distribution.
//                 let mut entropy = 0.0;
//                 for log_probs in new_log_probs {
//                     for &log_prob in log_probs.iter() {
//                         entropy += -log_prob.exp() * log_prob;
//                     }
//                 }
//                 entropy /= batch_states.len() as f32;

//                 // 10. Combine the losses.
//                 let loss = policy_loss + self.value_coefficient * value_loss
//                     - self.entropy_coefficient * entropy;

//                 // 11. Perform backpropagation to get gradients.
//                 // 12. Update network weights using an optimization algorithm.
//                 // Since `vexus` does not support automatic differentiation, we need to manually update the weights.
//                 self.policy_network.backwards(vec![loss]);
//                 self.value_network.backwards(vec![loss]);
//             }
//         }

//         self.update_old_policy();
//     }

//     pub fn update_old_policy(&mut self) {
//         // Since vexus doesn't have a direct way to copy parameters,
//         // we rely on the Clone implementation of NeuralNetwork.
//         self.old_policy_network = self.policy_network.clone();
//     }
// }

// pub struct Experience {
//     pub state: Vec<f32>,
//     pub action: usize,
//     pub reward: f32,
//     pub next_state: Vec<f32>,
//     pub done: bool,
//     pub log_prob: f32,
//     pub value: f32,
// }

// fn softmax(logits: &[f32]) -> Vec<f32> {
//     let max_logit = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
//     let exp_sum: f32 = logits.iter().map(|&x| (x - max_logit).exp()).sum();
//     logits
//         .iter()
//         .map(|&x| (x - max_logit).exp() / exp_sum)
//         .collect()
// }

// fn sample_from_distribution<R: Rng + ?Sized>(probs: &[f32], rng: &mut R) -> usize {
//     let mut cumulative_probs = Vec::with_capacity(probs.len());
//     let mut cumulative_sum = 0.0;
//     for &p in probs {
//         cumulative_sum += p;
//         cumulative_probs.push(cumulative_sum);
//     }

//     let random_value: f32 = rng.random();
//     cumulative_probs
//         .iter()
//         .position(|&cumulative_prob| random_value < cumulative_prob)
//         .unwrap_or(probs.len() - 1)
// }