Latest Post

Reinforcement Learning for Credit Scoring: Applications in Fintech

Here’s something that’ll blow your mind: the way fintech companies decide whether to lend you money is getting a serious upgrade. And I’m not talking about minor tweaks to old formulas — I’m talking about reinforcement learning algorithms that literally learn from every lending decision they make.

Hydra Configuration Management: Organize Your ML Experiments Efficiently

Your training script has 47 command-line arguments. You’re managing experiments by creating files like train_v7_lr001_batch32_final_ACTUALLY_FINAL_v2.py. You've got hardcoded hyperparameters scattered across three different Python files. When someone asks "what hyperparameters produced that 94% accuracy model from last week?", you have no idea because you forgot to log them. This is ML experiment hell, and you're living in it.

I spent two years in this chaos before discovering Hydra. It’s a configuration management framework from Facebook Research that transforms experiment organization from nightmare to actually manageable. No more hardcoded values, no more command-line argument spaghetti, no more “what hyperparameters did I use?” mysteries. Just clean, composable, reproducible configuration. Let me show you how to escape ML configuration hell.

Hydra Configuration Management

What Is Hydra and Why You Need It

Hydra is a framework for managing complex application configurations. For ML, this means organizing hyperparameters, dataset paths, model architectures, and training settings in a way that doesn’t make you want to quit programming.

What Hydra gives you:

  • Hierarchical configuration files (YAML)
  • Configuration composition and overrides
  • Automatic logging of all config
  • Multi-run support (hyperparameter sweeps)
  • Working directory management
  • Type checking for configs

What problem it solves:

  • Hardcoded hyperparameters everywhere
  • Command-line argument explosion
  • Non-reproducible experiments
  • Difficult hyperparameter sweeps
  • Config sprawl across multiple files

Think of Hydra as the difference between managing configs with argparse and having an actual configuration system. It's infrastructure for experiments that actually scales.

Installation and Basic Setup

Getting started is simple:

bash

pip install hydra-core

That’s it. Now let’s see the magic.

Before Hydra: The Nightmare

Here’s typical ML code before Hydra:

python

# train.py - THE WRONG WAY
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--lr', type=float, default=0.001)
parser.add_argument('--batch_size', type=int, default=32)
parser.add_argument('--epochs', type=int, default=10)
parser.add_argument('--model', type=str, default='resnet18')
parser.add_argument('--optimizer', type=str, default='adam')
parser.add_argument('--data_path', type=str, default='/data')
parser.add_argument('--output_dir', type=str, default='./outputs')
# ... 40 more arguments
args = parser.parse_args()
# Somewhere else in the code
HIDDEN_DIM = 256 # Hardcoded!
DROPOUT = 0.5 # Also hardcoded!
# Training code that uses args.lr, args.batch_size, etc.

This is terrible. Arguments everywhere, some hardcoded, no structure, impossible to reproduce.

After Hydra: Configuration Sanity

Here’s the same thing with Hydra:

Config File (config.yaml)

yaml

# config/config.yaml
model:
name: resnet18
hidden_dim: 256
dropout: 0.5
training:
lr: 0.001
batch_size: 32
epochs: 10
optimizer: adam
data:
path: /data
augmentation: true
output_dir: ./outputs

Training Script

python

# train.py - THE RIGHT WAY
import hydra
from omegaconf import DictConfig, OmegaConf
@hydra.main(version_base=None, config_path="config", config_name="config")
def train(cfg: DictConfig):
# Access config values
print(f"Learning rate: {cfg.training.lr}")
print(f"Batch size: {cfg.training.batch_size}")
print(f"Model: {cfg.model.name}")

# Config is automatically logged
print(OmegaConf.to_yaml(cfg))

# Your training code here
model = create_model(cfg.model)
train_model(model, cfg.training)
if __name__ == "__main__":
train()

Run it:

bash

python train.py

Override values:

bash

python train.py training.lr=0.01 training.batch_size=64

That’s it. Clean, organized, reproducible. Every experiment automatically logs its full configuration.

Hierarchical Configuration (Config Groups)

Hydra’s real power comes from composing configurations:

Directory Structure

config/
├── config.yaml
├── model/
│ ├── resnet18.yaml
│ ├── resnet50.yaml
│ └── efficientnet.yaml
├── optimizer/
│ ├── adam.yaml
│ ├── sgd.yaml
│ └── adamw.yaml
└── data/
├── cifar10.yaml
├── imagenet.yaml
└── custom.yaml

Model Configs

yaml

# config/model/resnet18.yaml
name: resnet18
hidden_dim: 256
dropout: 0.5
pretrained: true

yaml

# config/model/resnet50.yaml
name: resnet50
hidden_dim: 512
dropout: 0.3
pretrained: true

Optimizer Configs

yaml

# config/optimizer/adam.yaml
name: adam
lr: 0.001
weight_decay: 0.0001
betas: [0.9, 0.999]

yaml

# config/optimizer/sgd.yaml
name: sgd
lr: 0.01
momentum: 0.9
weight_decay: 0.0001

Main Config (Composition)

yaml

# config/config.yaml
defaults:
- model: resnet18
- optimizer: adam
- data: cifar10
training:
batch_size: 32
epochs: 10
output_dir: ./outputs

Now you can swap entire configurations:

bash

# Use ResNet50 instead of ResNet18
python train.py model=resnet50
# Use SGD instead of Adam
python train.py optimizer=sgd
# Change both
python train.py model=efficientnet optimizer=adamw
# Override specific values
python train.py model=resnet50 optimizer.lr=0.01 training.batch_size=64

This is game-changing. You’re not managing 50 command-line arguments — you’re composing configurations.

Upscaler: Increase image resolution and improve quality : Click Here

Multi-Run: Hyperparameter Sweeps

Hydra makes hyperparameter sweeps trivial:

bash

# Sweep over learning rates
python train.py --multirun optimizer.lr=0.001,0.01,0.1
# Sweep multiple parameters
python train.py --multirun optimizer.lr=0.001,0.01 training.batch_size=32,64,128
# Grid search
python train.py --multirun model=resnet18,resnet50 optimizer=adam,sgd

Hydra runs each combination automatically, organizing outputs in separate directories. No more writing custom sweep scripts.

Structured Configs (Type Safety)

Want type checking for your configs? Use structured configs:

python

# config.py
from dataclasses import dataclass
from hydra.core.config_store import ConfigStore
@dataclass
class ModelConfig:
name: str = "resnet18"
hidden_dim: int = 256
dropout: float = 0.5
@dataclass
class TrainingConfig:
lr: float = 0.001
batch_size: int = 32
epochs: int = 10
@dataclass
class Config:
model: ModelConfig = ModelConfig()
training: TrainingConfig = TrainingConfig()
# Register configs
cs = ConfigStore.instance()
cs.store(name="config", node=Config)

python

# train.py
import hydra
from omegaconf import DictConfig
from config import Config
@hydra.main(version_base=None, config_name="config")
def train(cfg: Config):
# Type hints work!
lr: float = cfg.training.lr
batch_size: int = cfg.training.batch_size

# Your training code
pass
if __name__ == "__main__":
train()

Now you get:

  • IDE autocomplete
  • Type checking
  • Runtime validation
  • Better documentation

IMO, structured configs should be default for any serious project. The type safety prevents stupid bugs.

Working Directory Management

Hydra automatically creates output directories:

python

@hydra.main(version_base=None, config_path="config", config_name="config")
def train(cfg: DictConfig):
# Hydra changes working directory to outputs/YYYY-MM-DD/HH-MM-SS/

# Save model
torch.save(model.state_dict(), 'model.pt') # Saved in experiment dir

# Save logs
with open('training.log', 'w') as f:
f.write(logs)

# Config is automatically saved to .hydra/config.yaml

# Access original working directory
original_cwd = hydra.utils.get_original_cwd()

Every experiment gets its own directory with:

  • Full configuration (.hydra/config.yaml)
  • Overrides (.hydra/overrides.yaml)
  • Your outputs (models, logs, etc.)

No more manually creating timestamped directories or losing track of experiment outputs.

Real-World Example: Complete Training Script

Here’s a production-ready example:

Config Structure

yaml

# config/config.yaml
defaults:
- model: resnet18
- optimizer: adam
- _self_
data:
path: ./data
num_workers: 4
pin_memory: true
training:
batch_size: 32
epochs: 10
device: cuda
seed: 42
logging:
wandb: true
project: my_project
log_every: 10
checkpoint:
save_every: 5
keep_last: 3

Training Script

python

# train.py
import hydra
from omegaconf import DictConfig, OmegaConf
import torch
import wandb
from pathlib import Path
@hydra.main(version_base=None, config_path="config", config_name="config")
def train(cfg: DictConfig):
# Set seed for reproducibility
torch.manual_seed(cfg.training.seed)

# Log config
print(OmegaConf.to_yaml(cfg))

# Initialize wandb
if cfg.logging.wandb:
wandb.init(
project=cfg.logging.project,
config=OmegaConf.to_container(cfg, resolve=True)
)

# Create model
model = create_model(cfg.model)
model = model.to(cfg.training.device)

# Create optimizer
optimizer = create_optimizer(cfg.optimizer, model.parameters())

# Create dataloaders
train_loader, val_loader = create_dataloaders(cfg)

# Training loop
for epoch in range(cfg.training.epochs):
# Train
train_loss = train_epoch(model, train_loader, optimizer, cfg)

# Validate
val_loss = validate(model, val_loader, cfg)

# Log
if cfg.logging.wandb:
wandb.log({
'train_loss': train_loss,
'val_loss': val_loss,
'epoch': epoch
})

# Save checkpoint
if (epoch + 1) % cfg.checkpoint.save_every == 0:
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'train_loss': train_loss,
'val_loss': val_loss
}, f'checkpoint_epoch_{epoch}.pt')

# Save final model
torch.save(model.state_dict(), 'final_model.pt')

print(f"Training complete! Outputs in: {Path.cwd()}")
def create_model(model_cfg):
# Model creation logic using model_cfg
pass
def create_optimizer(opt_cfg, parameters):
# Optimizer creation logic using opt_cfg
pass
def create_dataloaders(cfg):
# Dataloader creation using cfg.data
pass
def train_epoch(model, loader, optimizer, cfg):
# Training logic
pass
def validate(model, loader, cfg):
# Validation logic
pass
if __name__ == "__main__":
train()

Run experiments:

bash

# Default config
python train.py
# Change model
python train.py model=resnet50
# Change multiple settings
python train.py model=efficientnet optimizer.lr=0.01 training.batch_size=64
# Hyperparameter sweep
python train.py --multirun optimizer.lr=0.001,0.01,0.1 training.batch_size=32,64

Every experiment automatically gets:

  • Unique output directory
  • Full configuration saved
  • All outputs organized
  • Reproducible settings

Integrating with Other Tools

Hydra works great with existing tools:

With Weights & Biases

python

@hydra.main(version_base=None, config_path="config", config_name="config")
def train(cfg: DictConfig):
wandb.init(
project=cfg.project,
config=OmegaConf.to_container(cfg, resolve=True) # Log full config
)
# Training code

With PyTorch Lightning

python

import pytorch_lightning as pl
@hydra.main(version_base=None, config_path="config", config_name="config")
def train(cfg: DictConfig):
model = MyLightningModule(cfg.model)
trainer = pl.Trainer(
max_epochs=cfg.training.epochs,
gpus=cfg.training.gpus
)
trainer.fit(model)

With Optuna (Hyperparameter Optimization)

python

import optuna
@hydra.main(version_base=None, config_path="config", config_name="config")
def optimize(cfg: DictConfig):
def objective(trial):
# Suggest hyperparameters
lr = trial.suggest_loguniform('lr', 1e-5, 1e-1)
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64])

# Override config
cfg.optimizer.lr = lr
cfg.training.batch_size = batch_size

# Train and return metric
return train_and_evaluate(cfg)

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

Common Patterns and Best Practices

Pattern 1: Environment-Specific Configs

yaml

# config/config.yaml
defaults:
- model: resnet18
- env: local # or 'cluster', 'cloud'
# config/env/local.yaml
data:
path: ./data
num_workers: 2
device: cpu
# config/env/cluster.yaml
data:
path: /shared/data
num_workers: 16
device: cuda

Switch environments easily:

bash

python train.py env=cluster

Pattern 2: Experiment Configs

yaml

# config/experiment/baseline.yaml
# @package _global_
defaults:
- override /model: resnet18
- override /optimizer: adam
training:
lr: 0.001
batch_size: 32
# config/experiment/large_model.yaml
# @package _global_
defaults:
- override /model: resnet50
- override /optimizer: adamw
training:
lr: 0.0001
batch_size: 64

Run experiments by name:

bash

python train.py +experiment=baseline
python train.py +experiment=large_model

Pattern 3: Debugging Config

yaml

# config/debug.yaml
defaults:
- config
training:
epochs: 2
batch_size: 4
data:
num_workers: 0 # Single-threaded for debugging

logging:
log_every: 1

Quick debugging runs:

bash

python train.py --config-name=debug

Common Mistakes to Avoid

Learn from these Hydra failures:

Mistake 1: Modifying Config Objects

python

# Bad - modifies config in-place
cfg.training.lr = 0.01
# Good - create new config or use OmegaConf
from omegaconf import OmegaConf
cfg = OmegaConf.merge(cfg, {"training": {"lr": 0.01}})

Configs should be immutable after loading.

Mistake 2: Not Using Config Groups

python

# Bad - one giant config file
# config.yaml with 500 lines
# Good - organized groups
# config/model/*.yaml
# config/optimizer/*.yaml
# etc.

Config groups make things composable and maintainable.

Mistake 3: Hardcoding Paths

python

# Bad
data_path = "/home/user/data" # Hardcoded!
# Good
data_path = cfg.data.path # From config

Everything should be configurable, especially paths. FYI, hardcoded paths break when you move to different environments.

Mistake 4: Not Logging Configs

python

# Bad - no config tracking
train(cfg)
# Good - log config to wandb/mlflow
wandb.init(config=OmegaConf.to_container(cfg))

Always log your full configuration. Future you will thank present you.

The Bottom Line

Hydra transforms ML experiment management from chaos to organized, reproducible science. It’s not just about cleaner code — it’s about being able to reproduce results, sweep hyperparameters efficiently, and actually know what settings produced what results.

Use Hydra when:

  • Running multiple experiments
  • Need reproducibility
  • Hyperparameter sweeps are common
  • Working in a team
  • Configs are getting complex

Skip Hydra when:

  • One-off scripts
  • Configs are trivial
  • Learning ML basics (focus on fundamentals first)

For serious ML work, Hydra should be in your stack from day one. The small upfront investment pays off massively when you’re running your 50th experiment and can actually reproduce results from experiment #3.

Installation:

bash

pip install hydra-core

Stop managing configs with command-line arguments and hardcoded values. Start using Hydra. Your experiments will be cleaner, more reproducible, and actually manageable. The difference between “I think these were the hyperparameters” and “here’s the exact config that produced that result” is the difference between amateur hour and professional ML engineering. :)

Comments