Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions examples/machine-learning_ClassicAI/cpu-hpo-optuna/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Hyperparameter Tuning & Serving (Optuna + Ray Tune)

This template implements an end-to-end **Auto-ML workflow** on a **Python Server**. It automates the lifecycle of a machine learning model, combining the intelligent search of Optuna with the scalable execution of Ray Tune.

**Infrastructure:** [Saturn Cloud](https://saturncloud.io/)
**Resource:** Python Server
**Hardware:** CPU
**Tech Stack:** Optuna, Ray Tune, FastAPI, Scikit-Learn

---

## 📖 Overview

Standard hyperparameter tuning scripts often stop at printing the best parameters. This template goes further by **operationalizing** the result. It solves two key problems:

1. **Efficient Search:** It uses **Optuna's** Tree-Parzen Estimator (TPE) algorithm to intelligently select hyperparameters, wrapped in **Ray Tune** to run multiple trials in parallel.
2. **Instant Deployment:** The workflow automatically retrains the model with the best parameters, saves the artifact, and serves it via a production-ready **FastAPI** server.

---

## 🚀 Quick Start

### 1. Environment Setup
Run the setup script to create a virtual environment and install all dependencies (Ray, Optuna, FastAPI, Uvicorn, Scikit-Learn).
```bash
# 1. Make executable
chmod +x setup.sh

# 2. Run setup
bash setup.sh

```

### 2. Run the Tuning Job (Batch)

Execute the tuning script. This will:

* Launch 20 concurrent trials using Ray Tune.
* Identify the best configuration (e.g., `n_estimators`, `max_depth`).
* **Retrain** the model on the full dataset using those winning parameters.
* **Save** the final model to `best_model.pkl`.

```bash
# Activate environment
source venv/bin/activate

# Start tuning
python tune_hpo.py

```

### 3. Start the API Server

Once `tune_hpo.py` finishes and generates `best_model.pkl`, start the server to accept real-time requests.

```bash
python app.py

```

---

## 🧠 Architecture: "Tune & Serve"

The workflow consists of two distinct stages designed to bridge the gap between experimentation and production.

### Stage 1: Optimization (`tune_hpo.py`)

* **Search Algorithm:** We use `OptunaSearch`, which leverages Bayesian optimization to learn from previous trials and find optimal parameters faster.
* **Execution Engine:** Ray Tune manages the resources. It uses a `ConcurrencyLimiter` to run 4 trials simultaneously on the CPU, significantly reducing total wait time.

### Stage 2: Inference (`app.py`)

* **Loader:** On startup, the API loads the optimized `best_model.pkl` artifact.
* **Endpoint:** Exposes a `/predict` route that accepts Iris flower features and returns the classified species (Setosa, Versicolor, or Virginica).

---

## 🧪 Testing

You can test the API using the built-in Swagger UI or via the terminal.

### Method 1: Web Interface

Visit `http://localhost:8000/docs`. Click **POST /predict** -> **Try it out**.

**Test Case A: Setosa (Small Petals)**
Paste this JSON:

```json
{
"sepal_length": 5.1, "sepal_width": 3.5,
"petal_length": 1.4, "petal_width": 0.2
}

```

*Expected Result:* `{"class_name": "setosa"}`

**Test Case B: Virginica (Large Petals)**
Paste this JSON:

```json
{
"sepal_length": 6.5, "sepal_width": 3.0,
"petal_length": 5.2, "petal_width": 2.0
}

```

*Expected Result:* `{"class_name": "virginica"}`

### Method 2: Terminal (CURL)

Run this command in a new terminal window to test the Virginica prediction:

```bash
curl -X 'POST' \
'http://localhost:8000/predict' \
-H 'Content-Type: application/json' \
-d '{
"sepal_length": 6.5,
"sepal_width": 3.0,
"petal_length": 5.2,
"petal_width": 2.0
}'

```

---

## 🏁 Conclusion

This template demonstrates a "Best of Both Worlds" approach: using Optuna for search intelligence and Ray Tune for scaling. By automating the retraining and serving steps, you create a pipeline where model improvements can be deployed rapidly.

To scale the tuning phase—running hundreds of parallel trials across a distributed cluster of machines—consider deploying this workflow on [Saturn Cloud](https://saturncloud.io/).

```

```
55 changes: 55 additions & 0 deletions examples/machine-learning_ClassicAI/cpu-hpo-optuna/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import numpy as np
import os

app = FastAPI(title="Auto-Tuned Iris API")

# Define Input Schema
class IrisData(BaseModel):
sepal_length: float
sepal_width: float
petal_length: float
petal_width: float

# Global Model Variable
model = None
MODEL_PATH = "best_model.pkl"

@app.on_event("startup")
def load_model():
global model
if os.path.exists(MODEL_PATH):
model = joblib.load(MODEL_PATH)
print(f"✅ Loaded optimized model: {MODEL_PATH}")
else:
print(f"⚠️ Error: {MODEL_PATH} not found. Run 'python tune_hpo.py' first.")

@app.post("/predict")
def predict(data: IrisData):
if not model:
return {"error": "Model not loaded"}

# Prepare features
features = np.array([[
data.sepal_length,
data.sepal_width,
data.petal_length,
data.petal_width
]])

# Predict
prediction = int(model.predict(features)[0])

# Map to Class Name
classes = {0: "setosa", 1: "versicolor", 2: "virginica"}

return {
"class_id": prediction,
"class_name": classes.get(prediction, "unknown")
}

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
35 changes: 35 additions & 0 deletions examples/machine-learning_ClassicAI/cpu-hpo-optuna/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash
set -e

GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${GREEN}🚀 Starting Auto-ML Setup...${NC}"

# 1. Robust Python Detection
if command -v python3 &> /dev/null; then
PY_CMD="python3"
elif command -v python &> /dev/null; then
PY_CMD="python"
else
echo "❌ Error: Could not find 'python3' or 'python' in your PATH."
exit 1
fi

# 2. Create Virtual Environment
echo -e "${BLUE}📦 Creating Virtual Environment 'venv'...${NC}"
$PY_CMD -m venv venv

# 3. Install Dependencies
echo -e "${BLUE}⬇️ Installing libraries...${NC}"
. venv/bin/activate
pip install --upgrade pip
# Core stack: Ray Tune (HPO), Optuna (Search), FastAPI (Serving), Scikit-Learn (Model)
pip install "ray[tune]" "optuna>=3.0.0" scikit-learn pandas numpy fastapi uvicorn joblib

echo -e "${GREEN}✅ Environment Ready!${NC}"
echo "-------------------------------------------------------"
echo "1. Tune & Save Model: python tune_hpo.py"
echo "2. Serve Model: python app.py"
echo "-------------------------------------------------------"
86 changes: 86 additions & 0 deletions examples/machine-learning_ClassicAI/cpu-hpo-optuna/tune_hpo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import time
import joblib
import ray
from ray import tune
from ray.tune.search import ConcurrencyLimiter
from ray.tune.search.optuna import OptunaSearch
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# 1. Define Objective (The "Black Box" function)
def objective(config):
data = load_iris()
X, y = data.data, data.target

# Initialize model with current trial's hyperparameters
clf = RandomForestClassifier(
n_estimators=int(config["n_estimators"]),
max_depth=int(config["max_depth"]),
min_samples_split=float(config["min_samples_split"]),
random_state=42
)

# Evaluate performance using Cross-Validation
scores = cross_val_score(clf, X, y, cv=3)
accuracy = scores.mean()

# Report metric to Ray Tune
tune.report({"accuracy": accuracy})

def run_hpo():
print("🧠 Initializing Ray...")
ray.init(configure_logging=False)

# 2. Define Search Space
search_space = {
"n_estimators": tune.randint(10, 200),
"max_depth": tune.randint(2, 20),
"min_samples_split": tune.uniform(0.1, 1.0)
}

# 3. Setup Optuna Search Algorithm
algo = OptunaSearch()
algo = ConcurrencyLimiter(algo, max_concurrent=4)

print("🚀 Starting Tuning Job...")
tuner = tune.Tuner(
objective,
tune_config=tune.TuneConfig(
metric="accuracy",
mode="max",
search_alg=algo,
num_samples=20,
),
param_space=search_space,
)

results = tuner.fit()

# 4. Process Best Result
best_result = results.get_best_result("accuracy", "max")
best_config = best_result.config

print("\n" + "="*50)
print(f"🏆 Best Accuracy: {best_result.metrics['accuracy']:.4f}")
print(f"🔧 Best Config: {best_config}")
print("="*50)

# 5. Retrain & Save Best Model
print("💾 Retraining final model with best parameters...")
data = load_iris()
X, y = data.data, data.target

final_model = RandomForestClassifier(
n_estimators=int(best_config["n_estimators"]),
max_depth=int(best_config["max_depth"]),
min_samples_split=float(best_config["min_samples_split"]),
random_state=42
)
final_model.fit(X, y)

joblib.dump(final_model, "best_model.pkl")
print("✅ Model saved to 'best_model.pkl'")

if __name__ == "__main__":
run_hpo()
Loading
Loading