chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
name: pytorch-hpo-example
|
||||
|
||||
python_env: python_env.yaml
|
||||
|
||||
entry_points:
|
||||
main:
|
||||
parameters:
|
||||
n_trials: {type: int, default: 10}
|
||||
max_epochs: {type: int, default: 5}
|
||||
batch_size: {type: int, default: 64}
|
||||
command: "python hpo_mnist.py --n-trials {n_trials} --max-epochs {max_epochs} --batch-size {batch_size}"
|
||||
@@ -0,0 +1,68 @@
|
||||
# PyTorch Hyperparameter Optimization Example
|
||||
|
||||
This example demonstrates hyperparameter optimization with MLflow tracking using pure PyTorch (no Lightning dependencies).
|
||||
|
||||
## What it demonstrates
|
||||
|
||||
- **MLflow nested runs**: Parent run tracks the overall HPO experiment, child runs track individual trials
|
||||
- **Hyperparameter tuning**: Uses Optuna to optimize learning rate, hidden layer size, dropout rate, and batch size
|
||||
- **Pure PyTorch**: Simple, clean implementation without framework overhead
|
||||
- **Fast training**: MNIST classification completes quickly for rapid iteration
|
||||
|
||||
## Architecture
|
||||
|
||||
The model is a simple 2-layer neural network:
|
||||
|
||||
```
|
||||
Input (784) → FC1 (hidden_size) → ReLU → Dropout → FC2 (10) → LogSoftmax
|
||||
```
|
||||
|
||||
## Hyperparameters optimized
|
||||
|
||||
- `lr`: Learning rate (1e-4 to 1e-1, log scale)
|
||||
- `hidden_size`: Hidden layer size (64 to 512, step 64)
|
||||
- `dropout_rate`: Dropout probability (0.1 to 0.5)
|
||||
- `batch_size`: Batch size (32, 64, or 128)
|
||||
|
||||
## Running the example
|
||||
|
||||
### Quick test (3 trials, 3 epochs each)
|
||||
|
||||
```bash
|
||||
python hpo_mnist.py --n-trials 3 --max-epochs 3
|
||||
```
|
||||
|
||||
### Full optimization (10 trials, 5 epochs each)
|
||||
|
||||
```bash
|
||||
python hpo_mnist.py --n-trials 10 --max-epochs 5
|
||||
```
|
||||
|
||||
### Using MLflow projects
|
||||
|
||||
```bash
|
||||
mlflow run . -P n_trials=5 -P max_epochs=3
|
||||
```
|
||||
|
||||
## Viewing results
|
||||
|
||||
After running, view the results in MLflow UI:
|
||||
|
||||
```bash
|
||||
mlflow server
|
||||
```
|
||||
|
||||
Navigate to http://localhost:5000 to see:
|
||||
|
||||
- Parent run with overall HPO results
|
||||
- Child runs for each trial with their hyperparameters and metrics
|
||||
- Comparison view to analyze which hyperparameters work best
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `torch>=2.1`: PyTorch for model training
|
||||
- `torchvision>=0.15.1`: MNIST dataset
|
||||
- `optuna>=3.0.0`: Hyperparameter optimization framework
|
||||
- `mlflow`: Experiment tracking
|
||||
|
||||
**No Lightning, no torchmetrics, no transformers** = no dependency conflicts! 🎉
|
||||
@@ -0,0 +1,10 @@
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- python=3.10
|
||||
- pip
|
||||
- pip:
|
||||
- mlflow
|
||||
- torch>=2.1
|
||||
- torchvision>=0.15.1
|
||||
- optuna>=3.0.0
|
||||
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Hyperparameter Optimization Example with Pure PyTorch and MLflow
|
||||
|
||||
This example demonstrates:
|
||||
- Using MLflow to track hyperparameter optimization trials
|
||||
- Parent/child run structure for organizing HPO experiments
|
||||
- Pure PyTorch training (no Lightning dependencies)
|
||||
- Simple MNIST classification with configurable hyperparameters
|
||||
|
||||
Run with: python hpo_mnist.py --n-trials 5 --max-epochs 3
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
||||
import optuna
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
from torch.utils.data import DataLoader
|
||||
from torchvision import datasets, transforms
|
||||
|
||||
import mlflow
|
||||
|
||||
|
||||
class SimpleNet(nn.Module):
|
||||
def __init__(self, hidden_size, dropout_rate):
|
||||
super().__init__()
|
||||
self.fc1 = nn.Linear(784, hidden_size)
|
||||
self.dropout = nn.Dropout(dropout_rate)
|
||||
self.fc2 = nn.Linear(hidden_size, 10)
|
||||
|
||||
def forward(self, x):
|
||||
x = x.view(-1, 784)
|
||||
x = F.relu(self.fc1(x))
|
||||
x = self.dropout(x)
|
||||
x = self.fc2(x)
|
||||
return F.log_softmax(x, dim=1)
|
||||
|
||||
|
||||
def train_epoch(model, device, train_loader, optimizer):
|
||||
model.train()
|
||||
for data, target in train_loader:
|
||||
data = data.to(device)
|
||||
target = target.to(device)
|
||||
optimizer.zero_grad()
|
||||
output = model(data)
|
||||
loss = F.nll_loss(output, target)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
|
||||
def evaluate(model, device, test_loader):
|
||||
model.eval()
|
||||
test_loss = 0
|
||||
correct = 0
|
||||
with torch.no_grad():
|
||||
for data, target in test_loader:
|
||||
data = data.to(device)
|
||||
target = target.to(device)
|
||||
output = model(data)
|
||||
test_loss += F.nll_loss(output, target, reduction="sum").item()
|
||||
pred = output.argmax(dim=1, keepdim=True)
|
||||
correct += pred.eq(target.view_as(pred)).sum().item()
|
||||
|
||||
test_loss /= len(test_loader.dataset)
|
||||
accuracy = correct / len(test_loader.dataset)
|
||||
return test_loss, accuracy
|
||||
|
||||
|
||||
def objective(trial, args, train_loader, test_loader, device):
|
||||
# Suggest hyperparameters
|
||||
lr = trial.suggest_float("lr", 1e-4, 1e-1, log=True)
|
||||
hidden_size = trial.suggest_int("hidden_size", 64, 512, step=64)
|
||||
dropout_rate = trial.suggest_float("dropout_rate", 0.1, 0.5)
|
||||
batch_size = trial.suggest_categorical("batch_size", [32, 64, 128])
|
||||
|
||||
# Recreate data loaders with new batch size
|
||||
train_loader = DataLoader(train_loader.dataset, batch_size=batch_size, shuffle=True)
|
||||
test_loader = DataLoader(test_loader.dataset, batch_size=batch_size, shuffle=False)
|
||||
|
||||
# Start nested MLflow run for this trial
|
||||
with mlflow.start_run(nested=True, run_name=f"trial_{trial.number}"):
|
||||
# Log hyperparameters
|
||||
mlflow.log_params({
|
||||
"lr": lr,
|
||||
"hidden_size": hidden_size,
|
||||
"dropout_rate": dropout_rate,
|
||||
"batch_size": batch_size,
|
||||
})
|
||||
|
||||
# Create model and optimizer
|
||||
model = SimpleNet(hidden_size, dropout_rate).to(device)
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
# Training loop
|
||||
for epoch in range(args.max_epochs):
|
||||
train_epoch(model, device, train_loader, optimizer)
|
||||
test_loss, accuracy = evaluate(model, device, test_loader)
|
||||
|
||||
# Log metrics for each epoch
|
||||
mlflow.log_metrics({"test_loss": test_loss, "accuracy": accuracy}, step=epoch)
|
||||
|
||||
# Return final accuracy for optimization
|
||||
return accuracy
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--n-trials", type=int, default=10, help="Number of HPO trials")
|
||||
parser.add_argument("--max-epochs", type=int, default=5, help="Epochs per trial")
|
||||
parser.add_argument("--batch-size", type=int, default=64, help="Initial batch size")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup device
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
# Load MNIST data
|
||||
transform = transforms.Compose([
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize((0.1307,), (0.3081,)),
|
||||
])
|
||||
|
||||
train_dataset = datasets.MNIST("./data", train=True, download=True, transform=transform)
|
||||
test_dataset = datasets.MNIST("./data", train=False, transform=transform)
|
||||
|
||||
train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True)
|
||||
test_loader = DataLoader(test_dataset, batch_size=args.batch_size, shuffle=False)
|
||||
|
||||
# Start parent MLflow run
|
||||
with mlflow.start_run(run_name="HPO_Parent"):
|
||||
mlflow.log_params({"n_trials": args.n_trials, "max_epochs": args.max_epochs})
|
||||
|
||||
# Create Optuna study
|
||||
study = optuna.create_study(direction="maximize", study_name="mnist_hpo")
|
||||
|
||||
# Run optimization
|
||||
study.optimize(
|
||||
lambda trial: objective(trial, args, train_loader, test_loader, device),
|
||||
n_trials=args.n_trials,
|
||||
)
|
||||
|
||||
# Log best results to parent run
|
||||
mlflow.log_metrics({
|
||||
"best_accuracy": study.best_value,
|
||||
"best_trial": study.best_trial.number,
|
||||
})
|
||||
# Log best hyperparameters with 'best_' prefix to avoid conflicts
|
||||
best_params = {f"best_{k}": v for k, v in study.best_params.items()}
|
||||
mlflow.log_params(best_params)
|
||||
|
||||
print(f"\nBest trial: {study.best_trial.number}")
|
||||
print(f"Best accuracy: {study.best_value:.4f}")
|
||||
print(f"Best params: {study.best_params}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,7 @@
|
||||
build_dependencies:
|
||||
- pip
|
||||
dependencies:
|
||||
- mlflow
|
||||
- torch>=2.1
|
||||
- torchvision>=0.15.1
|
||||
- optuna>=3.0.0
|
||||
Reference in New Issue
Block a user