546 lines
14 KiB
Markdown
546 lines
14 KiB
Markdown
|
|
# Grid Search API
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
The Grid Search module provides exhaustive parameter space exploration by evaluating the objective function at all points on a regular grid. While computationally expensive, it guarantees finding the best solution within the discretized search space.
|
|||
|
|
|
|||
|
|
## Function: `grid_search`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Signature
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
grid_search(
|
|||
|
|
objective_fn: Callable[[np.ndarray], float],
|
|||
|
|
bounds: List[Tuple[float, float]],
|
|||
|
|
n_points: int = 10,
|
|||
|
|
) -> Tuple[np.ndarray, float]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Parameters
|
|||
|
|
|
|||
|
|
- **`objective_fn`** (callable): Function to **maximize**.
|
|||
|
|
- **Signature**: `objective_fn(x: np.ndarray) -> float`
|
|||
|
|
- Takes a 1D array of parameters and returns a scalar objective value.
|
|||
|
|
- **Higher values are better** (maximization).
|
|||
|
|
|
|||
|
|
- **`bounds`** (List[Tuple[float, float]]): List of (min, max) bounds for each parameter dimension.
|
|||
|
|
|
|||
|
|
- **`n_points`** (int, optional): Number of equally spaced grid points per dimension. Default is 10.
|
|||
|
|
|
|||
|
|
### Returns
|
|||
|
|
|
|||
|
|
Returns a tuple `(x, fun)`:
|
|||
|
|
- **`x`** (np.ndarray): Best parameters found (maximum).
|
|||
|
|
- **`fun`** (float): Best objective value (maximum).
|
|||
|
|
|
|||
|
|
Alternatively, when using the Rust backend directly, returns a `GridSearchResult` object with attributes:
|
|||
|
|
- `x`: Best parameters
|
|||
|
|
- `fun`: Best objective value
|
|||
|
|
- `nfev`: Number of function evaluations (= `n_points^n_params`)
|
|||
|
|
|
|||
|
|
### Complexity
|
|||
|
|
|
|||
|
|
- **Time**: O(`n_points`^`n_params` × cost_per_eval)
|
|||
|
|
- **Space**: O(`n_points`^`n_params`)
|
|||
|
|
|
|||
|
|
Exponential in the number of parameters!
|
|||
|
|
|
|||
|
|
## Basic Example
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# Simple quadratic function with maximum at (0, 0)
|
|||
|
|
def objective(x):
|
|||
|
|
return -(x[0]**2 + x[1]**2)
|
|||
|
|
|
|||
|
|
# Find maximum
|
|||
|
|
x_opt, f_max = grid_search(
|
|||
|
|
objective_fn=objective,
|
|||
|
|
bounds=[(-5, 5), (-5, 5)],
|
|||
|
|
n_points=50
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"Optimal point: ({x_opt[0]:.3f}, {x_opt[1]:.3f})")
|
|||
|
|
print(f"Maximum value: {f_max:.6f}")
|
|||
|
|
print(f"Total evaluations: {50**2}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Advanced Examples
|
|||
|
|
|
|||
|
|
### 1. Hyperparameter Tuning
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from sklearn.model_selection import cross_val_score
|
|||
|
|
from sklearn.ensemble import RandomForestClassifier
|
|||
|
|
from sklearn.datasets import load_iris
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# Load data
|
|||
|
|
X, y = load_iris(return_X_y=True)
|
|||
|
|
|
|||
|
|
def rf_objective(params):
|
|||
|
|
"""Optimize Random Forest hyperparameters"""
|
|||
|
|
n_estimators, max_depth = params
|
|||
|
|
|
|||
|
|
# Convert to integers
|
|||
|
|
n_estimators = int(n_estimators)
|
|||
|
|
max_depth = int(max_depth)
|
|||
|
|
|
|||
|
|
# Cross-validation accuracy
|
|||
|
|
model = RandomForestClassifier(
|
|||
|
|
n_estimators=n_estimators,
|
|||
|
|
max_depth=max_depth,
|
|||
|
|
random_state=42
|
|||
|
|
)
|
|||
|
|
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
|
|||
|
|
|
|||
|
|
return scores.mean()
|
|||
|
|
|
|||
|
|
# Grid search
|
|||
|
|
params_opt, acc_max = grid_search(
|
|||
|
|
objective_fn=rf_objective,
|
|||
|
|
bounds=[(10, 200), (2, 20)], # n_estimators, max_depth
|
|||
|
|
n_points=20
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"Best n_estimators: {int(params_opt[0])}")
|
|||
|
|
print(f"Best max_depth: {int(params_opt[1])}")
|
|||
|
|
print(f"Best CV accuracy: {acc_max:.4f}")
|
|||
|
|
print(f"Total evaluations: {20**2 = 400}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Feature Engineering
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
from sklearn.preprocessing import PolynomialFeatures
|
|||
|
|
from sklearn.linear_model import Ridge
|
|||
|
|
from sklearn.model_selection import cross_val_score
|
|||
|
|
|
|||
|
|
# Generate sample data
|
|||
|
|
np.random.seed(42)
|
|||
|
|
X = np.random.randn(100, 3)
|
|||
|
|
y = 2*X[:, 0] + 3*X[:, 1]**2 - X[:, 2] + np.random.randn(100)*0.1
|
|||
|
|
|
|||
|
|
def feature_objective(params):
|
|||
|
|
"""Optimize polynomial degree and regularization"""
|
|||
|
|
degree, alpha_log = params
|
|||
|
|
degree = int(degree)
|
|||
|
|
alpha = 10 ** alpha_log
|
|||
|
|
|
|||
|
|
# Create polynomial features
|
|||
|
|
poly = PolynomialFeatures(degree=degree, include_bias=False)
|
|||
|
|
X_poly = poly.fit_transform(X)
|
|||
|
|
|
|||
|
|
# Ridge regression with CV
|
|||
|
|
model = Ridge(alpha=alpha)
|
|||
|
|
scores = cross_val_score(model, X_poly, y, cv=5,
|
|||
|
|
scoring='neg_mean_squared_error')
|
|||
|
|
|
|||
|
|
return scores.mean() # Negative MSE (higher is better)
|
|||
|
|
|
|||
|
|
params_opt, score_max = grid_search(
|
|||
|
|
objective_fn=feature_objective,
|
|||
|
|
bounds=[(1, 4), (-3, 2)], # degree, log10(alpha)
|
|||
|
|
n_points=15
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"Best polynomial degree: {int(params_opt[0])}")
|
|||
|
|
print(f"Best alpha: {10**params_opt[1]:.6f}")
|
|||
|
|
print(f"Best CV score: {score_max:.6f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Signal Processing
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from scipy import signal
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# Generate noisy signal
|
|||
|
|
t = np.linspace(0, 1, 1000)
|
|||
|
|
true_signal = np.sin(2 * np.pi * 5 * t)
|
|||
|
|
noisy_signal = true_signal + np.random.normal(0, 0.5, len(t))
|
|||
|
|
|
|||
|
|
def filter_objective(params):
|
|||
|
|
"""Optimize Butterworth filter parameters"""
|
|||
|
|
order, cutoff = params
|
|||
|
|
order = int(order)
|
|||
|
|
|
|||
|
|
# Design and apply filter
|
|||
|
|
b, a = signal.butter(order, cutoff, btype='low', analog=False)
|
|||
|
|
filtered = signal.filtfilt(b, a, noisy_signal)
|
|||
|
|
|
|||
|
|
# Minimize MSE with true signal (negative for maximization)
|
|||
|
|
mse = np.mean((filtered - true_signal)**2)
|
|||
|
|
return -mse
|
|||
|
|
|
|||
|
|
params_opt, neg_mse = grid_search(
|
|||
|
|
objective_fn=filter_objective,
|
|||
|
|
bounds=[(2, 8), (0.05, 0.3)], # order, cutoff frequency
|
|||
|
|
n_points=20
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"Best filter order: {int(params_opt[0])}")
|
|||
|
|
print(f"Best cutoff frequency: {params_opt[1]:.3f}")
|
|||
|
|
print(f"MSE: {-neg_mse:.6f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Economic Optimization
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
def profit_function(params):
|
|||
|
|
"""Maximize profit given price and advertising budget"""
|
|||
|
|
price, advertising = params
|
|||
|
|
|
|||
|
|
# Demand model: q = 1000 - 20*price + 5*sqrt(advertising)
|
|||
|
|
quantity = 1000 - 20*price + 5*np.sqrt(advertising)
|
|||
|
|
quantity = max(0, quantity) # Can't be negative
|
|||
|
|
|
|||
|
|
# Cost model
|
|||
|
|
fixed_cost = 5000
|
|||
|
|
variable_cost = 10 # per unit
|
|||
|
|
total_cost = fixed_cost + variable_cost * quantity + advertising
|
|||
|
|
|
|||
|
|
# Revenue
|
|||
|
|
revenue = price * quantity
|
|||
|
|
|
|||
|
|
# Profit
|
|||
|
|
profit = revenue - total_cost
|
|||
|
|
|
|||
|
|
return profit
|
|||
|
|
|
|||
|
|
params_opt, profit_max = grid_search(
|
|||
|
|
objective_fn=profit_function,
|
|||
|
|
bounds=[(15, 60), (0, 10000)], # price, advertising
|
|||
|
|
n_points=30
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
price_opt, ad_opt = params_opt
|
|||
|
|
quantity_opt = 1000 - 20*price_opt + 5*np.sqrt(ad_opt)
|
|||
|
|
|
|||
|
|
print(f"Optimal price: ${price_opt:.2f}")
|
|||
|
|
print(f"Optimal advertising: ${ad_opt:.2f}")
|
|||
|
|
print(f"Expected quantity: {quantity_opt:.0f} units")
|
|||
|
|
print(f"Maximum profit: ${profit_max:.2f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Portfolio Allocation
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# Historical returns for 3 assets
|
|||
|
|
returns = np.array([
|
|||
|
|
[0.10, 0.12, 0.08], # Expected annual returns
|
|||
|
|
])
|
|||
|
|
cov_matrix = np.array([
|
|||
|
|
[0.04, 0.01, 0.02],
|
|||
|
|
[0.01, 0.09, 0.01],
|
|||
|
|
[0.02, 0.01, 0.03]
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
def portfolio_objective(params):
|
|||
|
|
"""Maximize risk-adjusted return (Sharpe ratio)"""
|
|||
|
|
# Only optimize 2 weights; third is determined
|
|||
|
|
w1, w2 = params
|
|||
|
|
w3 = 1 - w1 - w2
|
|||
|
|
|
|||
|
|
# Invalid if weights are negative
|
|||
|
|
if w3 < 0 or w1 < 0 or w2 < 0:
|
|||
|
|
return -1e10
|
|||
|
|
|
|||
|
|
weights = np.array([w1, w2, w3])
|
|||
|
|
|
|||
|
|
# Portfolio return
|
|||
|
|
port_return = np.sum(returns * weights)
|
|||
|
|
|
|||
|
|
# Portfolio volatility
|
|||
|
|
port_vol = np.sqrt(np.dot(weights, np.dot(cov_matrix, weights)))
|
|||
|
|
|
|||
|
|
# Sharpe ratio (assuming risk-free rate = 0.02)
|
|||
|
|
sharpe = (port_return - 0.02) / port_vol
|
|||
|
|
|
|||
|
|
return sharpe
|
|||
|
|
|
|||
|
|
params_opt, sharpe_max = grid_search(
|
|||
|
|
objective_fn=portfolio_objective,
|
|||
|
|
bounds=[(0, 1), (0, 1)], # weights for assets 1 and 2
|
|||
|
|
n_points=50
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
w1, w2 = params_opt
|
|||
|
|
w3 = 1 - w1 - w2
|
|||
|
|
|
|||
|
|
print(f"Optimal allocation:")
|
|||
|
|
print(f" Asset 1: {w1:.2%}")
|
|||
|
|
print(f" Asset 2: {w2:.2%}")
|
|||
|
|
print(f" Asset 3: {w3:.2%}")
|
|||
|
|
print(f"Sharpe Ratio: {sharpe_max:.3f}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Visualization
|
|||
|
|
|
|||
|
|
### 1D Grid Search
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# 1D function
|
|||
|
|
def func_1d(x):
|
|||
|
|
return -(x[0] - 2)**2 + 5
|
|||
|
|
|
|||
|
|
# Create fine grid for plotting
|
|||
|
|
x_plot = np.linspace(-5, 8, 1000)
|
|||
|
|
y_plot = [func_1d([x]) for x in x_plot]
|
|||
|
|
|
|||
|
|
# Grid search
|
|||
|
|
x_opt, f_max = grid_search(
|
|||
|
|
objective_fn=func_1d,
|
|||
|
|
bounds=[(-5, 8)],
|
|||
|
|
n_points=15
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Plot
|
|||
|
|
plt.figure(figsize=(10, 6))
|
|||
|
|
plt.plot(x_plot, y_plot, 'b-', label='Function', linewidth=2)
|
|||
|
|
|
|||
|
|
# Show grid points
|
|||
|
|
grid_points = np.linspace(-5, 8, 15)
|
|||
|
|
grid_values = [func_1d([x]) for x in grid_points]
|
|||
|
|
plt.scatter(grid_points, grid_values, c='red', s=50,
|
|||
|
|
label='Grid points', zorder=3)
|
|||
|
|
|
|||
|
|
plt.scatter(x_opt[0], f_max, c='green', s=200, marker='*',
|
|||
|
|
label=f'Optimum: ({x_opt[0]:.2f}, {f_max:.2f})', zorder=4)
|
|||
|
|
|
|||
|
|
plt.xlabel('x')
|
|||
|
|
plt.ylabel('f(x)')
|
|||
|
|
plt.title('Grid Search Visualization')
|
|||
|
|
plt.legend()
|
|||
|
|
plt.grid(True, alpha=0.3)
|
|||
|
|
plt.show()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2D Grid Search Heatmap
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import numpy as np
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
# 2D function
|
|||
|
|
def func_2d(x):
|
|||
|
|
return np.exp(-((x[0]-1)**2 + (x[1]+1)**2))
|
|||
|
|
|
|||
|
|
# Create grid for visualization
|
|||
|
|
x1 = np.linspace(-3, 3, 100)
|
|||
|
|
x2 = np.linspace(-3, 3, 100)
|
|||
|
|
X1, X2 = np.meshgrid(x1, x2)
|
|||
|
|
Z = np.array([[func_2d([x1, x2]) for x1, x2 in zip(row1, row2)]
|
|||
|
|
for row1, row2 in zip(X1, X2)])
|
|||
|
|
|
|||
|
|
# Grid search
|
|||
|
|
x_opt, f_max = grid_search(
|
|||
|
|
objective_fn=func_2d,
|
|||
|
|
bounds=[(-3, 3), (-3, 3)],
|
|||
|
|
n_points=15
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Plot
|
|||
|
|
plt.figure(figsize=(10, 8))
|
|||
|
|
plt.contourf(X1, X2, Z, levels=20, cmap='viridis')
|
|||
|
|
plt.colorbar(label='Objective Value')
|
|||
|
|
|
|||
|
|
# Show grid points
|
|||
|
|
grid_1d = np.linspace(-3, 3, 15)
|
|||
|
|
for x1 in grid_1d:
|
|||
|
|
for x2 in grid_1d:
|
|||
|
|
plt.plot(x1, x2, 'r.', markersize=3)
|
|||
|
|
|
|||
|
|
plt.scatter(x_opt[0], x_opt[1], c='red', s=300, marker='*',
|
|||
|
|
edgecolors='white', linewidths=2,
|
|||
|
|
label=f'Optimum: ({x_opt[0]:.2f}, {x_opt[1]:.2f})')
|
|||
|
|
|
|||
|
|
plt.xlabel('x₁')
|
|||
|
|
plt.ylabel('x₂')
|
|||
|
|
plt.title('2D Grid Search')
|
|||
|
|
plt.legend()
|
|||
|
|
plt.axis('equal')
|
|||
|
|
plt.show()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Performance Analysis
|
|||
|
|
|
|||
|
|
### Computational Cost
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
import time
|
|||
|
|
from optimizr import grid_search
|
|||
|
|
|
|||
|
|
def expensive_function(x):
|
|||
|
|
"""Simulate expensive computation"""
|
|||
|
|
time.sleep(0.001) # 1ms per evaluation
|
|||
|
|
return -(x[0]**2 + x[1]**2)
|
|||
|
|
|
|||
|
|
# Test different grid sizes
|
|||
|
|
for n_points in [5, 10, 20, 30]:
|
|||
|
|
n_evals = n_points ** 2
|
|||
|
|
|
|||
|
|
start = time.time()
|
|||
|
|
x_opt, f_max = grid_search(
|
|||
|
|
objective_fn=expensive_function,
|
|||
|
|
bounds=[(-5, 5), (-5, 5)],
|
|||
|
|
n_points=n_points
|
|||
|
|
)
|
|||
|
|
elapsed = time.time() - start
|
|||
|
|
|
|||
|
|
print(f"n_points={n_points:2d}: {n_evals:4d} evaluations, "
|
|||
|
|
f"{elapsed:.2f}s ({elapsed/n_evals*1000:.2f}ms per eval)")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Scaling with Dimensions
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Demonstrate exponential growth
|
|||
|
|
dimensions = [1, 2, 3, 4, 5]
|
|||
|
|
n_points = 10
|
|||
|
|
|
|||
|
|
for n_dim in dimensions:
|
|||
|
|
n_evals = n_points ** n_dim
|
|||
|
|
estimated_time = n_evals * 0.001 # Assuming 1ms per eval
|
|||
|
|
|
|||
|
|
print(f"{n_dim}D: {n_evals:,} evaluations "
|
|||
|
|
f"(~{estimated_time:.1f}s with 1ms/eval)")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Output:
|
|||
|
|
```
|
|||
|
|
1D: 10 evaluations (~0.0s with 1ms/eval)
|
|||
|
|
2D: 100 evaluations (~0.1s with 1ms/eval)
|
|||
|
|
3D: 1,000 evaluations (~1.0s with 1ms/eval)
|
|||
|
|
4D: 10,000 evaluations (~10.0s with 1ms/eval)
|
|||
|
|
5D: 100,000 evaluations (~100.0s with 1ms/eval)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Performance Notes
|
|||
|
|
|
|||
|
|
- **Rust Backend**: When available, grid point generation and evaluation is highly optimized.
|
|||
|
|
|
|||
|
|
- **Python Fallback**: Pure Python/NumPy fallback using `itertools.product`.
|
|||
|
|
|
|||
|
|
- **Parallelization**: Grid evaluations are independent and can be parallelized (future enhancement).
|
|||
|
|
|
|||
|
|
- **Memory**: All grid points are evaluated, so memory usage is O(n_points^n_params).
|
|||
|
|
|
|||
|
|
## When to Use Grid Search
|
|||
|
|
|
|||
|
|
### ✅ Good For
|
|||
|
|
|
|||
|
|
- **Small parameter spaces** (≤ 3 dimensions with reasonable resolution)
|
|||
|
|
- **Expensive models** where you want guaranteed coverage
|
|||
|
|
- **Visualization** and understanding the objective landscape
|
|||
|
|
- **Benchmarking** other optimization methods
|
|||
|
|
- **Discrete parameters** that naturally fit on a grid
|
|||
|
|
- **Verifying global optimum** in small problems
|
|||
|
|
|
|||
|
|
### ❌ Not Good For
|
|||
|
|
|
|||
|
|
- **High-dimensional problems** (exponential cost)
|
|||
|
|
- **Continuous optimization** (infinitely many points)
|
|||
|
|
- **Large-scale hyperparameter tuning** (use random search or Bayesian optimization instead)
|
|||
|
|
- **Time-critical applications** (too slow)
|
|||
|
|
|
|||
|
|
## Tips and Best Practices
|
|||
|
|
|
|||
|
|
### 1. Start Coarse, Then Refine
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# First pass: coarse grid
|
|||
|
|
x_coarse, f_coarse = grid_search(
|
|||
|
|
objective_fn=objective,
|
|||
|
|
bounds=[(-10, 10), (-10, 10)],
|
|||
|
|
n_points=10
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Second pass: fine grid around optimum
|
|||
|
|
margin = 2.0
|
|||
|
|
x_fine, f_fine = grid_search(
|
|||
|
|
objective_fn=objective,
|
|||
|
|
bounds=[
|
|||
|
|
(x_coarse[0] - margin, x_coarse[0] + margin),
|
|||
|
|
(x_coarse[1] - margin, x_coarse[1] + margin)
|
|||
|
|
],
|
|||
|
|
n_points=20
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"Refined optimum: {x_fine}")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Use Logarithmic Scales
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# For parameters that span orders of magnitude
|
|||
|
|
def objective_log(params):
|
|||
|
|
# Convert from log scale
|
|||
|
|
learning_rate = 10 ** params[0]
|
|||
|
|
regularization = 10 ** params[1]
|
|||
|
|
|
|||
|
|
# Evaluate model...
|
|||
|
|
score = model_score(learning_rate, regularization)
|
|||
|
|
return score
|
|||
|
|
|
|||
|
|
x_opt, f_max = grid_search(
|
|||
|
|
objective_fn=objective_log,
|
|||
|
|
bounds=[(-5, -1), (-4, 0)], # log10 scale
|
|||
|
|
n_points=20
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
lr_opt = 10 ** x_opt[0]
|
|||
|
|
reg_opt = 10 ** x_opt[1]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Intelligent Bounds Selection
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Use domain knowledge to set reasonable bounds
|
|||
|
|
def intelligent_bounds(parameter_type):
|
|||
|
|
bounds_dict = {
|
|||
|
|
'learning_rate': (1e-5, 1e-1),
|
|||
|
|
'n_estimators': (10, 500),
|
|||
|
|
'max_depth': (2, 20),
|
|||
|
|
'alpha': (1e-4, 10),
|
|||
|
|
}
|
|||
|
|
return bounds_dict.get(parameter_type, (0, 1))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Comparison with Other Methods
|
|||
|
|
|
|||
|
|
| Method | Coverage | Speed | Use Case |
|
|||
|
|
|--------|----------|-------|----------|
|
|||
|
|
| **Grid Search** | Complete | Slow | Small spaces, verification |
|
|||
|
|
| Random Search | Incomplete | Fast | High dimensions |
|
|||
|
|
| Differential Evolution | Adaptive | Medium | Non-convex functions |
|
|||
|
|
| Bayesian Optimization | Intelligent | Medium | Expensive evaluations |
|
|||
|
|
| Gradient Descent | Local | Very fast | Smooth, differentiable |
|
|||
|
|
|
|||
|
|
## See Also
|
|||
|
|
|
|||
|
|
- [Differential Evolution API](differential_evolution.md) - For large-scale optimization
|
|||
|
|
- [MCMC API](mcmc.md) - For Bayesian inference
|
|||
|
|
- [Examples](../examples/) - Complete working examples and tutorials
|