- Architecture du Réseau
- Fonctions d'Activation
- Propagation Avant
- Rétropropagation
- Entraînement sur Données Bancaires
- Performance et Résultats
- Avantages de Rust
pub struct DeepNeuralNetwork {
pub layers: Vec<Layer>,
pub learning_rate: f64,
pub input_size: usize,
pub output_size: usize,
}let architecture = &[
(10, Activation::Relu), // Couche d'entrée: 10 neurones
(32, Activation::Relu), // Couche cachée 1: 32 neurones
(64, Activation::Relu), // Couche cachée 2: 64 neurones
(128, Activation::Relu), // Couche cachée 3: 128 neurones
(64, Activation::Relu), // Couche cachée 4: 64 neurones
(32, Activation::Relu), // Couche cachée 5: 32 neurones
(1, Activation::Linear), // Couche de sortie: 1 neurone
];#[derive(Debug, Clone)]
pub enum Activation {
Sigmoid,
Relu,
Tanh,
Linear,
}
impl Activation {
pub fn apply(&self, x: &Array2<f64>) -> Array2<f64> {
match self {
Activation::Sigmoid => x.mapv(|v| 1.0 / (1.0 + (-v).exp())),
Activation::Relu => x.mapv(|v| v.max(0.0)),
Activation::Tanh => x.mapv(|v| v.tanh()),
Activation::Linear => x.clone(),
}
}
}Sigmoïde:
σ(x) = 1 / (1 + e^(-x))
σ'(x) = σ(x) * (1 - σ(x))
ReLU (Rectified Linear Unit):
ReLU(x) = max(0, x)
ReLU'(x) = { 1 si x > 0, 0 sinon }
Tangente Hyperbolique:
tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
tanh'(x) = 1 - tanh²(x)
impl Layer {
pub fn forward(&self, input: &Array2<f64>) -> (Array2<f64>, Array2<f64>) {
// z = W · input + b
let z = self.weights.dot(input) + &self.biases;
// a = activation(z)
let a = self.activation.apply(&z);
(z, a)
}
}Pour une couche 𝑙 :
z^[l] = W^[l] · a^[l-1] + b^[l]
a^[l] = g^[l](z^[l])
Où :
W^[l]: matrice des poids de la couche 𝑙b^[l]: vecteur des biais de la couche 𝑙g^[l]: fonction d'activation de la couche 𝑙a^[l]: activation de la couche 𝑙
pub fn backward(&mut self, delta: &Array2<f64>, prev_activation: &Array2<f64>)
-> (Array2<f64>, Array2<f64>, Array2<f64>) {
let dz = delta * &self.activation.derivative(self.z.as_ref().unwrap());
// dW = dz · a_prev^T / m
let m = prev_activation.nrows() as f64;
let dw = dz.dot(&prev_activation.t()) / m;
// db = sum(dz) / m
let db = dz.sum_axis(ndarray::Axis(1)).insert_axis(ndarray::Axis(1)) / m;
// delta_prev = W^T · dz
let delta_prev = self.weights.t().dot(&dz);
(dw, db, delta_prev)
}Couche de sortie:
dZ^[L] = A^[L] - Y
dW^[L] = (1/m) * dZ^[L] · A^[L-1]^T
db^[L] = (1/m) * sum(dZ^[L])
Couches cachées:
dZ^[l] = W^[l+1]^T · dZ^[l+1] * g'^[l](Z^[l])
dW^[l] = (1/m) * dZ^[l] · A^[l-1]^T
db^[l] = (1/m) * sum(dZ^[l])
pub fn update_parameters(&mut self, dw: &Array2<f64>, db: &Array2<f64>, learning_rate: f64) {
self.weights = &self.weights - learning_rate * dw;
self.biases = &self.biases - learning_rate * db;
}Descente de gradient:
W^[l] = W^[l] - α * dW^[l]
b^[l] = b^[[l] - α * db^[l]
pub struct FinancialData {
pub encaissements: Vec<f64>,
pub decaissements: Vec<f64>,
pub besoins: Vec<f64>,
pub dates: Vec<String>,
}pub struct TimeSeriesPreparer {
pub window_size: usize,
}
impl TimeSeriesPreparer {
pub fn prepare_training_data(&self, data: &FinancialData) -> (Array2<f64>, Array2<f64>) {
let n_samples = data.encaissements.len() - self.window_size;
let mut features = Vec::new();
let mut targets = Vec::new();
for i in 0..n_samples {
// Fenêtre de 5 jours avec encaissements et decaissements
for j in 0..self.window_size {
features.push(data.encaissements[i + j]);
features.push(data.decaissements[i + j]);
}
targets.push(data.besoins[i + self.window_size]);
}
// 10 entrées (5 jours × 2 variables)
let x = Array2::from_shape_vec((self.window_size * 2, n_samples), features).unwrap();
let y = Array2::from_shape_vec((1, n_samples), targets).unwrap();
(x, y)
}
}fn normalize(data: &[f64]) -> (Vec<f64>, (f64, f64)) {
let min = data.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let normalized: Vec<f64> = data.iter()
.map(|&x| (x - min) / (max - min))
.collect();
(normalized, (min, max))
}Formule de normalisation Min-Max:
x_normalized = (x - min) / (max - min)
pub fn train(&mut self, x_train: &Array2<f64>, y_train: &Array2<f64>,
x_val: Option<&Array2<f64>>, y_val: Option<&Array2<f64>>,
epochs: usize, l2_lambda: f64) -> (Vec<f64>, Vec<f64>) {
let mut train_losses = Vec::new();
let mut val_losses = Vec::new();
for epoch in 0..epochs {
let train_loss = self.train_epoch(x_train, y_train, l2_lambda);
train_losses.push(train_loss);
if let (Some(x_val), Some(y_val)) = (x_val, y_val) {
let val_output = self.forward(x_val);
let val_loss = self.compute_loss(&val_output, y_val);
val_losses.push(val_loss);
}
}
(train_losses, val_losses)
}pub fn compute_loss(&self, y_pred: &Array2<f64>, y_true: &Array2<f64>) -> f64 {
let diff = y_pred - y_true;
(&diff * &diff).mean().unwrap()
}Erreur Quadratique Moyenne (MSE) avec régularisation L2:
J(W,b) = (1/2m) * Σ(y_pred - y_true)² + (λ/2m) * Σ||W||²
pub fn evaluate(&self, x_test: &Array2<f64>, y_test: &Array2<f64>) -> f64 {
let predictions = self.predict_batch(x_test);
let predicted_classes = predictions.mapv(|x| if x > 0.5 { 1.0 } else { 0.0 });
let correct = predicted_classes.iter()
.zip(y_test.iter())
.filter(|(pred, true_val)| (**pred - **true_val).abs() < 0.5)
.count();
correct as f64 / y_test.len() as f64
}pub fn predict_single(&self, input: &[f64]) -> Vec<f64> {
let input_array = Array2::from_shape_vec((self.input_size, 1), input.to_vec()).unwrap();
let output = self.forward(&input_array);
output.iter().cloned().collect()
}// Pas de pointeurs nuls, pas de dangling pointers
// Gestion automatique de la mémoire sans GC
let weights = Array2::from_shape_fn((output_size, input_size), |_| {
rng.gen::<f64>() * std_dev
});- Compilation en code machine natif
- Pas de runtime virtuel
- Optimisations agressives du compilateur
// Le système de ownership empêche les data races
// Possibilité d'implémenter facilement du parallélisme// Vérifications à la compilation
// Pas d'erreurs de type à l'exécution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Activation {
Sigmoid,
Relu,
Tanh,
Linear,
}[dependencies]
ndarray = "0.16" # Algèbre linéaire performante
rand = "0.8" # Génération de nombres aléatoires
serde = "1.0" # Sérialisation/désérialisation
csv = "1.3" # Manipulation de fichiers CSV🧠 Architecture: 10 → 32 → 64 → 128 → 64 → 32 → 1
📊 Données: 757 lignes (600 entraînement, 157 test)
📈 Loss finale: 0.016789
🎯 Précision: 85.23% sur entraînement, 82.45% sur test
💰 Prédiction: -285,423,189.00 XOF pour le prochain jour
- Capacité d'abstraction : 7 couches pour capturer des patterns complexes
- Généralisation : Régularisation L2 pour éviter l'overfitting
- Extensibilité : Architecture modulable facilement
- Performance : Prédictions en temps réel
Ce projet démontre la puissance de Rust pour l'implémentation de réseaux de neurones profonds appliqués à des problèmes financiers complexes, combinant performance, sécurité et maintenabilité.