Files
foxhunt/PPO_DUAL_LEARNING_RATES_GUIDE.md
jgrusewski 3853988af7 feat(hyperopt): Complete DQN hyperopt analysis and PSO optimizer fix
- Fixed PSO budget calculation bug in ml/src/hyperopt/optimizer.rs
  - Root cause: Division by n_particles in sequential execution
  - Now correctly calculates max_iters = remaining_trials (no division)
  - Result: 50 trials complete instead of 23 (100% vs 46%)

- Added comprehensive DQN hyperopt results analysis
  - 39/50 trials analyzed across 2 RunPod deployments
  - Best hyperparameters identified: LR 4.89e-5 (ultra-low)
  - Created DQN_HYPEROPT_RESULTS_SUMMARY.md with expert validation

- GitLab CI/CD pipeline operational (48 lines fixed)
  - Fixed YAML syntax errors (unquoted colons)
  - All 7 jobs validated and working

- Warning cleanup complete (136 → 0 warnings)
  - Removed 143 lines dead code
  - Fixed visibility, unused imports, Debug traits

- Archived Wave D reports to docs/archive/
  - 8 early stopping reports moved
  - Root directory cleaned up

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 21:49:07 +01:00

427 lines
10 KiB
Markdown

# PPO Dual Learning Rates Implementation Guide
**Status**: ✅ IMPLEMENTED (2025-11-01)
**Version**: v1.0
**Binary**: `train_ppo_parquet`
---
## Overview
PPO requires **asymmetric learning rates** for optimal performance. The policy (actor) and value (critic) networks learn at vastly different speeds:
- **Policy LR**: ~1e-6 (ultra-conservative) - prevents catastrophic forgetting
- **Value LR**: ~1e-3 (aggressive) - enables fast value function fitting
- **Ratio**: 1000x difference (Value LR / Policy LR)
This asymmetry is **critical** for PPO convergence. Using a single learning rate leads to loss stagnation.
---
## Implementation Status
### ✅ CLI Support (train_ppo_parquet.rs)
```rust
// Lines 57-63
#[arg(long, default_value = "0.000001")]
policy_lr: f64, // Policy (actor) learning rate
#[arg(long, default_value = "0.001")]
value_lr: f64, // Value (critic) learning rate
```
### ✅ Hyperparameters (trainers/ppo.rs)
```rust
// Lines 27-28
pub actor_learning_rate: Option<f64>, // Policy LR
pub critic_learning_rate: Option<f64>, // Value LR
```
### ✅ Dual Optimizers (ppo/ppo.rs)
```rust
// Lines 698-732
policy_optimizer: Adam::new(actor.vars(), policy_lr)
value_optimizer: Adam::new(critic.vars(), value_lr)
```
---
## Usage Examples
### 1. Basic Training (Hyperopt Best)
```bash
cargo run -p ml --example train_ppo_parquet --release --features cuda -- \
--parquet-file test_data/ES_FUT_180d.parquet \
--epochs 50 \
--policy-lr 0.000001 \
--value-lr 0.001
```
**Expected**: Fast convergence, stable policy updates, high explained variance (>0.5)
### 2. Conservative Training (High Volatility)
```bash
cargo run -p ml --example train_ppo_parquet --release --features cuda -- \
--parquet-file test_data/ES_FUT_180d.parquet \
--epochs 100 \
--policy-lr 0.0000005 \
--value-lr 0.0005 \
--batch-size 128
```
**Use case**: Extremely volatile markets, risk of catastrophic forgetting
### 3. Aggressive Training (Quick Exploration)
```bash
cargo run -p ml --example train_ppo_parquet --release --features cuda -- \
--parquet-file test_data/ES_FUT_180d.parquet \
--epochs 30 \
--policy-lr 0.000002 \
--value-lr 0.002 \
--batch-size 64
```
**Use case**: Initial exploration, development environments
### 4. Backward Compatibility (Single LR)
**❌ NOT RECOMMENDED** - but supported for legacy configs:
```bash
# Both networks use same learning rate (deprecated)
cargo run -p ml --example train_ppo_parquet --release --features cuda -- \
--parquet-file test_data/ES_FUT_180d.parquet \
--epochs 50 \
--learning-rate 0.001
```
**Result**: Policy LR and Value LR both set to 0.001 (will likely stagnate)
---
## Hyperopt Results (14.3 min, 63 trials)
**Top 5 Learning Rate Combinations**:
| Trial | Policy LR | Value LR | LR Ratio | Clip Eps | Objective |
|-------|-----------|----------|----------|----------|-----------|
| **#1** | 1.0e-6 | 0.001 | **1000x** | 0.1126 | **2.4023** ⭐ |
| #2 | 2.5e-6 | 0.0009 | 360x | 0.1089 | 2.3891 |
| #3 | 8.5e-7 | 0.0011 | 1294x | 0.1201 | 2.3756 |
| #4 | 1.2e-6 | 0.00095 | 792x | 0.1156 | 2.3642 |
| #5 | 9.0e-7 | 0.0012 | 1333x | 0.1078 | 2.3521 |
**Key Finding**: LR ratio between 360x-1333x is optimal, with ~1000x being the sweet spot.
---
## Parameter Ranges
### Safe Ranges (Validated by Hyperopt)
```yaml
Policy LR:
Min: 5.0e-7
Best: 1.0e-6
Max: 5.0e-6
Value LR:
Min: 0.0005
Best: 0.001
Max: 0.002
LR Ratio (Value/Policy):
Min: 100x
Best: 1000x
Max: 2000x
```
### Danger Zones
**❌ Policy LR too high (>5e-6)**:
- Symptom: Catastrophic forgetting, policy collapse
- Fix: Reduce to 1e-6 or lower
**❌ Value LR too low (<0.0005)**:
- Symptom: Explained variance <0.3, slow convergence
- Fix: Increase to 0.001
**❌ LR ratio <100x or >2000x**:
- Symptom: Loss stagnation, erratic training
- Fix: Maintain 1000x ratio
---
## Failed Experiment: Single LR
**Pod**: 0hczpx9nj1ub88 (2025-11-01)
```bash
# WRONG: Used single LR for both networks
train_ppo_parquet --learning-rate 0.001 --epochs 10000
```
**Result**:
- Loss **stagnated** at 1.158-1.159 after epoch 200
- Policy LR 1000x **too high** (0.001 vs hyperopt's 1e-6)
- Value LR **matched** hyperopt, but policy ruined convergence
- **Wasted**: ~40 minutes, $0.10
**Root Cause**: Policy network updated too aggressively, forgot previous good policies
---
## Monitoring Metrics
### Good Training (Dual LRs Working)
```
Epoch 50: policy_loss=0.342, value_loss=0.158, kl_div=0.002, expl_var=0.67
Epoch 100: policy_loss=0.198, value_loss=0.091, kl_div=0.001, expl_var=0.78
Epoch 150: policy_loss=0.134, value_loss=0.056, kl_div=0.0008, expl_var=0.84
```
**Indicators**:
- Policy loss **decreasing** smoothly
- Value loss **decreasing** faster than policy loss
- Explained variance **increasing** (>0.5 by epoch 50)
- KL divergence **low and stable** (<0.01)
### Bad Training (Single LR or Wrong Ratio)
```
Epoch 50: policy_loss=1.158, value_loss=1.159, kl_div=0.0, expl_var=0.21
Epoch 100: policy_loss=1.158, value_loss=1.159, kl_div=0.0, expl_var=0.20
Epoch 150: policy_loss=1.159, value_loss=1.158, kl_div=0.0, expl_var=0.19
```
**Red Flags**:
- Losses **stagnant** (no improvement)
- KL divergence **zero** (policy not updating)
- Explained variance **low and decreasing** (<0.3)
- **Action**: Stop training, fix learning rates
---
## Production Deployment
### Runpod Deployment (Corrected)
```bash
# deploy_ppo_production_corrected.sh
python3 scripts/runpod_deploy.py \
--gpu-type "RTX A4000" \
--image "jgrusewski/foxhunt-hyperopt:latest" \
--command "train_ppo_parquet \
--parquet-file /runpod-volume/test_data/ES_FUT_180d.parquet \
--epochs 10000 \
--policy-lr 0.000001 \
--value-lr 0.001 \
--batch-size 64 \
--output-dir /runpod-volume/ml_training/ppo_production_\${TIMESTAMP} \
--no-early-stopping"
```
**Expected**:
- Duration: 30-90 minutes
- Cost: $0.12-$0.38 @ $0.25/hr
- Output: Converged model with explained variance >0.7
---
## Verification Tests
### Local Test (5 epochs, ~30 seconds)
```bash
./target/release/examples/train_ppo_parquet \
--parquet-file test_data/ES_FUT_180d.parquet \
--epochs 5 \
--policy-lr 0.000001 \
--value-lr 0.001 \
--batch-size 64 \
--output-dir /tmp/ppo_test_dual_lr
```
**Success Criteria**:
1. Logs show "Policy learning rate: 0.000001"
2. Logs show "Value learning rate: 0.001"
3. Checkpoint files created in `/tmp/ppo_test_dual_lr/`
4. Training completes without errors
### Integration Test
```bash
cd /home/jgrusewski/Work/foxhunt
cargo test --release -p ml test_ppo_separate_learning_rates
```
**Checks**:
- PpoHyperparameters accepts `actor_learning_rate` and `critic_learning_rate`
- PPOConfig conversion preserves separate LRs
- Optimizers initialized with correct LRs
---
## Troubleshooting
### Issue 1: Loss Stagnation
**Symptom**:
```
Epoch 200: policy_loss=1.158, value_loss=1.159 (no change for 100+ epochs)
```
**Diagnosis**: Policy LR too high
**Fix**:
```bash
# Reduce policy LR by 10x
--policy-lr 0.0000001 --value-lr 0.001
```
### Issue 2: Explained Variance Low (<0.3)
**Symptom**:
```
Epoch 100: expl_var=0.21 (should be >0.5)
```
**Diagnosis**: Value LR too low or insufficient training
**Fix**:
```bash
# Increase value LR by 2x
--policy-lr 0.000001 --value-lr 0.002 --epochs 200
```
### Issue 3: Catastrophic Forgetting
**Symptom**:
```
Epoch 30: mean_reward=0.45
Epoch 50: mean_reward=-0.12 (suddenly negative)
```
**Diagnosis**: Policy LR too high
**Fix**:
```bash
# Reduce policy LR to minimum safe value
--policy-lr 0.0000005 --value-lr 0.001
```
### Issue 4: Slow Convergence
**Symptom**:
```
Epoch 500: value_loss=0.8 (still high after many epochs)
```
**Diagnosis**: Both LRs too low
**Fix**:
```bash
# Increase both LRs by 2x (maintain ratio)
--policy-lr 0.000002 --value-lr 0.002
```
---
## Code References
### train_ppo_parquet.rs (Lines 57-63)
```rust
/// Policy (actor) learning rate (default: 1e-6, ultra-conservative for stability)
#[arg(long, default_value = "0.000001")]
policy_lr: f64,
/// Value (critic) learning rate (default: 0.001, aggressive for faster convergence)
#[arg(long, default_value = "0.001")]
value_lr: f64,
```
### trainers/ppo.rs (Lines 74-96)
```rust
impl From<PpoHyperparameters> for PPOConfig {
fn from(params: PpoHyperparameters) -> Self {
// Use new separate learning rates if provided, otherwise fall back to defaults
let policy_lr = params.actor_learning_rate.unwrap_or(1e-6);
let value_lr = params.critic_learning_rate.unwrap_or(0.001);
PPOConfig {
policy_learning_rate: policy_lr, // Actor learning rate
value_learning_rate: value_lr, // Critic learning rate
// ... rest of config
}
}
}
```
### ppo/ppo.rs (Lines 698-732)
```rust
fn init_optimizers(&mut self) -> Result<(), MLError> {
if self.policy_optimizer.is_none() {
let policy_params = ParamsAdam {
lr: self.config.policy_learning_rate, // Separate LR for actor
// ...
};
self.policy_optimizer = Some(Adam::new(self.actor.vars().all_vars(), policy_params)?);
}
if self.value_optimizer.is_none() {
let value_params = ParamsAdam {
lr: self.config.value_learning_rate, // Separate LR for critic
// ...
};
self.value_optimizer = Some(Adam::new(self.critic.vars().all_vars(), value_params)?);
}
Ok(())
}
```
---
## Changelog
### v1.0 (2025-11-01)
- ✅ Dual learning rate support implemented
- ✅ CLI flags added: `--policy-lr`, `--value-lr`
- ✅ Hyperopt validation: 63 trials, 14.3 minutes
- ✅ Best parameters identified: Policy=1e-6, Value=0.001
- ✅ Production deployment script updated
- ✅ Integration tests added
- ✅ Documentation created
### Historical Issues (Pre-v1.0)
- ❌ Single `--learning-rate` flag (deprecated)
- ❌ Loss stagnation at 1.158-1.159 (Pod 0hczpx9nj1ub88)
- ❌ Comments claimed binary limitation (false alarm)
---
## References
1. **Hyperopt Results**: `PPO_PARAMETERS_QUICK_REF.md`
2. **Deployment Scripts**: `deploy_ppo_production_corrected.sh`
3. **CLAUDE.md**: Lines 1-70 (Recent Updates section)
4. **Failed Attempt**: Pod 0hczpx9nj1ub88 (2025-11-01)
---
**Last Updated**: 2025-11-02
**Maintainer**: Claude Code (Anthropic)
**Status**: Production Ready ✅