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) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-29 23:43:33 +02:00
parent ae99567362
commit 37cb034eed
2 changed files with 24 additions and 0 deletions

View File

@@ -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

View File

@@ -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?;