use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingConfig {
pub model: ModelConfig,
pub data: DataConfig,
pub training: TrainingHyperparams,
pub optimizer: OptimizerConfig,
pub scheduler: SchedulerConfig,
pub logging: LoggingConfig,
pub checkpoint: CheckpointConfig,
pub hardware: HardwareConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelConfig {
pub model_type: String,
pub architecture: HashMap<String, serde_json::Value>,
pub input_size: (u32, u32, u32),
pub vocab_size: usize,
pub max_seq_length: usize,
pub num_classes: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataConfig {
pub dataset_path: String,
pub dataset_format: String,
pub batch_size: usize,
pub val_batch_size: usize,
pub num_workers: usize,
pub shuffle: bool,
pub augmentation: AugmentationConfig,
pub preprocessing: PreprocessingConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AugmentationConfig {
pub enabled: bool,
pub rotation_range: (f32, f32),
pub scale_range: (f32, f32),
pub translation_range: (f32, f32),
pub brightness_range: (f32, f32),
pub contrast_range: (f32, f32),
pub noise_level: f32,
pub blur_probability: f32,
pub crop_probability: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreprocessingConfig {
pub mean: Vec<f32>,
pub std: Vec<f32>,
pub grayscale: bool,
pub target_size: (u32, u32),
pub preserve_aspect_ratio: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingHyperparams {
pub num_epochs: usize,
pub early_stopping_patience: usize,
pub gradient_clip_norm: Option<f32>,
pub mixed_precision: bool,
pub label_smoothing: f32,
pub dropout_rate: f32,
pub weight_decay: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizerConfig {
pub optimizer_type: String,
pub learning_rate: f32,
pub parameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchedulerConfig {
pub scheduler_type: String,
pub parameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub log_level: String,
pub log_dir: String,
pub console_logging: bool,
pub file_logging: bool,
pub log_interval: usize,
pub metrics: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointConfig {
pub checkpoint_dir: String,
pub save_interval: usize,
pub max_checkpoints: usize,
pub save_best: bool,
pub best_metric: String,
pub save_optimizer: bool,
pub save_scheduler: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareConfig {
pub device: String,
pub num_gpus: usize,
pub gpu_memory_fraction: f32,
pub use_mixed_precision: bool,
pub num_threads: usize,
}
impl Default for TrainingConfig {
fn default() -> Self {
Self {
model: ModelConfig::default(),
data: DataConfig::default(),
training: TrainingHyperparams::default(),
optimizer: OptimizerConfig::default(),
scheduler: SchedulerConfig::default(),
logging: LoggingConfig::default(),
checkpoint: CheckpointConfig::default(),
hardware: HardwareConfig::default(),
}
}
}
impl Default for ModelConfig {
fn default() -> Self {
Self {
model_type: "LSTM".to_string(),
architecture: HashMap::new(),
input_size: (224, 224, 3),
vocab_size: 1000,
max_seq_length: 128,
num_classes: 1000,
}
}
}
impl Default for DataConfig {
fn default() -> Self {
Self {
dataset_path: "./data".to_string(),
dataset_format: "synthetic".to_string(),
batch_size: 32,
val_batch_size: 64,
num_workers: 4,
shuffle: true,
augmentation: AugmentationConfig::default(),
preprocessing: PreprocessingConfig::default(),
}
}
}
impl Default for AugmentationConfig {
fn default() -> Self {
Self {
enabled: true,
rotation_range: (-5.0, 5.0),
scale_range: (0.9, 1.1),
translation_range: (-10.0, 10.0),
brightness_range: (0.8, 1.2),
contrast_range: (0.8, 1.2),
noise_level: 0.01,
blur_probability: 0.1,
crop_probability: 0.1,
}
}
}
impl Default for PreprocessingConfig {
fn default() -> Self {
Self {
mean: vec![0.485, 0.456, 0.406],
std: vec![0.229, 0.224, 0.225],
grayscale: false,
target_size: (224, 224),
preserve_aspect_ratio: true,
}
}
}
impl Default for TrainingHyperparams {
fn default() -> Self {
Self {
num_epochs: 100,
early_stopping_patience: 10,
gradient_clip_norm: Some(1.0),
mixed_precision: false,
label_smoothing: 0.0,
dropout_rate: 0.1,
weight_decay: 1e-4,
}
}
}
impl Default for OptimizerConfig {
fn default() -> Self {
let mut parameters = HashMap::new();
parameters.insert(
"momentum".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(0.9).unwrap()),
);
parameters.insert(
"weight_decay".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(1e-4).unwrap()),
);
Self {
optimizer_type: "Adam".to_string(),
learning_rate: 1e-3,
parameters,
}
}
}
impl Default for SchedulerConfig {
fn default() -> Self {
let mut parameters = HashMap::new();
parameters.insert(
"step_size".to_string(),
serde_json::Value::Number(serde_json::Number::from(30)),
);
parameters.insert(
"gamma".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(0.1).unwrap()),
);
Self {
scheduler_type: "StepLR".to_string(),
parameters,
}
}
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
log_level: "info".to_string(),
log_dir: "./logs".to_string(),
console_logging: true,
file_logging: true,
log_interval: 100,
metrics: vec![
"loss".to_string(),
"accuracy".to_string(),
"learning_rate".to_string(),
],
}
}
}
impl Default for CheckpointConfig {
fn default() -> Self {
Self {
checkpoint_dir: "./checkpoints".to_string(),
save_interval: 5,
max_checkpoints: 5,
save_best: true,
best_metric: "val_accuracy".to_string(),
save_optimizer: true,
save_scheduler: true,
}
}
}
impl Default for HardwareConfig {
fn default() -> Self {
Self {
device: "CPU".to_string(),
num_gpus: 0,
gpu_memory_fraction: 0.8,
use_mixed_precision: false,
num_threads: 4, }
}
}
impl TrainingConfig {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: TrainingConfig = serde_json::from_str(&content)?;
Ok(config)
}
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn validate(&self) -> Result<()> {
if self.model.vocab_size == 0 {
return Err(anyhow::anyhow!("Vocabulary size must be greater than 0"));
}
if self.model.max_seq_length == 0 {
return Err(anyhow::anyhow!(
"Maximum sequence length must be greater than 0"
));
}
if self.data.batch_size == 0 {
return Err(anyhow::anyhow!("Batch size must be greater than 0"));
}
if self.data.val_batch_size == 0 {
return Err(anyhow::anyhow!(
"Validation batch size must be greater than 0"
));
}
if self.training.num_epochs == 0 {
return Err(anyhow::anyhow!("Number of epochs must be greater than 0"));
}
if self.optimizer.learning_rate <= 0.0 {
return Err(anyhow::anyhow!("Learning rate must be greater than 0"));
}
if self.optimizer.learning_rate <= 0.0 {
return Err(anyhow::anyhow!(
"Optimizer learning rate must be greater than 0"
));
}
if self.hardware.num_gpus > 0 && self.hardware.device == "CPU" {
return Err(anyhow::anyhow!("Cannot use GPUs with CPU device"));
}
Ok(())
}
pub fn get_learning_rate(&self, epoch: usize, step: usize) -> f32 {
match self.scheduler.scheduler_type.as_str() {
"StepLR" => {
let step_size = self
.scheduler
.parameters
.get("step_size")
.and_then(|v| v.as_u64())
.unwrap_or(30) as usize;
let gamma = self
.scheduler
.parameters
.get("gamma")
.and_then(|v| v.as_f64())
.unwrap_or(0.1) as f32;
self.optimizer.learning_rate * gamma.powi((step / step_size) as i32)
}
"ExponentialLR" => {
let gamma = self
.scheduler
.parameters
.get("gamma")
.and_then(|v| v.as_f64())
.unwrap_or(0.95) as f32;
self.optimizer.learning_rate * gamma.powi(step as i32)
}
"CosineAnnealingLR" => {
let t_max = self
.scheduler
.parameters
.get("t_max")
.and_then(|v| v.as_u64())
.unwrap_or(100) as usize;
let eta_min = self
.scheduler
.parameters
.get("eta_min")
.and_then(|v| v.as_f64())
.unwrap_or(0.0) as f32;
let step = step % t_max;
eta_min
+ (self.optimizer.learning_rate - eta_min)
* (1.0 + (std::f32::consts::PI * step as f32 / t_max as f32).cos())
/ 2.0
}
_ => self.optimizer.learning_rate,
}
}
pub fn test_config() -> Self {
Self {
model: ModelConfig {
model_type: "LSTM".to_string(),
architecture: HashMap::new(),
input_size: (64, 64, 1),
vocab_size: 100,
max_seq_length: 32,
num_classes: 100,
},
data: DataConfig {
dataset_path: "./test_data".to_string(),
dataset_format: "synthetic".to_string(),
batch_size: 4,
val_batch_size: 8,
num_workers: 1,
shuffle: true,
augmentation: AugmentationConfig {
enabled: false,
..Default::default()
},
preprocessing: PreprocessingConfig {
grayscale: true,
target_size: (64, 64),
..Default::default()
},
},
training: TrainingHyperparams {
num_epochs: 2,
early_stopping_patience: 1,
..Default::default()
},
optimizer: OptimizerConfig {
optimizer_type: "Adam".to_string(),
learning_rate: 1e-2,
parameters: HashMap::new(),
},
scheduler: SchedulerConfig {
scheduler_type: "StepLR".to_string(),
parameters: HashMap::new(),
},
logging: LoggingConfig {
log_level: "debug".to_string(),
log_dir: "./test_logs".to_string(),
console_logging: true,
file_logging: false,
log_interval: 1,
metrics: vec!["loss".to_string()],
},
checkpoint: CheckpointConfig {
checkpoint_dir: "./test_checkpoints".to_string(),
save_interval: 1,
max_checkpoints: 2,
save_best: false,
best_metric: "loss".to_string(),
save_optimizer: false,
save_scheduler: false,
},
hardware: HardwareConfig {
device: "CPU".to_string(),
num_gpus: 0,
gpu_memory_fraction: 0.8,
use_mixed_precision: false,
num_threads: 1,
},
}
}
}