From 37cb034eedcef6476f0ae11830cfe9a16ed382b4 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sun, 29 Mar 2026 23:43:33 +0200 Subject: [PATCH] feat(generalization): periodic shrink-and-perturb every 20 epochs Kills memorized weights by blending 85% current + 15% Xavier random. Surviving weights encode robust features. Memorized artifacts die. Configurable: shrink_perturb_interval, alpha, sigma. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/ml/src/trainers/dqn/config.rs | 11 +++++++++++ crates/ml/src/trainers/dqn/trainer/training_loop.rs | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/ml/src/trainers/dqn/config.rs b/crates/ml/src/trainers/dqn/config.rs index d938b9503..b9e5ae9e5 100644 --- a/crates/ml/src/trainers/dqn/config.rs +++ b/crates/ml/src/trainers/dqn/config.rs @@ -931,6 +931,13 @@ pub struct DQNHyperparameters { /// Prevents memorization of fixed simulation parameters. pub enable_domain_randomization: bool, + /// Periodic shrink-and-perturb interval (0=disabled, 20=every 20 epochs) + pub shrink_perturb_interval: usize, + /// Shrink-and-perturb alpha (0.85 = keep 85%, reinit 15%) + pub shrink_perturb_alpha: f64, + /// Shrink-and-perturb noise scale (Xavier) + pub shrink_perturb_sigma: f64, + // Wave 16 Portfolio Features /// Enable action masking (filters invalid actions based on position limits) pub enable_action_masking: bool, @@ -1413,6 +1420,10 @@ impl DQNHyperparameters { // Generalization: domain randomization (per-epoch jitter on sim params) enable_domain_randomization: true, // Default: enabled (prevents memorization of fixed sim params) + // Generalization: periodic shrink-and-perturb (kills memorized weights) + shrink_perturb_interval: 20, // Every 20 epochs (0=disabled) + shrink_perturb_alpha: 0.85, // Keep 85% of weights, reinit 15% + shrink_perturb_sigma: 0.01, // Xavier noise scale // Wave 16 Portfolio Features (default: ALL ENABLED) enable_action_masking: true, // Default: action masking enabled diff --git a/crates/ml/src/trainers/dqn/trainer/training_loop.rs b/crates/ml/src/trainers/dqn/trainer/training_loop.rs index 99846daec..5b9c72403 100644 --- a/crates/ml/src/trainers/dqn/trainer/training_loop.rs +++ b/crates/ml/src/trainers/dqn/trainer/training_loop.rs @@ -262,6 +262,19 @@ impl DQNTrainer { )); } + // ── Periodic shrink-and-perturb: kill memorized weights ── + let sp_interval = self.hyperparams.shrink_perturb_interval; + if sp_interval > 0 && epoch > 0 && epoch % sp_interval == 0 { + if let Some(ref mut fused) = self.fused_ctx { + let alpha = self.hyperparams.shrink_perturb_alpha as f32; + let sigma = self.hyperparams.shrink_perturb_sigma as f32; + match fused.shrink_and_perturb(alpha, sigma) { + Ok(()) => info!(epoch, alpha, sigma, "Periodic shrink-and-perturb applied"), + Err(e) => warn!(epoch, "Shrink-and-perturb failed (non-fatal): {e}"), + } + } + } + // ── Phase 3: Batched training from replay buffer ── let phase3_start = std::time::Instant::now(); let train_step_count = self.run_training_steps(training_data).await?;