perf(ci): enable Kaniko layer caching + Docker Hub auth on all builds
- Add --cache=true --cache-repo to all 12 Kaniko builds - Cache Docker layers in Scaleway CR (rg.fr-par.scw.cloud/foxhunt-ci/cache) - Add Docker Hub auth to devcontainer + infra-runner prepare jobs - First build populates cache; subsequent builds skip base image pulls Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ build-ci-builder:
|
||||
- /kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.ci-builder"
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${CI_BUILDER_IMAGE}"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -83,12 +84,16 @@ build-devcontainer:
|
||||
allow_failure: true
|
||||
before_script:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- |
|
||||
echo "{\"auths\":{\"rg.fr-par.scw.cloud\":{\"username\":\"nologin\",\"password\":\"${SCW_SECRET_KEY}\"}}}" > /kaniko/.docker/config.json
|
||||
- >-
|
||||
echo "{\"auths\":{
|
||||
\"rg.fr-par.scw.cloud\":{\"username\":\"nologin\",\"password\":\"${SCW_SECRET_KEY}\"},
|
||||
\"https://index.docker.io/v1/\":{\"username\":\"${DOCKERHUB_USERNAME}\",\"password\":\"${DOCKERHUB_TOKEN}\"}
|
||||
}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/.devcontainer/Dockerfile"
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "rg.fr-par.scw.cloud/foxhunt-ci/devcontainer:${CI_COMMIT_SHA}"
|
||||
--destination "rg.fr-par.scw.cloud/foxhunt-ci/devcontainer:latest"
|
||||
|
||||
@@ -112,12 +117,16 @@ build-infra-runner:
|
||||
allow_failure: true
|
||||
before_script:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- |
|
||||
echo "{\"auths\":{\"rg.fr-par.scw.cloud\":{\"username\":\"nologin\",\"password\":\"${SCW_SECRET_KEY}\"}}}" > /kaniko/.docker/config.json
|
||||
- >-
|
||||
echo "{\"auths\":{
|
||||
\"rg.fr-par.scw.cloud\":{\"username\":\"nologin\",\"password\":\"${SCW_SECRET_KEY}\"},
|
||||
\"https://index.docker.io/v1/\":{\"username\":\"${DOCKERHUB_USERNAME}\",\"password\":\"${DOCKERHUB_TOKEN}\"}
|
||||
}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.infra-runner"
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${INFRA_RUNNER_IMAGE}"
|
||||
|
||||
# Base template for Rust jobs — pre-baked CI builder from Scaleway CR
|
||||
@@ -251,6 +260,7 @@ build-trading-service:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/trading_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/trading_service:latest"
|
||||
|
||||
@@ -265,6 +275,7 @@ build-api-gateway:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/api_gateway:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/api_gateway:latest"
|
||||
|
||||
@@ -279,6 +290,7 @@ build-broker-gateway:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/broker_gateway_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/broker_gateway_service:latest"
|
||||
|
||||
@@ -293,6 +305,7 @@ build-ml-training:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/ml_training_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/ml_training_service:latest"
|
||||
|
||||
@@ -307,6 +320,7 @@ build-backtesting:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/backtesting_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/backtesting_service:latest"
|
||||
|
||||
@@ -321,6 +335,7 @@ build-trading-agent:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/trading_agent_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/trading_agent_service:latest"
|
||||
|
||||
@@ -335,6 +350,7 @@ build-data-acquisition:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/data_acquisition_service:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/data_acquisition_service:latest"
|
||||
|
||||
@@ -344,6 +360,7 @@ build-web-gateway:
|
||||
- /kaniko/executor
|
||||
--context "${CI_PROJECT_DIR}"
|
||||
--dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.web-gateway"
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/web-gateway:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/web-gateway:latest"
|
||||
|
||||
@@ -357,6 +374,7 @@ build-training:
|
||||
--build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY}
|
||||
--build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY}
|
||||
--build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT}
|
||||
--cache=true --cache-repo="${REGISTRY}/cache"
|
||||
--destination "${REGISTRY}/training:${CI_COMMIT_SHA}"
|
||||
--destination "${REGISTRY}/training:latest"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Hyperopt Runner for DQN/PPO on Real Databento Market Data
|
||||
//! Hyperopt RL Runner for DQN/PPO on Real Databento Market Data
|
||||
//!
|
||||
//! Runs hyperparameter optimization using Particle Swarm Optimization (PSO) for
|
||||
//! DQN, PPO, or both models on downloaded Databento futures data. This binary is
|
||||
@@ -47,7 +47,7 @@ use ml::hyperopt::ArgminOptimizer;
|
||||
|
||||
/// Hyperparameter optimization runner for DQN/PPO on Databento market data
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "hyperopt-baseline")]
|
||||
#[command(name = "hyperopt-baseline-rl")]
|
||||
#[command(about = "Run hyperparameter optimization for DQN/PPO on real Databento futures data")]
|
||||
struct Args {
|
||||
/// Model to optimize: "dqn", "ppo", or "both"
|
||||
294
crates/ml/examples/hyperopt_baseline_supervised.rs
Normal file
294
crates/ml/examples/hyperopt_baseline_supervised.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
//! Hyperopt Runner for Supervised Models on Parquet Data
|
||||
//!
|
||||
//! Runs hyperparameter optimization using Particle Swarm Optimization (PSO) for
|
||||
//! TFT, MAMBA-2, or both models. This binary is the supervised counterpart to
|
||||
//! `hyperopt_baseline_rl` (which handles DQN/PPO on DBN data).
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Run TFT hyperopt
|
||||
//! SQLX_OFFLINE=true cargo run -p ml --example hyperopt_baseline_supervised --release -- \
|
||||
//! --model tft --parquet-file data/ES_FUT_180d.parquet \
|
||||
//! --trials 20 --epochs 20
|
||||
//!
|
||||
//! # Run Mamba2 hyperopt
|
||||
//! SQLX_OFFLINE=true cargo run -p ml --example hyperopt_baseline_supervised --release -- \
|
||||
//! --model mamba2 --parquet-file data/ES_FUT_180d.parquet \
|
||||
//! --trials 20 --epochs 10
|
||||
//!
|
||||
//! # Run both models
|
||||
//! SQLX_OFFLINE=true cargo run -p ml --example hyperopt_baseline_supervised --release -- \
|
||||
//! --model both --parquet-file data/ES_FUT_180d.parquet
|
||||
//! ```
|
||||
//!
|
||||
//! ## Output
|
||||
//!
|
||||
//! Results are written as JSON to `--output` (default: `ml/trained_models/hyperopt_results.json`).
|
||||
|
||||
#![allow(unused_crate_dependencies)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use serde_json::Value;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use tracing::{error, info, warn, Level};
|
||||
|
||||
use ml::hyperopt::adapters::mamba2::Mamba2Trainer;
|
||||
use ml::hyperopt::adapters::tft::TFTTrainer;
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use ml::hyperopt::ArgminOptimizer;
|
||||
|
||||
/// Hyperparameter optimization runner for supervised models
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "hyperopt-baseline-supervised")]
|
||||
#[command(about = "Run hyperparameter optimization for supervised models (TFT, Mamba2)")]
|
||||
struct Args {
|
||||
/// Model to optimize: "tft", "mamba2", or "both"
|
||||
#[arg(long, default_value = "both")]
|
||||
model: String,
|
||||
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long)]
|
||||
parquet_file: String,
|
||||
|
||||
/// Number of PSO trials per model
|
||||
#[arg(long, default_value = "20")]
|
||||
trials: usize,
|
||||
|
||||
/// Number of initial LHS (Latin Hypercube Sampling) samples
|
||||
#[arg(long, default_value = "5")]
|
||||
n_initial: usize,
|
||||
|
||||
/// Training epochs per trial
|
||||
#[arg(long, default_value = "20")]
|
||||
epochs: usize,
|
||||
|
||||
/// Output path for JSON results
|
||||
#[arg(long, default_value = "ml/trained_models/hyperopt_results.json")]
|
||||
output: PathBuf,
|
||||
|
||||
/// Random seed for reproducibility
|
||||
#[arg(long, default_value = "42")]
|
||||
seed: u64,
|
||||
|
||||
/// Base directory for training run outputs (checkpoints, logs, metrics)
|
||||
#[arg(long, default_value = "/tmp/ml_training")]
|
||||
base_dir: String,
|
||||
|
||||
/// Early stopping patience (epochs without improvement)
|
||||
#[arg(long, default_value = "10")]
|
||||
early_stopping_patience: usize,
|
||||
}
|
||||
|
||||
/// Result entry for one model's hyperopt run
|
||||
fn build_model_result(
|
||||
best_objective: f64,
|
||||
best_params_json: Value,
|
||||
num_trials: usize,
|
||||
elapsed_secs: f64,
|
||||
) -> Value {
|
||||
serde_json::json!({
|
||||
"best_objective": best_objective,
|
||||
"best_params": best_params_json,
|
||||
"trials": num_trials,
|
||||
"elapsed_secs": elapsed_secs,
|
||||
})
|
||||
}
|
||||
|
||||
fn run_tft_hyperopt(args: &Args) -> Result<Value> {
|
||||
info!("========================================");
|
||||
info!(" TFT Hyperparameter Optimization");
|
||||
info!("========================================");
|
||||
|
||||
let run_id = generate_run_id("hyperopt-tft");
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "tft", &run_id);
|
||||
|
||||
info!("Run ID: {}", run_id);
|
||||
info!("Parquet file: {}", args.parquet_file);
|
||||
info!("Epochs per trial: {}", args.epochs);
|
||||
info!("Trials: {}", args.trials);
|
||||
|
||||
let trainer = TFTTrainer::new(&args.parquet_file, args.epochs)
|
||||
.context("Failed to create TFT trainer")?
|
||||
.with_early_stopping(args.early_stopping_patience)
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
let optimizer = ArgminOptimizer::builder()
|
||||
.max_trials(args.trials)
|
||||
.n_initial(args.n_initial)
|
||||
.seed(args.seed)
|
||||
.build();
|
||||
|
||||
let start = Instant::now();
|
||||
let result = optimizer
|
||||
.optimize(trainer)
|
||||
.context("TFT hyperopt optimization failed")?;
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
|
||||
info!("TFT hyperopt complete:");
|
||||
info!(" Best objective: {:.6}", result.best_objective);
|
||||
info!(" Total trials: {}", result.all_trials.len());
|
||||
info!(" Elapsed: {:.1}s", elapsed);
|
||||
|
||||
let best_params_json = serde_json::to_value(&result.best_params)
|
||||
.ok()
|
||||
.unwrap_or(Value::Null);
|
||||
|
||||
Ok(build_model_result(
|
||||
result.best_objective,
|
||||
best_params_json,
|
||||
result.all_trials.len(),
|
||||
elapsed,
|
||||
))
|
||||
}
|
||||
|
||||
fn run_mamba2_hyperopt(args: &Args) -> Result<Value> {
|
||||
info!("========================================");
|
||||
info!(" Mamba2 Hyperparameter Optimization");
|
||||
info!("========================================");
|
||||
|
||||
let run_id = generate_run_id("hyperopt-mamba2");
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "mamba2", &run_id);
|
||||
|
||||
info!("Run ID: {}", run_id);
|
||||
info!("Parquet file: {}", args.parquet_file);
|
||||
info!("Epochs per trial: {}", args.epochs);
|
||||
info!("Trials: {}", args.trials);
|
||||
|
||||
let trainer = Mamba2Trainer::new(&args.parquet_file, args.epochs)
|
||||
.context("Failed to create Mamba2 trainer")?
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
let optimizer = ArgminOptimizer::builder()
|
||||
.max_trials(args.trials)
|
||||
.n_initial(args.n_initial)
|
||||
.seed(args.seed)
|
||||
.build();
|
||||
|
||||
let start = Instant::now();
|
||||
let result = optimizer
|
||||
.optimize(trainer)
|
||||
.context("Mamba2 hyperopt optimization failed")?;
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
|
||||
info!("Mamba2 hyperopt complete:");
|
||||
info!(" Best objective: {:.6}", result.best_objective);
|
||||
info!(" Total trials: {}", result.all_trials.len());
|
||||
info!(" Elapsed: {:.1}s", elapsed);
|
||||
|
||||
let best_params_json = serde_json::to_value(&result.best_params)
|
||||
.ok()
|
||||
.unwrap_or(Value::Null);
|
||||
|
||||
Ok(build_model_result(
|
||||
result.best_objective,
|
||||
best_params_json,
|
||||
result.all_trials.len(),
|
||||
elapsed,
|
||||
))
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
info!("========================================");
|
||||
info!(" Hyperopt Baseline Supervised Runner");
|
||||
info!("========================================");
|
||||
info!("Model: {}", args.model);
|
||||
info!("Parquet file: {}", args.parquet_file);
|
||||
info!("Trials: {}", args.trials);
|
||||
info!("Initial LHS samples: {}", args.n_initial);
|
||||
info!("Epochs per trial: {}", args.epochs);
|
||||
info!("Output: {}", args.output.display());
|
||||
info!("Seed: {}", args.seed);
|
||||
|
||||
// Validate parquet file exists
|
||||
if !std::path::Path::new(&args.parquet_file).exists() {
|
||||
anyhow::bail!(
|
||||
"Parquet file not found: {}. Provide a valid path via --parquet-file.",
|
||||
args.parquet_file
|
||||
);
|
||||
}
|
||||
|
||||
// Validate model selection
|
||||
let run_tft = args.model == "tft" || args.model == "both";
|
||||
let run_mamba2 = args.model == "mamba2" || args.model == "both";
|
||||
|
||||
if !run_tft && !run_mamba2 {
|
||||
anyhow::bail!(
|
||||
"Invalid --model value '{}'. Must be 'tft', 'mamba2', or 'both'.",
|
||||
args.model
|
||||
);
|
||||
}
|
||||
|
||||
// Verify trials > n_initial
|
||||
if args.trials <= args.n_initial {
|
||||
anyhow::bail!(
|
||||
"trials ({}) must be greater than n_initial ({})",
|
||||
args.trials,
|
||||
args.n_initial
|
||||
);
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
if let Some(parent) = args.output.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| format!("Failed to create output directory: {}", parent.display()))?;
|
||||
}
|
||||
|
||||
let mut results = serde_json::Map::new();
|
||||
|
||||
if run_tft {
|
||||
match run_tft_hyperopt(&args) {
|
||||
Ok(tft_result) => {
|
||||
results.insert("tft".to_string(), tft_result);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("TFT hyperopt failed: {:#}", e);
|
||||
warn!("Continuing with remaining models...");
|
||||
results.insert(
|
||||
"tft".to_string(),
|
||||
serde_json::json!({ "error": format!("{:#}", e) }),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if run_mamba2 {
|
||||
match run_mamba2_hyperopt(&args) {
|
||||
Ok(mamba2_result) => {
|
||||
results.insert("mamba2".to_string(), mamba2_result);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Mamba2 hyperopt failed: {:#}", e);
|
||||
warn!("Continuing...");
|
||||
results.insert(
|
||||
"mamba2".to_string(),
|
||||
serde_json::json!({ "error": format!("{:#}", e) }),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write results to JSON
|
||||
let output_json = Value::Object(results);
|
||||
let output_str =
|
||||
serde_json::to_string_pretty(&output_json).context("Failed to serialize results")?;
|
||||
|
||||
std::fs::write(&args.output, &output_str)
|
||||
.with_context(|| format!("Failed to write results to {}", args.output.display()))?;
|
||||
|
||||
info!("========================================");
|
||||
info!(" Results saved to: {}", args.output.display());
|
||||
info!("========================================");
|
||||
info!("{}", output_str);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
//! Continuous PPO Hyperparameter Optimization Demo
|
||||
//!
|
||||
//! This example demonstrates the continuous PPO hyperparameter optimization adapter
|
||||
//! using the generic egobox optimization framework.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Run with default settings (5 trials, 10 epochs)
|
||||
//! cargo run -p ml --example hyperopt_continuous_ppo_demo --release --features cuda -- \
|
||||
//! --parquet-file test_data/ES_FUT_180d.parquet
|
||||
//!
|
||||
//! # Custom trials and epochs
|
||||
//! cargo run -p ml --example hyperopt_continuous_ppo_demo --release --features cuda -- \
|
||||
//! --parquet-file test_data/ES_FUT_180d.parquet \
|
||||
//! --trials 30 \
|
||||
//! --epochs 50
|
||||
//! ```
|
||||
//!
|
||||
//! # Expected Output
|
||||
//!
|
||||
//! - Real continuous PPO training with market data
|
||||
//! - Varying Sharpe ratios across trials
|
||||
//! - Convergence visible (best Sharpe improves)
|
||||
//! - Logs showing actual PPO training steps
|
||||
//! - GPU utilization (if CUDA available)
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use tracing::{info, Level};
|
||||
|
||||
use ml::hyperopt::adapters::continuous_ppo::{ContinuousPPOParams, ContinuousPPOTrainer};
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use ml::hyperopt::traits::ParameterSpace;
|
||||
use ml::hyperopt::EgoboxOptimizer;
|
||||
|
||||
/// CLI arguments
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "hyperopt_continuous_ppo_demo",
|
||||
about = "Continuous PPO hyperparameter optimization demonstration"
|
||||
)]
|
||||
struct Args {
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long, help = "Path to Parquet file with OHLCV data")]
|
||||
parquet_file: String,
|
||||
|
||||
/// Number of optimization trials
|
||||
#[arg(long, default_value = "5", help = "Number of optimization trials")]
|
||||
trials: usize,
|
||||
|
||||
/// Epochs per trial
|
||||
#[arg(long, default_value = "10", help = "Training epochs per trial")]
|
||||
epochs: usize,
|
||||
|
||||
/// Base directory for training outputs
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "/tmp/ml_training",
|
||||
help = "Base directory for training outputs"
|
||||
)]
|
||||
base_dir: PathBuf,
|
||||
|
||||
/// Run ID (auto-generated if not provided)
|
||||
#[arg(long, help = "Unique run ID (YYYYMMDD_HHMMSS_type if not provided)")]
|
||||
run_id: Option<String>,
|
||||
|
||||
/// Run type for auto-generated run ID
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "hyperopt_continuous_ppo",
|
||||
help = "Run type for auto-generated run ID"
|
||||
)]
|
||||
run_type: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.with_thread_ids(false)
|
||||
.init();
|
||||
|
||||
info!("╔═══════════════════════════════════════════════════════════╗");
|
||||
info!("║ Continuous PPO Hyperparameter Optimization Demo ║");
|
||||
info!("╚═══════════════════════════════════════════════════════════╝");
|
||||
info!("");
|
||||
|
||||
// Parse arguments
|
||||
let args = Args::parse();
|
||||
|
||||
info!("Configuration:");
|
||||
info!(" Trials: {}", args.trials);
|
||||
info!(" Epochs per trial: {}", args.epochs);
|
||||
info!(" Parquet file: {}", args.parquet_file);
|
||||
info!("");
|
||||
|
||||
// Validate Parquet file exists
|
||||
let parquet_path = std::path::Path::new(&args.parquet_file);
|
||||
if !parquet_path.exists() {
|
||||
anyhow::bail!("Parquet file not found: {}", args.parquet_file);
|
||||
}
|
||||
|
||||
// Generate run ID if not provided
|
||||
let run_id = args
|
||||
.run_id
|
||||
.unwrap_or_else(|| generate_run_id(&args.run_type));
|
||||
|
||||
// Create training paths
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "continuous_ppo", &run_id);
|
||||
|
||||
// Create all directories
|
||||
training_paths
|
||||
.create_all()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create training directories: {}", e))?;
|
||||
|
||||
info!("Training Paths:");
|
||||
info!(" Base directory: {:?}", args.base_dir);
|
||||
info!(" Run ID: {}", run_id);
|
||||
info!(" Run directory: {:?}", training_paths.run_dir());
|
||||
info!(" Checkpoints: {:?}", training_paths.checkpoints_dir());
|
||||
info!("");
|
||||
|
||||
// Create continuous PPO trainer with training paths
|
||||
let trainer = ContinuousPPOTrainer::new(&args.parquet_file, args.epochs)?
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
info!("Parameter Space:");
|
||||
let names = ContinuousPPOParams::param_names();
|
||||
let cont_bounds = ContinuousPPOParams::continuous_bounds();
|
||||
let int_bounds = ContinuousPPOParams::integer_bounds();
|
||||
let cat_choices = ContinuousPPOParams::categorical_choices();
|
||||
|
||||
info!(" Continuous Parameters:");
|
||||
for (i, name) in names.iter().take(cont_bounds.len()).enumerate() {
|
||||
let (min, max) = cont_bounds[i];
|
||||
info!(" {}: [{:.6}, {:.6}]", name, min, max);
|
||||
}
|
||||
|
||||
info!(" Integer Parameters:");
|
||||
for (i, name) in names
|
||||
.iter()
|
||||
.skip(cont_bounds.len())
|
||||
.take(int_bounds.len())
|
||||
.enumerate()
|
||||
{
|
||||
let (min, max) = int_bounds[i];
|
||||
info!(" {}: [{}, {}]", name, min, max);
|
||||
}
|
||||
|
||||
info!(" Categorical Parameters:");
|
||||
for (i, name) in names
|
||||
.iter()
|
||||
.skip(cont_bounds.len() + int_bounds.len())
|
||||
.take(cat_choices.len())
|
||||
.enumerate()
|
||||
{
|
||||
let choices = &cat_choices[i];
|
||||
info!(" {}: {:?}", name, choices);
|
||||
}
|
||||
info!("");
|
||||
|
||||
// Create optimizer
|
||||
let optimizer = EgoboxOptimizer::with_trials(args.trials, 3);
|
||||
|
||||
info!("Starting optimization...");
|
||||
info!("");
|
||||
|
||||
// Run optimization
|
||||
let result = optimizer.optimize(trainer)?;
|
||||
|
||||
info!("");
|
||||
info!("╔═══════════════════════════════════════════════════════════╗");
|
||||
info!("║ Optimization Complete ║");
|
||||
info!("╚═══════════════════════════════════════════════════════════╝");
|
||||
info!("");
|
||||
info!("Best Parameters:");
|
||||
info!(" Policy LR: {:.6}", result.best_params.policy_lr);
|
||||
info!(" Value LR: {:.6}", result.best_params.value_lr);
|
||||
info!(
|
||||
" Action bounds: [{:.2}, {:.2}]",
|
||||
result.best_params.action_min, result.best_params.action_max
|
||||
);
|
||||
info!(" Init log std: {:.2}", result.best_params.init_log_std);
|
||||
info!(" Learnable std: {}", result.best_params.learnable_std);
|
||||
info!(" Clip epsilon: {:.3}", result.best_params.clip_epsilon);
|
||||
info!(" Entropy coeff: {:.6}", result.best_params.entropy_coeff);
|
||||
info!(" GAE lambda: {:.3}", result.best_params.gae_lambda);
|
||||
info!(" Gamma: {:.3}", result.best_params.gamma);
|
||||
info!(" Batch size: {}", result.best_params.batch_size);
|
||||
info!(" Num epochs: {}", result.best_params.num_epochs);
|
||||
info!("");
|
||||
info!(
|
||||
"Best Objective (Sharpe ratio): {:.6}",
|
||||
-result.best_objective
|
||||
); // Negated (optimizer minimizes)
|
||||
info!("Total Evaluations: {}", result.all_trials.len());
|
||||
info!("");
|
||||
|
||||
// Print trial history for convergence analysis
|
||||
info!("Trial History:");
|
||||
info!("┌───────┬──────────────────┬──────────────────┬──────────────────┐");
|
||||
info!("│ Trial │ Policy LR │ Value LR │ Sharpe Ratio │");
|
||||
info!("├───────┼──────────────────┼──────────────────┼──────────────────┤");
|
||||
|
||||
for trial in &result.all_trials {
|
||||
info!(
|
||||
"│ {:5} │ {:16.6} │ {:16.6} │ {:16.6} │",
|
||||
trial.trial_num,
|
||||
trial.params.policy_lr,
|
||||
trial.params.value_lr,
|
||||
-trial.objective // Negate to show actual Sharpe
|
||||
);
|
||||
}
|
||||
|
||||
info!("└───────┴──────────────────┴──────────────────┴──────────────────┘");
|
||||
info!("");
|
||||
|
||||
// Compute convergence metrics
|
||||
if result.all_trials.len() >= 2 {
|
||||
let first_sharpe = -result.all_trials[0].objective;
|
||||
let best_sharpe = -result.best_objective;
|
||||
let improvement = ((best_sharpe - first_sharpe) / first_sharpe.abs().max(1.0)) * 100.0;
|
||||
|
||||
info!("Convergence Analysis:");
|
||||
info!(" First Trial Sharpe: {:.6}", first_sharpe);
|
||||
info!(" Best Trial Sharpe: {:.6}", best_sharpe);
|
||||
info!(" Improvement: {:.2}%", improvement);
|
||||
info!("");
|
||||
|
||||
// Compute variance in Sharpe values
|
||||
let sharpe_values: Vec<f64> = result.all_trials.iter().map(|e| -e.objective).collect();
|
||||
let mean_sharpe: f64 = sharpe_values.iter().sum::<f64>() / sharpe_values.len() as f64;
|
||||
let variance: f64 = sharpe_values
|
||||
.iter()
|
||||
.map(|s| (s - mean_sharpe).powi(2))
|
||||
.sum::<f64>()
|
||||
/ sharpe_values.len() as f64;
|
||||
let std_dev = variance.sqrt();
|
||||
let coeff_var = (std_dev / mean_sharpe.abs().max(0.001)) * 100.0;
|
||||
|
||||
info!("Sharpe Variance Analysis:");
|
||||
info!(" Mean Sharpe: {:.6}", mean_sharpe);
|
||||
info!(" Std Dev: {:.6}", std_dev);
|
||||
info!(" Coefficient of Variation: {:.2}%", coeff_var);
|
||||
info!("");
|
||||
|
||||
if coeff_var < 5.0 {
|
||||
info!(
|
||||
"⚠️ WARNING: Low Sharpe variance ({:.2}%) suggests mock metrics",
|
||||
coeff_var
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"✓ Sharpe variance ({:.2}%) confirms real training",
|
||||
coeff_var
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
info!("✓ Continuous PPO hyperparameter optimization demo complete");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
//! DQN Hyperparameter Optimization Demo
|
||||
//!
|
||||
//! This example demonstrates how to use the argmin-based hyperparameter
|
||||
//! optimization framework with DQN. It runs a small-scale optimization
|
||||
//! to show the complete workflow with REAL training (not mock metrics).
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Quick test with small DBN directory (5-10 minutes)
|
||||
//! cargo run -p ml --example hyperopt_dqn_demo --release --features cuda -- \
|
||||
//! --dbn-data-dir test_data/real/databento/ml_training_small \
|
||||
//! --trials 3 \
|
||||
//! --epochs 5
|
||||
//!
|
||||
//! # Production run with full optimization (1-2 hours)
|
||||
//! cargo run -p ml --example hyperopt_dqn_demo --release --features cuda -- \
|
||||
//! --dbn-data-dir test_data/real/databento/ml_training \
|
||||
//! --trials 30 \
|
||||
//! --epochs 50
|
||||
//! ```
|
||||
//!
|
||||
//! ## Output
|
||||
//!
|
||||
//! The example will:
|
||||
//! 1. Initialize DQN trainer with specified Parquet file
|
||||
//! 2. Run argmin optimization with Nelder-Mead simplex
|
||||
//! 3. Display trial results including loss and parameter values
|
||||
//! 4. Report best hyperparameters found
|
||||
//! 5. Show convergence and top trials
|
||||
//!
|
||||
//! ## Verification
|
||||
//!
|
||||
//! This example uses REAL training via `InternalDQNTrainer`, not mock metrics.
|
||||
//! You should see:
|
||||
//! - Loss values VARY across trials (not identical)
|
||||
//! - Training takes time (not instant)
|
||||
//! - GPU utilization visible (if CUDA available)
|
||||
//! - Convergence over trials (best loss improves)
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use ml::hyperopt::adapters::dqn::DQNTrainer;
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use std::path::PathBuf;
|
||||
use ml::hyperopt::ArgminOptimizer;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "DQN Hyperparameter Optimization Demo")]
|
||||
#[command(about = "Demonstrates argmin-based hyperparameter optimization for DQN")]
|
||||
struct Args {
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long)]
|
||||
parquet_file: String,
|
||||
|
||||
/// Number of optimization trials (default: 10)
|
||||
#[arg(long, default_value = "10")]
|
||||
trials: usize,
|
||||
|
||||
/// Number of training epochs per trial (default: 20)
|
||||
#[arg(long, default_value = "20")]
|
||||
epochs: usize,
|
||||
|
||||
/// Number of initial random samples (default: 2)
|
||||
#[arg(long, default_value = "2")]
|
||||
n_initial: usize,
|
||||
|
||||
/// Random seed for reproducibility (default: 42)
|
||||
#[arg(long, default_value = "42")]
|
||||
seed: u64,
|
||||
|
||||
/// Base directory for training outputs (default: /tmp/ml_training)
|
||||
#[arg(long, default_value = "/tmp/ml_training")]
|
||||
base_dir: String,
|
||||
|
||||
/// Run ID for organizing outputs (default: auto-generated)
|
||||
#[arg(long)]
|
||||
run_id: Option<String>,
|
||||
|
||||
/// Run type for run ID generation (default: hyperopt)
|
||||
#[arg(long, default_value = "hyperopt")]
|
||||
run_type: String,
|
||||
|
||||
/// Early stopping plateau window (epochs to check for improvement)
|
||||
#[arg(long, default_value = "5")]
|
||||
early_stopping_plateau_window: usize,
|
||||
|
||||
/// Early stopping minimum epochs (minimum epochs before early stopping can trigger)
|
||||
/// Default: 1000 (effectively disabled - Wave 7 validation proved early stopping kills 8-10 profitable trials)
|
||||
#[arg(long, default_value = "1000")]
|
||||
early_stopping_min_epochs: usize,
|
||||
|
||||
/// Optional path to feature cache directory for faster hyperopt
|
||||
///
|
||||
/// Pre-compute cache with: cargo run -p ml --example cache_dqn_features
|
||||
///
|
||||
/// Expected speedup: 2m 25s → <1s per trial (99% reduction)
|
||||
#[arg(long)]
|
||||
feature_cache_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn estimate_runtime(trials: usize, epochs: usize) -> usize {
|
||||
// DQN training is faster than MAMBA-2
|
||||
// Rough estimate: ~0.5 min per trial per 10 epochs
|
||||
let mins_per_trial = (epochs as f64 / 10.0) * 0.5;
|
||||
(trials as f64 * mins_per_trial).ceil() as usize
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
// Parse arguments
|
||||
let args = Args::parse();
|
||||
|
||||
info!("========================================");
|
||||
info!("DQN Hyperparameter Optimization Demo");
|
||||
info!("========================================");
|
||||
info!("Configuration:");
|
||||
info!(" Parquet file: {}", args.parquet_file);
|
||||
info!(" Trials: {}", args.trials);
|
||||
info!(" Epochs per trial: {}", args.epochs);
|
||||
info!(" Initial samples: {}", args.n_initial);
|
||||
info!(" Random seed: {}", args.seed);
|
||||
info!(" Base directory: {}", args.base_dir);
|
||||
info!("");
|
||||
|
||||
// Validate Parquet file exists
|
||||
let parquet_path = std::path::Path::new(&args.parquet_file);
|
||||
if !parquet_path.exists() {
|
||||
anyhow::bail!("Parquet file not found: {}", args.parquet_file);
|
||||
}
|
||||
|
||||
// Generate run ID
|
||||
let run_id = args
|
||||
.run_id
|
||||
.unwrap_or_else(|| generate_run_id(&args.run_type));
|
||||
info!("Run ID: {}", run_id);
|
||||
|
||||
// Create training paths
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "dqn", &run_id);
|
||||
info!("Training paths:");
|
||||
info!(" Run directory: {:?}", training_paths.run_dir());
|
||||
info!(" Checkpoints: {:?}", training_paths.checkpoints_dir());
|
||||
info!(" Hyperopt: {:?}", training_paths.hyperopt_dir());
|
||||
info!("");
|
||||
|
||||
// Create trainer with parquet file path directly
|
||||
info!(
|
||||
"Creating DQN trainer with parquet file: {}",
|
||||
args.parquet_file
|
||||
);
|
||||
let mut trainer = DQNTrainer::new(&args.parquet_file, args.epochs)?
|
||||
.with_early_stopping(
|
||||
args.early_stopping_plateau_window,
|
||||
args.early_stopping_min_epochs,
|
||||
);
|
||||
|
||||
// Enable feature cache if provided
|
||||
if let Some(cache_dir) = args.feature_cache_dir {
|
||||
info!("🚀 Enabling feature cache: {:?}", cache_dir);
|
||||
trainer = trainer.with_feature_cache(cache_dir);
|
||||
}
|
||||
|
||||
let trainer = trainer.with_training_paths(training_paths);
|
||||
|
||||
// Create optimizer
|
||||
info!("Initializing argmin optimizer...");
|
||||
let optimizer = ArgminOptimizer::builder()
|
||||
.max_trials(args.trials)
|
||||
.n_initial(args.n_initial)
|
||||
.seed(args.seed)
|
||||
.build();
|
||||
|
||||
// Run optimization
|
||||
info!("");
|
||||
info!("Starting optimization (this may take a while)...");
|
||||
info!(
|
||||
"Expected runtime: ~{} minutes",
|
||||
estimate_runtime(args.trials, args.epochs)
|
||||
);
|
||||
info!("");
|
||||
info!("VERIFICATION CHECKS:");
|
||||
info!(" ✓ Each trial should take >1 second (real training)");
|
||||
info!(" ✓ Loss values should VARY across trials");
|
||||
info!(" ✓ Best loss should improve over trials");
|
||||
info!(" ✓ GPU utilization should be visible (if CUDA available)");
|
||||
info!("");
|
||||
|
||||
let result = optimizer.optimize(trainer)?;
|
||||
|
||||
// Display results
|
||||
info!("");
|
||||
info!("========================================");
|
||||
info!("Optimization Complete!");
|
||||
info!("========================================");
|
||||
info!("");
|
||||
info!("Best Hyperparameters:");
|
||||
info!(" Learning rate: {:.6}", result.best_params.learning_rate);
|
||||
info!(" Batch size: {}", result.best_params.batch_size);
|
||||
info!(" Gamma: {:.3}", result.best_params.gamma);
|
||||
info!(" Buffer size: {}", result.best_params.buffer_size);
|
||||
info!("");
|
||||
info!("Performance:");
|
||||
info!(" Best episode reward: {:.6}", -result.best_objective); // Negate to get actual reward
|
||||
info!(" Total trials: {}", result.all_trials.len());
|
||||
|
||||
// Find convergence trial (where best was found)
|
||||
let convergence_trial = result
|
||||
.all_trials
|
||||
.iter()
|
||||
.position(|t| (t.objective - result.best_objective).abs() < 1e-10)
|
||||
.unwrap_or(0);
|
||||
info!(" Convergence: {} trials to best", convergence_trial + 1);
|
||||
info!("");
|
||||
|
||||
// Show top 5 trials (sorted by reward descending = objective ascending)
|
||||
if result.all_trials.len() >= 5 {
|
||||
info!("Top 5 Trials (by episode reward):");
|
||||
let mut sorted_trials = result.all_trials.clone();
|
||||
sorted_trials.sort_by(|a, b| a.objective.partial_cmp(&b.objective).unwrap());
|
||||
|
||||
for (i, trial) in sorted_trials.iter().take(5).enumerate() {
|
||||
info!(
|
||||
" {}. Reward: {:.6} (LR: {:.6}, BS: {}, Gamma: {:.3})",
|
||||
i + 1,
|
||||
-trial.objective, // Negate to show actual reward
|
||||
trial.params.learning_rate,
|
||||
trial.params.batch_size,
|
||||
trial.params.gamma
|
||||
);
|
||||
}
|
||||
info!("");
|
||||
}
|
||||
|
||||
// Validation check: Verify reward variance (objectives are negated rewards)
|
||||
let objectives: Vec<f64> = result.all_trials.iter().map(|t| t.objective).collect();
|
||||
let rewards: Vec<f64> = objectives.iter().map(|o| -o).collect();
|
||||
let mean_reward = rewards.iter().sum::<f64>() / rewards.len() as f64;
|
||||
let variance = rewards
|
||||
.iter()
|
||||
.map(|r| (r - mean_reward).powi(2))
|
||||
.sum::<f64>()
|
||||
/ rewards.len() as f64;
|
||||
let std_dev = variance.sqrt();
|
||||
|
||||
info!("VERIFICATION RESULTS:");
|
||||
info!(" Mean reward: {:.6}", mean_reward);
|
||||
info!(" Std deviation: {:.6}", std_dev);
|
||||
info!(
|
||||
" Min reward: {:.6}",
|
||||
rewards.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
|
||||
);
|
||||
info!(
|
||||
" Max reward: {:.6}",
|
||||
rewards.iter().cloned().fold(f64::INFINITY, f64::min)
|
||||
);
|
||||
info!("");
|
||||
|
||||
if std_dev < 1e-6 {
|
||||
info!("⚠️ WARNING: Reward values are identical across trials!");
|
||||
info!(" This suggests mock metrics are being used instead of real training.");
|
||||
info!(" Expected: std_dev > 0.001 for real training");
|
||||
} else {
|
||||
info!("✅ VERIFIED: Reward values vary across trials (real training confirmed)");
|
||||
info!(
|
||||
" Coefficient of variation: {:.2}%",
|
||||
(std_dev / mean_reward.abs()) * 100.0
|
||||
);
|
||||
}
|
||||
info!("");
|
||||
|
||||
// Calculate improvement over default
|
||||
let default_reward = rewards[0]; // First trial uses near-default params
|
||||
let best_reward = -result.best_objective;
|
||||
let improvement_pct = ((best_reward - default_reward) / default_reward.abs()) * 100.0;
|
||||
|
||||
info!("Improvement:");
|
||||
info!(" Initial (near-default): {:.6}", default_reward);
|
||||
info!(" Best (optimized): {:.6}", best_reward);
|
||||
info!(" Improvement: {:.2}%", improvement_pct);
|
||||
info!("");
|
||||
|
||||
info!("========================================");
|
||||
info!("Next Steps:");
|
||||
info!(" 1. Review hyperparameters above");
|
||||
info!(" 2. Run full optimization with --trials 30 --epochs 50");
|
||||
info!(" 3. Deploy best params to production DQN config");
|
||||
info!("========================================");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
//! MAMBA-2 Hyperparameter Optimization Demo
|
||||
//!
|
||||
//! This example demonstrates how to use the argmin-based hyperparameter
|
||||
//! optimization framework with MAMBA-2. It runs a small-scale optimization
|
||||
//! to show the complete workflow.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Local training with small dataset (default /tmp)
|
||||
//! cargo run -p ml --example hyperopt_mamba2_demo --release --features cuda -- \
|
||||
//! --parquet-file test_data/ES_FUT_small.parquet \
|
||||
//! --trials 4 --epochs 3
|
||||
//!
|
||||
//! # Runpod training with custom base dir
|
||||
//! ./hyperopt_mamba2_demo \
|
||||
//! --parquet-file /runpod-volume/datasets/parquet/futures/ES_FUT_180d.parquet \
|
||||
//! --base-dir /runpod-volume \
|
||||
//! --trials 30 --epochs 50
|
||||
//!
|
||||
//! # Resume from specific run
|
||||
//! ./hyperopt_mamba2_demo \
|
||||
//! --base-dir /runpod-volume \
|
||||
//! --run-id 20251028_223000_hyperopt \
|
||||
//! --parquet-file test_data/ES_FUT_180d.parquet \
|
||||
//! --trials 30 --epochs 50
|
||||
//! ```
|
||||
//!
|
||||
//! ## Output
|
||||
//!
|
||||
//! The example will:
|
||||
//! 1. Initialize MAMBA-2 trainer with specified Parquet file
|
||||
//! 2. Run argmin optimization with Nelder-Mead simplex
|
||||
//! 3. Display trial results including loss and parameter values
|
||||
//! 4. Report best hyperparameters found
|
||||
//! 5. Show expected improvement vs default parameters
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use ml::hyperopt::adapters::mamba2::Mamba2Trainer;
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use ml::hyperopt::ArgminOptimizer;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "MAMBA-2 Hyperparameter Optimization Demo")]
|
||||
#[command(about = "Demonstrates argmin-based hyperparameter optimization for MAMBA-2")]
|
||||
struct Args {
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long)]
|
||||
parquet_file: String,
|
||||
|
||||
/// Number of optimization trials (default: 10)
|
||||
#[arg(long, default_value = "10")]
|
||||
trials: usize,
|
||||
|
||||
/// Number of training epochs per trial (default: 20)
|
||||
#[arg(long, default_value = "20")]
|
||||
epochs: usize,
|
||||
|
||||
/// Number of initial random samples (default: 3)
|
||||
#[arg(long, default_value = "3")]
|
||||
n_initial: usize,
|
||||
|
||||
/// Random seed for reproducibility (default: 42)
|
||||
#[arg(long, default_value = "42")]
|
||||
seed: u64,
|
||||
|
||||
/// Minimum batch size (default: 4)
|
||||
#[arg(long, default_value = "4")]
|
||||
batch_size_min: usize,
|
||||
|
||||
/// Maximum batch size for GPU memory constraints (default: 96 for RTX A4000 16GB)
|
||||
/// Examples: RTX 3050 Ti 4GB = 32, RTX A4000 16GB = 96, RTX 4090 24GB = 256
|
||||
#[arg(long, default_value = "96")]
|
||||
batch_size_max: usize,
|
||||
|
||||
/// Base directory for training outputs (e.g., /runpod-volume)
|
||||
#[arg(long, default_value = "/tmp/ml_training")]
|
||||
base_dir: String,
|
||||
|
||||
/// Run ID (auto-generated if not provided)
|
||||
#[arg(long)]
|
||||
run_id: Option<String>,
|
||||
|
||||
/// Run type (hyperopt, production, test)
|
||||
#[arg(long, default_value = "hyperopt")]
|
||||
run_type: String,
|
||||
|
||||
/// Early stopping patience (epochs without improvement before stopping)
|
||||
#[arg(long, default_value = "5")]
|
||||
early_stopping_patience: usize,
|
||||
|
||||
/// Early stopping minimum epochs (minimum epochs before early stopping can trigger)
|
||||
#[arg(long, default_value = "5")]
|
||||
early_stopping_min_epochs: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
// Parse arguments
|
||||
let args = Args::parse();
|
||||
|
||||
// Generate run ID if not provided
|
||||
let run_id = args
|
||||
.run_id
|
||||
.unwrap_or_else(|| generate_run_id(&args.run_type));
|
||||
|
||||
// Create training paths
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "mamba2", &run_id);
|
||||
|
||||
info!("========================================");
|
||||
info!("MAMBA-2 Hyperparameter Optimization Demo");
|
||||
info!("========================================");
|
||||
info!("Configuration:");
|
||||
info!(" Parquet file: {}", args.parquet_file);
|
||||
info!(" Trials: {}", args.trials);
|
||||
info!(" Epochs per trial: {}", args.epochs);
|
||||
info!(" Initial samples: {}", args.n_initial);
|
||||
info!(" Random seed: {}", args.seed);
|
||||
info!(
|
||||
" Batch size bounds: [{}, {}]",
|
||||
args.batch_size_min, args.batch_size_max
|
||||
);
|
||||
info!("");
|
||||
info!("Training Paths:");
|
||||
info!(" Run ID: {}", run_id);
|
||||
info!(" Base directory: {}", args.base_dir);
|
||||
info!(" Run directory: {:?}", training_paths.run_dir());
|
||||
info!(" Checkpoints: {:?}", training_paths.checkpoints_dir());
|
||||
info!(" Logs: {:?}", training_paths.logs_dir());
|
||||
info!(" Hyperopt: {:?}", training_paths.hyperopt_dir());
|
||||
info!("");
|
||||
|
||||
// Create trainer with training paths
|
||||
info!("Creating MAMBA-2 trainer...");
|
||||
let trainer = Mamba2Trainer::new(&args.parquet_file, args.epochs)?
|
||||
.with_batch_size_bounds(args.batch_size_min as f64, args.batch_size_max as f64)
|
||||
.with_early_stopping(args.early_stopping_patience, args.early_stopping_min_epochs)
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
// Create optimizer
|
||||
info!("Initializing argmin optimizer...");
|
||||
let optimizer = ArgminOptimizer::builder()
|
||||
.max_trials(args.trials)
|
||||
.n_initial(args.n_initial)
|
||||
.seed(args.seed)
|
||||
.build();
|
||||
|
||||
// Run optimization
|
||||
info!("");
|
||||
info!("Starting optimization (this may take a while)...");
|
||||
info!(
|
||||
"Expected runtime: ~{} minutes",
|
||||
estimate_runtime(args.trials, args.epochs)
|
||||
);
|
||||
info!("");
|
||||
|
||||
let result = optimizer.optimize(trainer)?;
|
||||
|
||||
// Display results
|
||||
info!("");
|
||||
info!("========================================");
|
||||
info!("Optimization Complete!");
|
||||
info!("========================================");
|
||||
info!("");
|
||||
info!("Best Hyperparameters:");
|
||||
info!(" Learning rate: {:.6}", result.best_params.learning_rate);
|
||||
info!(" Batch size: {}", result.best_params.batch_size);
|
||||
info!(" Dropout: {:.3}", result.best_params.dropout);
|
||||
info!(" Weight decay: {:.6}", result.best_params.weight_decay);
|
||||
info!("");
|
||||
info!("Performance:");
|
||||
info!(" Best validation loss: {:.6}", result.best_objective);
|
||||
info!(" Total trials: {}", result.all_trials.len());
|
||||
|
||||
// Find convergence trial (where best was found)
|
||||
let convergence_trial = result
|
||||
.all_trials
|
||||
.iter()
|
||||
.position(|t| (t.objective - result.best_objective).abs() < 1e-10)
|
||||
.unwrap_or(0);
|
||||
info!(" Convergence: {} trials to best", convergence_trial + 1);
|
||||
info!("");
|
||||
|
||||
// Show top 5 trials
|
||||
if result.all_trials.len() >= 5 {
|
||||
info!("Top 5 Trials:");
|
||||
let mut sorted_trials = result.all_trials.clone();
|
||||
sorted_trials.sort_by(|a, b| a.objective.partial_cmp(&b.objective).unwrap());
|
||||
|
||||
for (i, trial) in sorted_trials.iter().take(5).enumerate() {
|
||||
info!(
|
||||
" {}. Loss: {:.6} (LR: {:.6}, BS: {}, Dropout: {:.3})",
|
||||
i + 1,
|
||||
trial.objective,
|
||||
trial.params.learning_rate,
|
||||
trial.params.batch_size,
|
||||
trial.params.dropout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
info!("");
|
||||
info!("========================================");
|
||||
info!("Next Steps:");
|
||||
info!("========================================");
|
||||
info!("1. Use best parameters for production training");
|
||||
info!("2. Run longer optimization (50+ trials) for better results");
|
||||
info!("3. Validate on holdout dataset");
|
||||
info!("4. Deploy optimized model to trading system");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Estimate runtime based on trials and epochs
|
||||
fn estimate_runtime(trials: usize, epochs: usize) -> usize {
|
||||
// Rough estimate: 2 min per 50 epochs on RTX 3050 Ti
|
||||
let minutes_per_trial = (epochs as f64 / 50.0) * 2.0;
|
||||
let total_minutes = (trials as f64 * minutes_per_trial).ceil() as usize;
|
||||
total_minutes
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
//! PPO Hyperparameter Optimization Demo
|
||||
//!
|
||||
//! This example demonstrates the PPO hyperparameter optimization adapter
|
||||
//! using the generic egobox optimization framework.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Run with default settings (3 trials, 1000 episodes)
|
||||
//! cargo run -p ml --example hyperopt_ppo_demo --release --features cuda
|
||||
//!
|
||||
//! # Custom trials and episodes
|
||||
//! cargo run -p ml --example hyperopt_ppo_demo --release --features cuda -- \
|
||||
//! --trials 5 \
|
||||
//! --episodes 500
|
||||
//! ```
|
||||
//!
|
||||
//! # Expected Output
|
||||
//!
|
||||
//! - Real PPO training with synthetic trajectories
|
||||
//! - Varying loss values across trials (not hardcoded)
|
||||
//! - Convergence visible (best metric improves)
|
||||
//! - Logs showing actual PPO training steps
|
||||
//! - GPU utilization (if CUDA available)
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
use tracing::{info, Level};
|
||||
|
||||
use ml::hyperopt::adapters::ppo::{PPOParams, PPOTrainer};
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use ml::hyperopt::traits::ParameterSpace;
|
||||
use ml::hyperopt::EgoboxOptimizer;
|
||||
|
||||
/// CLI arguments
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "hyperopt_ppo_demo",
|
||||
about = "PPO hyperparameter optimization demonstration"
|
||||
)]
|
||||
struct Args {
|
||||
/// Number of optimization trials
|
||||
#[arg(long, default_value = "3", help = "Number of optimization trials")]
|
||||
trials: usize,
|
||||
|
||||
/// Episodes per trial
|
||||
#[arg(long, default_value = "1000", help = "Training episodes per trial")]
|
||||
episodes: usize,
|
||||
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long, help = "Path to Parquet file with OHLCV data")]
|
||||
parquet_file: String,
|
||||
|
||||
/// Base directory for training outputs
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "/tmp/ml_training",
|
||||
help = "Base directory for training outputs"
|
||||
)]
|
||||
base_dir: PathBuf,
|
||||
|
||||
/// Run ID (auto-generated if not provided)
|
||||
#[arg(long, help = "Unique run ID (YYYYMMDD_HHMMSS_type if not provided)")]
|
||||
run_id: Option<String>,
|
||||
|
||||
/// Run type for auto-generated run ID
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "hyperopt",
|
||||
help = "Run type for auto-generated run ID"
|
||||
)]
|
||||
run_type: String,
|
||||
|
||||
/// Early stopping patience (epochs without improvement before stopping)
|
||||
#[arg(long, default_value = "5")]
|
||||
early_stopping_patience: usize,
|
||||
|
||||
/// Early stopping minimum epochs (minimum epochs before early stopping can trigger)
|
||||
#[arg(long, default_value = "5")]
|
||||
early_stopping_min_epochs: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.with_thread_ids(false)
|
||||
.init();
|
||||
|
||||
info!("╔═══════════════════════════════════════════════════════════╗");
|
||||
info!("║ PPO Hyperparameter Optimization Demo ║");
|
||||
info!("╚═══════════════════════════════════════════════════════════╝");
|
||||
info!("");
|
||||
|
||||
// Parse arguments
|
||||
let args = Args::parse();
|
||||
|
||||
info!("Configuration:");
|
||||
info!(" Trials: {}", args.trials);
|
||||
info!(" Episodes per trial: {}", args.episodes);
|
||||
info!(" Parquet file: {}", args.parquet_file);
|
||||
info!("");
|
||||
|
||||
// Generate run ID if not provided
|
||||
let run_id = args
|
||||
.run_id
|
||||
.unwrap_or_else(|| generate_run_id(&args.run_type));
|
||||
|
||||
// Create training paths
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "ppo", &run_id);
|
||||
|
||||
// Create all directories
|
||||
training_paths
|
||||
.create_all()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create training directories: {}", e))?;
|
||||
|
||||
info!("Training Paths:");
|
||||
info!(" Base directory: {:?}", args.base_dir);
|
||||
info!(" Run ID: {}", run_id);
|
||||
info!(" Run directory: {:?}", training_paths.run_dir());
|
||||
info!(" Checkpoints: {:?}", training_paths.checkpoints_dir());
|
||||
info!("");
|
||||
|
||||
// Validate Parquet file exists and extract directory
|
||||
let parquet_path = std::path::Path::new(&args.parquet_file);
|
||||
if !parquet_path.exists() {
|
||||
anyhow::bail!("Parquet file not found: {}", args.parquet_file);
|
||||
}
|
||||
|
||||
let data_dir = parquet_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to extract directory from parquet file path"))?;
|
||||
|
||||
// Create PPO trainer with training paths
|
||||
let trainer = PPOTrainer::new(data_dir, args.episodes)?
|
||||
.with_early_stopping(args.early_stopping_patience, args.early_stopping_min_epochs)
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
info!("Parameter Space:");
|
||||
let names = PPOParams::param_names();
|
||||
let bounds = PPOParams::continuous_bounds();
|
||||
for (name, (min, max)) in names.iter().zip(bounds.iter()) {
|
||||
info!(" {}: [{:.6}, {:.6}]", name, min, max);
|
||||
}
|
||||
info!("");
|
||||
|
||||
// Create optimizer
|
||||
let optimizer = EgoboxOptimizer::with_trials(args.trials, 3);
|
||||
|
||||
info!("Starting optimization...");
|
||||
info!("");
|
||||
|
||||
// Run optimization
|
||||
let result = optimizer.optimize(trainer)?;
|
||||
|
||||
info!("");
|
||||
info!("╔═══════════════════════════════════════════════════════════╗");
|
||||
info!("║ Optimization Complete ║");
|
||||
info!("╚═══════════════════════════════════════════════════════════╝");
|
||||
info!("");
|
||||
info!("Best Parameters:");
|
||||
info!(
|
||||
" Policy LR: {:.6}",
|
||||
result.best_params.policy_learning_rate
|
||||
);
|
||||
info!(" Value LR: {:.6}", result.best_params.value_learning_rate);
|
||||
info!(" Clip epsilon: {:.3}", result.best_params.clip_epsilon);
|
||||
info!(
|
||||
" Value loss coeff: {:.3}",
|
||||
result.best_params.value_loss_coeff
|
||||
);
|
||||
info!(" Entropy coeff: {:.6}", result.best_params.entropy_coeff);
|
||||
info!("");
|
||||
info!(
|
||||
"Best Objective (combined loss): {:.6}",
|
||||
result.best_objective
|
||||
);
|
||||
info!("Total Evaluations: {}", result.all_trials.len());
|
||||
info!("");
|
||||
|
||||
// Print trial history for convergence analysis
|
||||
info!("Trial History:");
|
||||
info!("┌───────┬──────────────────┬──────────────────┬──────────────────┐");
|
||||
info!("│ Trial │ Policy LR │ Value LR │ Combined Loss │");
|
||||
info!("├───────┼──────────────────┼──────────────────┼──────────────────┤");
|
||||
|
||||
for trial in &result.all_trials {
|
||||
info!(
|
||||
"│ {:5} │ {:16.6} │ {:16.6} │ {:16.6} │",
|
||||
trial.trial_num,
|
||||
trial.params.policy_learning_rate,
|
||||
trial.params.value_learning_rate,
|
||||
trial.objective
|
||||
);
|
||||
}
|
||||
|
||||
info!("└───────┴──────────────────┴──────────────────┴──────────────────┘");
|
||||
info!("");
|
||||
|
||||
// Compute convergence metrics
|
||||
if result.all_trials.len() >= 2 {
|
||||
let first_loss = result.all_trials[0].objective;
|
||||
let best_loss = result.best_objective;
|
||||
let improvement = ((first_loss - best_loss) / first_loss) * 100.0;
|
||||
|
||||
info!("Convergence Analysis:");
|
||||
info!(" First Trial Loss: {:.6}", first_loss);
|
||||
info!(" Best Trial Loss: {:.6}", best_loss);
|
||||
info!(" Improvement: {:.2}%", improvement);
|
||||
info!("");
|
||||
|
||||
// Compute variance in loss values
|
||||
let mean_loss: f64 = result.all_trials.iter().map(|e| e.objective).sum::<f64>()
|
||||
/ result.all_trials.len() as f64;
|
||||
let variance: f64 = result
|
||||
.all_trials
|
||||
.iter()
|
||||
.map(|e| (e.objective - mean_loss).powi(2))
|
||||
.sum::<f64>()
|
||||
/ result.all_trials.len() as f64;
|
||||
let std_dev = variance.sqrt();
|
||||
let coeff_var = (std_dev / mean_loss) * 100.0;
|
||||
|
||||
info!("Loss Variance Analysis:");
|
||||
info!(" Mean Loss: {:.6}", mean_loss);
|
||||
info!(" Std Dev: {:.6}", std_dev);
|
||||
info!(" Coefficient of Variation: {:.2}%", coeff_var);
|
||||
info!("");
|
||||
|
||||
if coeff_var < 5.0 {
|
||||
info!(
|
||||
"⚠️ WARNING: Low loss variance ({:.2}%) suggests mock metrics",
|
||||
coeff_var
|
||||
);
|
||||
} else {
|
||||
info!("✓ Loss variance ({:.2}%) confirms real training", coeff_var);
|
||||
}
|
||||
}
|
||||
|
||||
info!("✓ PPO hyperparameter optimization demo complete");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
//! TFT Hyperparameter Optimization Demo
|
||||
//!
|
||||
//! This example demonstrates how to use the argmin-based hyperparameter
|
||||
//! optimization framework with Temporal Fusion Transformer (TFT). It runs
|
||||
//! a small-scale optimization to show the complete workflow.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Local training with small dataset (default /tmp)
|
||||
//! cargo run -p ml --example hyperopt_tft_demo --release --features cuda -- \
|
||||
//! --parquet-file test_data/ES_FUT_180d.parquet \
|
||||
//! --trials 10 \
|
||||
//! --epochs 20
|
||||
//!
|
||||
//! # Runpod training with custom base dir
|
||||
//! ./hyperopt_tft_demo \
|
||||
//! --parquet-file /runpod-volume/datasets/parquet/futures/ES_FUT_180d.parquet \
|
||||
//! --base-dir /runpod-volume \
|
||||
//! --trials 50 \
|
||||
//! --epochs 50
|
||||
//!
|
||||
//! # Resume from specific run
|
||||
//! ./hyperopt_tft_demo \
|
||||
//! --base-dir /runpod-volume \
|
||||
//! --run-id 20251028_223000_hyperopt \
|
||||
//! --parquet-file test_data/ES_FUT_180d.parquet \
|
||||
//! --trials 50 \
|
||||
//! --epochs 50
|
||||
//! ```
|
||||
//!
|
||||
//! ## Output
|
||||
//!
|
||||
//! The example will:
|
||||
//! 1. Initialize TFT trainer with specified Parquet file
|
||||
//! 2. Run argmin optimization with Particle Swarm
|
||||
//! 3. Display trial results including loss and parameter values
|
||||
//! 4. Report best hyperparameters found
|
||||
//! 5. Show expected improvement vs default parameters
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use ml::hyperopt::adapters::tft::TFTTrainer;
|
||||
use ml::hyperopt::paths::{generate_run_id, TrainingPaths};
|
||||
use ml::hyperopt::ArgminOptimizer;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "TFT Hyperparameter Optimization Demo")]
|
||||
#[command(about = "Demonstrates argmin-based hyperparameter optimization for TFT")]
|
||||
struct Args {
|
||||
/// Path to Parquet file with OHLCV data
|
||||
#[arg(long)]
|
||||
parquet_file: String,
|
||||
|
||||
/// Number of optimization trials (default: 10)
|
||||
#[arg(long, default_value = "10")]
|
||||
trials: usize,
|
||||
|
||||
/// Number of training epochs per trial (default: 20)
|
||||
#[arg(long, default_value = "20")]
|
||||
epochs: usize,
|
||||
|
||||
/// Number of initial random samples (default: 3)
|
||||
#[arg(long, default_value = "3")]
|
||||
n_initial: usize,
|
||||
|
||||
/// Random seed for reproducibility (default: 42)
|
||||
#[arg(long, default_value = "42")]
|
||||
seed: u64,
|
||||
|
||||
/// Minimum batch size (default: 16)
|
||||
#[arg(long, default_value = "16")]
|
||||
batch_size_min: usize,
|
||||
|
||||
/// Maximum batch size for GPU memory constraints (default: 128 for RTX A4000 16GB)
|
||||
/// Examples: RTX 3050 Ti 4GB = 64, RTX A4000 16GB = 128, RTX 4090 24GB = 256
|
||||
#[arg(long, default_value = "128")]
|
||||
batch_size_max: usize,
|
||||
|
||||
/// Base directory for training outputs (e.g., /runpod-volume)
|
||||
#[arg(long, default_value = "/tmp/ml_training")]
|
||||
base_dir: String,
|
||||
|
||||
/// Run ID (auto-generated if not provided)
|
||||
#[arg(long)]
|
||||
run_id: Option<String>,
|
||||
|
||||
/// Run type (hyperopt, production, test)
|
||||
#[arg(long, default_value = "hyperopt")]
|
||||
run_type: String,
|
||||
|
||||
/// Early stopping patience (epochs without improvement before stopping)
|
||||
#[arg(long, default_value = "10")]
|
||||
early_stopping_patience: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(Level::INFO)
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
// Parse arguments
|
||||
let args = Args::parse();
|
||||
|
||||
// Generate run ID if not provided
|
||||
let run_id = args
|
||||
.run_id
|
||||
.unwrap_or_else(|| generate_run_id(&args.run_type));
|
||||
|
||||
// Create training paths
|
||||
let training_paths = TrainingPaths::new(&args.base_dir, "tft", &run_id);
|
||||
|
||||
info!("========================================");
|
||||
info!("TFT Hyperparameter Optimization Demo");
|
||||
info!("========================================");
|
||||
info!("Configuration:");
|
||||
info!(" Parquet file: {}", args.parquet_file);
|
||||
info!(" Trials: {}", args.trials);
|
||||
info!(" Epochs per trial: {}", args.epochs);
|
||||
info!(" Initial samples: {}", args.n_initial);
|
||||
info!(" Random seed: {}", args.seed);
|
||||
info!(
|
||||
" Batch size bounds: [{}, {}]",
|
||||
args.batch_size_min, args.batch_size_max
|
||||
);
|
||||
info!("");
|
||||
info!("Training Paths:");
|
||||
info!(" Run ID: {}", run_id);
|
||||
info!(" Base directory: {}", args.base_dir);
|
||||
info!(" Run directory: {:?}", training_paths.run_dir());
|
||||
info!(" Checkpoints: {:?}", training_paths.checkpoints_dir());
|
||||
info!(" Logs: {:?}", training_paths.logs_dir());
|
||||
info!(" Hyperopt: {:?}", training_paths.hyperopt_dir());
|
||||
info!("");
|
||||
|
||||
// Create trainer with training paths
|
||||
info!("Creating TFT trainer...");
|
||||
let trainer = TFTTrainer::new(&args.parquet_file, args.epochs)?
|
||||
.with_early_stopping(args.early_stopping_patience)
|
||||
.with_training_paths(training_paths);
|
||||
|
||||
info!("TFT Configuration:");
|
||||
info!(" Input features: 54 (Wave C + Wave D)");
|
||||
info!(" Sequence length: 60");
|
||||
info!(" Prediction horizon: 10");
|
||||
info!(" Quantiles: 3 (0.1, 0.5, 0.9)");
|
||||
info!("");
|
||||
|
||||
// Create optimizer
|
||||
info!("Initializing argmin optimizer...");
|
||||
let optimizer = ArgminOptimizer::builder()
|
||||
.max_trials(args.trials)
|
||||
.n_initial(args.n_initial)
|
||||
.seed(args.seed)
|
||||
.build();
|
||||
|
||||
// Run optimization
|
||||
info!("");
|
||||
info!("Starting optimization (this may take a while)...");
|
||||
info!(
|
||||
"Expected runtime: ~{} minutes",
|
||||
estimate_runtime(args.trials, args.epochs)
|
||||
);
|
||||
info!("");
|
||||
|
||||
let result = optimizer.optimize(trainer)?;
|
||||
|
||||
// Display results
|
||||
info!("");
|
||||
info!("========================================");
|
||||
info!("Optimization Complete!");
|
||||
info!("========================================");
|
||||
info!("");
|
||||
info!("Best Hyperparameters:");
|
||||
info!(" Learning rate: {:.6}", result.best_params.learning_rate);
|
||||
info!(" Batch size: {}", result.best_params.batch_size);
|
||||
info!(" Hidden size: {}", result.best_params.hidden_size);
|
||||
info!(" Attention heads: {}", result.best_params.num_heads);
|
||||
info!(" Dropout: {:.3}", result.best_params.dropout);
|
||||
info!("");
|
||||
info!("Performance:");
|
||||
info!(" Best validation loss: {:.6}", result.best_objective);
|
||||
info!(" Total trials: {}", result.all_trials.len());
|
||||
|
||||
// Find convergence trial (where best was found)
|
||||
let convergence_trial = result
|
||||
.all_trials
|
||||
.iter()
|
||||
.position(|t| (t.objective - result.best_objective).abs() < 1e-10)
|
||||
.unwrap_or(0);
|
||||
info!(" Convergence: {} trials to best", convergence_trial + 1);
|
||||
info!("");
|
||||
|
||||
// Show top 5 trials
|
||||
if result.all_trials.len() >= 5 {
|
||||
info!("Top 5 Trials:");
|
||||
let mut sorted_trials = result.all_trials.clone();
|
||||
sorted_trials.sort_by(|a, b| a.objective.partial_cmp(&b.objective).unwrap());
|
||||
|
||||
for (i, trial) in sorted_trials.iter().take(5).enumerate() {
|
||||
info!(
|
||||
" {}. Loss: {:.6} (LR: {:.6}, BS: {}, Hidden: {}, Heads: {})",
|
||||
i + 1,
|
||||
trial.objective,
|
||||
trial.params.learning_rate,
|
||||
trial.params.batch_size,
|
||||
trial.params.hidden_size,
|
||||
trial.params.num_heads
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
info!("");
|
||||
info!("========================================");
|
||||
info!("Architecture Insights:");
|
||||
info!("========================================");
|
||||
|
||||
// Analyze best parameters
|
||||
let best = &result.best_params;
|
||||
|
||||
// Calculate model complexity
|
||||
let complexity_score = (best.hidden_size as f64 * best.num_heads as f64) / 1000.0;
|
||||
let complexity_level = if complexity_score < 2.0 {
|
||||
"Light"
|
||||
} else if complexity_score < 4.0 {
|
||||
"Balanced"
|
||||
} else {
|
||||
"Heavy"
|
||||
};
|
||||
|
||||
info!(
|
||||
"Model Complexity: {} (score: {:.2})",
|
||||
complexity_level, complexity_score
|
||||
);
|
||||
info!(" Hidden dimension: {} features", best.hidden_size);
|
||||
info!(" Attention heads: {} heads", best.num_heads);
|
||||
info!(
|
||||
" Head dimension: {} features/head",
|
||||
best.hidden_size / best.num_heads
|
||||
);
|
||||
info!("");
|
||||
|
||||
// Regularization analysis
|
||||
let regularization_level = if best.dropout < 0.1 {
|
||||
"Low"
|
||||
} else if best.dropout < 0.2 {
|
||||
"Medium"
|
||||
} else {
|
||||
"High"
|
||||
};
|
||||
|
||||
info!("Regularization: {}", regularization_level);
|
||||
info!(" Dropout rate: {:.1}%", best.dropout * 100.0);
|
||||
info!("");
|
||||
|
||||
// Training characteristics
|
||||
info!("Training Characteristics:");
|
||||
info!(
|
||||
" Learning rate: {:.6} ({})",
|
||||
best.learning_rate,
|
||||
if best.learning_rate < 5e-5 {
|
||||
"Conservative"
|
||||
} else if best.learning_rate < 2e-4 {
|
||||
"Balanced"
|
||||
} else {
|
||||
"Aggressive"
|
||||
}
|
||||
);
|
||||
info!(
|
||||
" Batch size: {} (GPU memory: ~{}MB)",
|
||||
best.batch_size,
|
||||
estimate_gpu_memory(best.batch_size, best.hidden_size)
|
||||
);
|
||||
info!("");
|
||||
|
||||
info!("========================================");
|
||||
info!("Next Steps:");
|
||||
info!("========================================");
|
||||
info!("1. Use best parameters for production training");
|
||||
info!("2. Run longer optimization (50+ trials) for better results");
|
||||
info!("3. Validate on holdout dataset");
|
||||
info!("4. Deploy optimized model to trading system");
|
||||
info!(
|
||||
"5. Consider hidden_size={} as your production baseline",
|
||||
best.hidden_size
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Estimate runtime based on trials and epochs
|
||||
fn estimate_runtime(trials: usize, epochs: usize) -> usize {
|
||||
// Rough estimate: 2 min per 50 epochs on RTX 3050 Ti for TFT
|
||||
let minutes_per_trial = (epochs as f64 / 50.0) * 2.0;
|
||||
let total_minutes = (trials as f64 * minutes_per_trial).ceil() as usize;
|
||||
total_minutes
|
||||
}
|
||||
|
||||
/// Estimate GPU memory usage for a given configuration
|
||||
fn estimate_gpu_memory(batch_size: usize, hidden_size: usize) -> usize {
|
||||
// Rough estimate: base (200MB) + sequence memory
|
||||
// TFT has encoder-decoder architecture with attention
|
||||
let base_memory = 200;
|
||||
let sequence_memory = (batch_size * hidden_size * 60 * 8) / 1_000_000; // 60 seq length, 8 bytes/float
|
||||
let attention_memory = (batch_size * 60 * 60 * 4) / 1_000_000; // attention matrix
|
||||
|
||||
base_memory + sequence_memory + attention_memory
|
||||
}
|
||||
@@ -59,17 +59,15 @@ COPY testing ./testing
|
||||
ENV SQLX_OFFLINE=true
|
||||
ENV CUDA_COMPUTE_CAP=90
|
||||
|
||||
# Build training binaries with CUDA support (2 unified baselines + eval + hyperopt)
|
||||
# Build training + hyperopt binaries with CUDA support
|
||||
RUN cargo build --release -p ml --features ml/cuda \
|
||||
--example train_baseline_rl \
|
||||
--example train_baseline_supervised \
|
||||
--example evaluate_baseline \
|
||||
--example hyperopt_dqn_demo \
|
||||
--example hyperopt_ppo_demo \
|
||||
--example hyperopt_tft_demo \
|
||||
--example hyperopt_mamba2_demo \
|
||||
--example hyperopt_baseline_rl \
|
||||
--example hyperopt_baseline_supervised \
|
||||
&& mkdir -p /build/out \
|
||||
&& for bin in train_baseline_rl train_baseline_supervised evaluate_baseline hyperopt_dqn_demo hyperopt_ppo_demo hyperopt_tft_demo hyperopt_mamba2_demo; do \
|
||||
&& for bin in train_baseline_rl train_baseline_supervised evaluate_baseline hyperopt_baseline_rl hyperopt_baseline_supervised; do \
|
||||
cp target/release/examples/${bin} /build/out/ && strip /build/out/${bin}; \
|
||||
done
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ spec:
|
||||
# train_baseline_rl (for dqn, ppo)
|
||||
# train_baseline_supervised (for tft, mamba2, tggn, tlob, liquid, kan, xlstm, diffusion)
|
||||
# evaluate_baseline
|
||||
# hyperopt_dqn_demo, hyperopt_ppo_demo, hyperopt_tft_demo, hyperopt_mamba2_demo
|
||||
# hyperopt_baseline_rl (for dqn, ppo)
|
||||
# hyperopt_baseline_supervised (for tft, mamba2)
|
||||
command: ["/usr/local/bin/$(TRAINING_BINARY)"]
|
||||
args:
|
||||
- "--symbol=ES.FUT"
|
||||
|
||||
@@ -47,10 +47,8 @@ DOCKER_REGISTRY="jgrusewski"
|
||||
IMAGE_NAME="foxhunt-hyperopt"
|
||||
DEFAULT_DOCKERFILE="Dockerfile.foxhunt-build"
|
||||
EXPECTED_BINARIES=(
|
||||
"hyperopt_mamba2_demo"
|
||||
"hyperopt_dqn_demo"
|
||||
"hyperopt_ppo_demo"
|
||||
"hyperopt_tft_demo"
|
||||
"hyperopt_baseline_rl"
|
||||
"hyperopt_baseline_supervised"
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user