Files
foxhunt/docs/WAVE26_P1.8_CURIOSITY_INTEGRATION_REPORT.md
jgrusewski 2df1ea92e1 feat(ml): WAVE 29 DQN Codebase Cleanup & Refactoring Campaign
BREAKING CHANGES:
- Removed orphaned dqn.rs monolithic trainer (4,975 lines)
- Removed orphaned dqn_ensemble.rs module (816 lines)
- Removed orphaned tft.rs and tft_complete_int8_integration_test.rs
- TFT trainer split into modular directory structure

DQN Module Refactoring:
- Split trainers/dqn.rs into modular structure (config.rs, statistics.rs, trainer.rs)
- Fixed hyperopt 39D search space (continuous params only)
- Boolean flags (use_dueling, use_double_dqn, use_per, use_noisy_nets) are now FIXED architectural decisions
- use_distributional defaults to false (Candle BUG #36 - scatter_add gradient issues)

Clean Module Structure:
- ml/src/trainers/dqn/ directory with proper mod.rs exports
- ml/src/trainers/tft/ directory with config.rs, types.rs, model.rs, trainer.rs, tests.rs
- All P0 features validated: TD-error clamping, batch diversity, LR scheduler, priority staleness

Documentation:
- Added comprehensive docs in docs/codebase-cleanup/
- ADR-001 for DQN refactoring decisions
- Rainbow DQN component matrix and quick reference guides

Build Status: Compiles with zero errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 23:46:13 +01:00

6.9 KiB
Raw Blame History

WAVE 26 P1.8: Curiosity-Driven Exploration Integration Report

Summary

Integrated existing curiosity module (ml/src/dqn/curiosity.rs) into DQN training pipeline to enhance exploration via novelty-based intrinsic rewards.

Changes Made

1. Added curiosity_weight hyperparameter (ml/src/trainers/dqn/config.rs)

  • Added pub curiosity_weight: f64 to DQNHyperparameters struct (line 475)
  • Default value: 0.0 (disabled, hyperopt will tune 0.0-0.5)
  • Range: 0.0 (pure extrinsic reward) to 0.5 (balanced extrinsic/intrinsic)

2. Exported curiosity module (ml/src/dqn/mod.rs)

  • Added pub mod curiosity; export (line 12)
  • Module was implemented but not previously exposed

3. Integrated curiosity into DQNTrainer (ml/src/trainers/dqn/trainer.rs)

  • Import: Added use crate::dqn::curiosity::CuriosityModule; (line 24)
  • Struct field: Added curiosity_module: Option<CuriosityModule> (line 438)
  • Initialization: Created curiosity module if curiosity_weight > 0.0 (lines 798-806)
    curiosity_module: if hyperparams.curiosity_weight > 0.0 {
        Some(CuriosityModule::new(
            device.clone(),
            0.001,  // Forward model learning rate
            2.0,    // Max curiosity reward (clip to prevent noise exploitation)
        )?)
    } else {
        None
    },
    
  • Reward calculation: Added intrinsic reward computation after risk-adjusted reward (lines 1718-1743)
    let total_reward = if let Some(ref mut curiosity) = self.curiosity_module {
        // Convert states to tensors [1, 54]
        let state_tensor = Tensor::from_vec(state.to_vec(), (1, state.len()), &self.device)?;
        let next_state_tensor = Tensor::from_vec(next_state.clone(), (1, next_state.len()), &self.device)?;
    
        // Calculate intrinsic curiosity reward (prediction error)
        let intrinsic_reward = curiosity.calculate_curiosity_reward(
            &state_tensor,
            action,
            &next_state_tensor,
        )?;
    
        // Combine extrinsic (trading) + intrinsic (novelty) rewards
        risk_adjusted_reward + self.hyperparams.curiosity_weight * intrinsic_reward
    } else {
        risk_adjusted_reward
    };
    

4. Added curiosity_weight to hyperopt search space (ml/src/hyperopt/adapters/dqn.rs)

  • Search space: Added (0.0, 0.5) range for parameter 28 (line 391)
  • DQNParams struct: Added pub curiosity_weight: f64 field (line 279)
  • Default value: 0.0 (disabled) (line 328)
  • Parameter extraction: Extract from x[28] and clamp to [0.0, 0.5] (line 451)
  • Parameter passing: Added to struct initialization (line 514), to_continuous() (line 557), and param_names() (line 596)
  • Hyperparameter mapping: Pass params.curiosity_weight to DQNHyperparameters (line 1976)
  • Dimension update: Changed expected dimension from 28 to 29 parameters (line 402)

How Curiosity Works

Forward Dynamics Model

  • Predicts next state from (state, action) pair
  • Architecture: [state_35 + action_3] → FC1(64) → LeakyReLU → FC2(32) → predicted_next_state_32
  • Uses Xavier initialization for stable gradients
  • Online learning: Trains on every transition via MSE loss

Intrinsic Reward Calculation

  1. Prediction: Forward model predicts next state from (state, action)
  2. Error: Compute MSE between predicted and actual next state
  3. Reward: Prediction error = novelty bonus (high error = novel transition)
  4. Clipping: Cap at max_reward=2.0 to prevent noise exploitation
  5. Training: Update forward model to improve predictions (reduces future rewards for familiar states)

Reward Composition

final_reward = extrinsic_reward (trading P&L) + curiosity_weight × intrinsic_reward (novelty)
  • curiosity_weight = 0.0: Pure extrinsic (standard DQN)
  • curiosity_weight = 0.25: Balanced exploration/exploitation
  • curiosity_weight = 0.5: Strong exploration focus

Hyperopt Integration

  • Search dimension: 28D → 29D (added curiosity_weight)
  • Range: [0.0, 0.5] (disabled → strong exploration)
  • Expected benefit: Improved exploration in sparse reward environments
  • Risk mitigation: Clipping prevents noise exploitation, online learning reduces rewards for familiar states

Testing Strategy

Unit Tests (existing in ml/src/dqn/curiosity.rs)

  1. test_forward_model_prediction: Forward model output shape [1, 32]
  2. test_forward_model_training: Loss decreases after 50 training steps
  3. test_curiosity_reward_novel_state: Novel states yield positive reward
  4. test_curiosity_reward_familiar_state: Familiar states yield low reward after training
  5. test_curiosity_reward_clipping: Rewards capped at max_reward
  6. test_action_one_hot_encoding: Different actions produce different predictions
  7. test_state_embedding_extraction: First 32 features used for prediction
  8. test_online_learning_convergence: Rewards decrease with online learning

Integration Tests (to verify)

  1. Test curiosity disabled (curiosity_weight = 0.0):

    • Module should be None
    • Reward should equal risk_adjusted_reward (no intrinsic component)
  2. Test curiosity enabled (curiosity_weight = 0.3):

    • Module should be Some(CuriosityModule)
    • Novel states should increase total reward
    • Familiar states should have minimal intrinsic reward after training
  3. Test tensor conversion:

    • State tensors [1, 54] created correctly
    • Next state tensors [1, 54] created correctly
    • Curiosity calculation succeeds

Expected Benefits

  1. Enhanced Exploration: Novelty bonus encourages visiting under-explored states
  2. Sparse Reward Mitigation: Intrinsic rewards provide learning signal even when extrinsic rewards are sparse
  3. Adaptive Exploration: Online learning reduces curiosity for familiar states, naturally transitioning to exploitation
  4. Hyperopt Tunable: curiosity_weight in search space allows automatic balancing of exploration/exploitation

Performance Considerations

  • Computational Cost: Forward model adds ~5% overhead (2-layer network inference + gradient update per transition)
  • Memory: Forward model parameters: ~35×64 + 64×32 ≈ 4.3K parameters (negligible)
  • GPU: Tensor operations leverage existing GPU infrastructure (minimal impact)

Files Modified

  1. /home/jgrusewski/Work/foxhunt/ml/src/trainers/dqn/config.rs (+3 lines)
  2. /home/jgrusewski/Work/foxhunt/ml/src/dqn/mod.rs (+1 line)
  3. /home/jgrusewski/Work/foxhunt/ml/src/trainers/dqn/trainer.rs (+35 lines)
  4. /home/jgrusewski/Work/foxhunt/ml/src/hyperopt/adapters/dqn.rs (+12 lines)

Next Steps

  1. Build verification: cargo build --package ml
  2. Run tests: cargo test --package ml curiosity
  3. Hyperopt validation: Deploy with 29D search space
  4. Monitor metrics: Track intrinsic reward contribution during training

Status: Implementation Complete | Testing In Progress Wave: 26 P1.8 Integration Time: ~30 minutes Risk: Low (fallback to disabled via curiosity_weight=0.0)