Wave 10 Summary: - A1-A4: Architecture upgrades (4x network, LeakyReLU, Xavier init, diagnostics) - A5-A6: Integration testing and production validation - A7: Research hyperopt vs manual tuning (manual recommended) - A8-A12: HOLD penalty tuning and critical bug fixes Architecture Changes: - Network expansion: [128,64,32] → [256,128,64] (2.5x parameters) - LeakyReLU activation (alpha=0.01) to prevent dead neurons - Xavier/Glorot initialization for better gradient flow - Real-time diagnostic monitoring (Q-values, dead neurons, gradients) Critical Bugs Fixed: - Bug #1: HOLD penalty not wired to reward calculation - Bug #2: Zero price error in calculate_hold_reward (velocity-based fix) - Huber loss default enabled (Wave 9) - Shape mismatch fix (Wave 8) Test Results: - Integration tests: 149/152 passing (98%) - New tests: 40+ tests added across 15 files - Xavier init: 5/5 tests passing - HOLD penalty wiring: 4/4 tests passing - Zero price fix: 4/4 tests passing Known Issues: - HOLD bias persists at ~100% despite penalties - Gradient collapse: 217 instances per training run (norm=0.0) - Reversed penalty effect: Higher penalties → worse Q-spread - Root cause: Gradient clipping bottleneck (max_norm=10.0 vs penalty signal) Phase 1 Trials (all completed without crashes): - Penalty 0.5: Q-spread 250 pts, HOLD 100% - Penalty 1.0: Q-spread 251 pts, HOLD 100% - Penalty 2.0: Q-spread 255 pts, HOLD 100% (+ Q-value explosion) Next Steps: Architectural investigation via parallel agent debugging 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
Wave 9-A4: Huber Loss Production Validation Report
Date: 2025-11-05 Agent: Wave 9-A4 Objective: Validate that Huber loss default reduces action bias in DQN training
Executive Summary
RESULT: ❌ HUBER LOSS DOES NOT FIX ACTION BIAS
Despite Huber loss being correctly implemented and enabled by default, the DQN model still exhibits:
- 96.7% BUY bias (134,596/139,202 training samples)
- Gradient collapse from 35,000 → 0.7 at step 210
- Q-value collapse to 0.0000 for all actions
- Negative loss values starting at step 1010
CRITICAL FINDING: The action bias problem is NOT caused by the loss function. Root cause analysis points to a deeper architectural issue in the Q-network or training dynamics.
Training Configuration
Command
cargo run --release --package ml --example train_dqn --features cuda -- \
--epochs 10 \
--parquet-file test_data/ES_FUT_180d.parquet
Hyperparameters
- Learning rate: 0.0001
- Batch size: 32
- Gamma: 0.9626
- Epsilon: 0.3 → 0.05 (decay 0.995)
- Buffer size: 104,346
- Loss function: Huber loss (delta=1.0) ✅ CONFIRMED
- Gradient clipping: max_norm=10.0
Code Verification
Huber loss implementation confirmed at:
- Config:
/home/jgrusewski/Work/foxhunt/ml/src/dqn/dqn.rs:91(use_huber_loss: true) - Usage:
/home/jgrusewski/Work/foxhunt/ml/examples/train_dqn.rs:285(use_huber_loss: true) - Implementation:
/home/jgrusewski/Work/foxhunt/ml/src/dqn/dqn.rs:539-567
Training Results
Final Metrics (Epoch 10)
| Metric | Value | Target | Status |
|---|---|---|---|
| Training Steps | 43,500 | >40,000 | ✅ PASS |
| Training Loss | 0.006215 | Decreasing | ✅ PASS |
| Validation Loss | 0.000290 | Low | ✅ PASS |
| Average Q-value | 0.0156 | >0.1 | ❌ FAIL (collapsed) |
| Gradient Norm | 0.0158 | >0.1 | ❌ FAIL (collapsed) |
| Training Duration | 100.88s | N/A | ✅ PASS |
Action Distribution (Epoch 10)
BUY: 96.7% (134,596 / 139,202) ❌ EXTREME BIAS
SELL: 1.6% (2,255 / 139,202) ❌ SUPPRESSED
HOLD: 1.7% (2,351 / 139,202) ❌ SUPPRESSED
Comparison to Wave 8 (without Huber loss fix):
- Wave 8: 96.6% SELL bias
- Wave 9: 96.7% BUY bias
- Delta: +0.1% bias, action flipped from SELL to BUY
CONCLUSION: Huber loss did NOT reduce action bias. Bias simply shifted from SELL to BUY.
Q-Value Analysis
All three actions collapsed to zero by epoch 10:
BUY: 0.0000 ❌ COLLAPSED
SELL: 0.0000 ❌ COLLAPSED
HOLD: 0.0000 ❌ COLLAPSED
Gradient Collapse Timeline
Healthy Phase (Steps 1-200)
Step 10: grad=35,144 ✅ Strong gradients
Step 50: grad=30,466 ✅ Stable
Step 100: grad=32,672 ✅ Healthy
Step 150: grad=41,079 ✅ Strong
Step 200: grad=36,341 ✅ Last healthy step
Collapse Event (Step 210)
Step 200: grad=36,341 ✅ Healthy
Step 210: grad=0.7967 ❌ COLLAPSED (99.998% drop)
Step 220: grad=0.7992 ❌ Collapsed
Step 230: grad=0.6946 ❌ Collapsed
CRITICAL: Gradient collapsed by 4,561x in a single step (36,341 → 0.80).
Degenerate Phase (Steps 210-4350)
Step 300: grad=0.8280 ❌ Collapsed
Step 1000: grad=0.7411 ❌ Collapsed
Step 2000: grad=0.0035 ❌ Near-zero
Step 3000: grad=0.0064 ❌ Near-zero
Step 4350: grad=0.0158 ❌ Near-zero (final)
Pattern: After step 210, gradients never recover. Training continues in a degenerate state for 4,140 steps (95% of total training).
Loss Trajectory
Positive Loss Phase (Steps 1-1000)
Step 10: loss=4,175 ✅ High but decreasing
Step 50: loss=3,380 ✅ Decreasing
Step 100: loss=3,473 ✅ Stable
Step 500: loss=374 ✅ Declining
Step 1000: loss=503 ✅ Last positive
Negative Loss Phase (Steps 1010-4350)
Step 1010: loss=-0.0822 ❌ NEGATIVE (impossible for MSE/Huber)
Step 1500: loss=-0.0817 ❌ Negative
Step 2000: loss=-0.0825 ❌ Negative
Step 3000: loss=-0.0826 ❌ Negative
Step 4000: loss=-0.0825 ❌ Negative
Step 4350: loss=-0.0825 ❌ Negative (final)
CRITICAL: Loss became negative at step 1010 and stayed negative for 3,340 steps (77% of training). This is mathematically impossible for Huber loss or MSE loss, indicating a severe numerical instability.
Root Cause Analysis
What Huber Loss SHOULD Fix
- Outlier robustness: Reduces impact of large TD errors
- Gradient stability: Prevents exploding gradients from extreme Q-values
- Training smoothness: More stable learning with noisy rewards
What Huber Loss CANNOT Fix
- Architectural collapse: Q-network producing identical outputs
- Dead ReLU units: Neurons stuck at zero activation
- Vanishing gradients: Gradients too small to propagate
- Numerical instability: Loss becoming negative (non-physical)
Evidence of Architectural Problem
-
Gradient collapse precedes Q-value collapse:
- Step 200: grad=36,341, Q-values likely healthy
- Step 210: grad=0.80 (collapsed), Q-values begin converging
- Step 1010: loss becomes negative, Q-values fully collapsed
-
Loss function working correctly:
- Huber loss implementation verified at lines 539-567
- Initial training (steps 1-200) shows healthy gradient flow
- Collapse occurs DURING training, not at initialization
-
Action bias persists across loss functions:
- Wave 8 (MSE loss): 96.6% SELL bias
- Wave 9 (Huber loss): 96.7% BUY bias
- No meaningful improvement in diversity
Likely Root Causes
-
Network architecture issues:
- Insufficient hidden layer capacity (64, 32 neurons)
- Poor initialization causing early neuron death
- ReLU activation causing dead units after step 210
-
Learning dynamics:
- Learning rate too high (0.0001) for this architecture
- Target network updates (freq=1000) too infrequent
- Epsilon decay (0.995) too fast, insufficient exploration
-
Numerical instability:
- Negative loss indicates severe numerical issues
- Q-values collapsing to zero suggests vanishing gradients
- Gradient clipping (max_norm=10.0) may be triggering prematurely
Recommendations
Priority 1: Architectural Changes (IMMEDIATE)
-
Increase network capacity:
hidden_dims: vec![256, 128, 64] // vs current [64, 32]- More neurons to prevent early collapse
- Deeper network for richer representations
-
Change activation function:
use LeakyReLU(alpha=0.01) instead of ReLU- Prevents dead neurons
- Maintains gradient flow for negative inputs
-
Improve initialization:
use Xavier/Glorot initialization for all layers- Better initial gradient magnitudes
- Reduces risk of early collapse
Priority 2: Hyperparameter Tuning
-
Lower learning rate:
--learning-rate 0.00001 # 10x lower- Prevents overshooting optimal Q-values
- More stable convergence
-
Increase target network update frequency:
--target-update-freq 100 # vs current 1000- Reduces target staleness
- Better temporal difference estimates
-
Slow epsilon decay:
--epsilon-decay 0.999 # vs current 0.995- More exploration throughout training
- Better action space coverage
Priority 3: Training Dynamics
-
Add learning rate scheduling:
LR schedule: 0.0001 → 0.00001 over 50 epochs- Start with higher LR for fast learning
- Reduce LR as Q-values stabilize
-
Implement gradient clipping by value:
clip_grad_value: 1.0 // vs current max_norm=10.0- Prevents extreme gradient spikes
- More stable training
-
Add batch normalization:
BatchNorm after each hidden layer- Stabilizes internal activations
- Reduces covariate shift
Priority 4: Diagnostic Tools
-
Add Q-value monitoring:
- Log Q-values for each action every 10 steps
- Detect collapse early (before gradients collapse)
-
Add activation monitoring:
- Log % of dead ReLU units every 100 steps
- Detect neuron death patterns
-
Add loss breakdown:
- Log separate losses for BUY/SELL/HOLD
- Identify which actions are collapsing first
Conclusion
Huber loss is correctly implemented and enabled by default, but it does NOT solve the action bias problem. The DQN model exhibits:
- Gradient collapse at step 210 (99.998% drop)
- Q-value collapse to 0.0000 for all actions
- Negative loss values (mathematically impossible)
- 96.7% action bias (no improvement vs Wave 8)
CRITICAL FINDING: The problem is architectural, not algorithmic. The Q-network is collapsing due to:
- Insufficient network capacity (64, 32 neurons)
- Dead ReLU units after step 210
- Numerical instability causing negative loss
RECOMMENDATION: Implement Priority 1 architectural changes immediately. Huber loss fix should be abandoned as it does not address the root cause.
Appendix: Gradient Statistics
Gradient Distribution
Steps 1-200: mean=36,820 std=6,142 ✅ Healthy
Steps 201-500: mean=0.798 std=0.078 ❌ Collapsed
Steps 501-1000: mean=0.792 std=0.073 ❌ Collapsed
Steps 1001-2000: mean=0.007 std=0.004 ❌ Near-zero
Steps 2001-4350: mean=0.009 std=0.006 ❌ Near-zero
Collapse Severity
- Magnitude drop: 36,341 → 0.80 (4,561x reduction)
- Recovery time: Never recovered (4,140 steps in collapsed state)
- Final gradient: 0.0158 (99.96% below healthy baseline)
Loss Statistics
Steps 1-500: mean=2,453 std=1,842 ✅ Positive
Steps 501-1000: mean=442 std=189 ✅ Positive
Steps 1001-4350: mean=-0.0816 std=0.0021 ❌ NEGATIVE (impossible)
CRITICAL: Negative loss values indicate severe numerical instability, likely due to:
- Q-values collapsing to exactly zero
- Target Q-values also zero
- TD error becoming zero
- Loss calculation producing small negative artifacts due to floating-point rounding
This is a non-physical result that suggests the training loop has entered a degenerate state where the model is no longer learning meaningful Q-values.
Files Referenced
Training Script
/home/jgrusewski/Work/foxhunt/ml/examples/train_dqn.rs:285(use_huber_loss: true)
DQN Implementation
/home/jgrusewski/Work/foxhunt/ml/src/dqn/dqn.rs:91(default config)/home/jgrusewski/Work/foxhunt/ml/src/dqn/dqn.rs:539-567(Huber loss implementation)
Training Log
/tmp/wave9_huber_production.log(10-epoch training, 100.88s)
Generated: 2025-11-05 21:00:28 UTC Training Duration: 100.88 seconds (10 epochs) Agent: Wave 9-A4 Production Validation