Implement grid search

This commit is contained in:
Manuel Raimann
2026-01-30 22:01:44 +01:00
committed by Manuel
parent 90bf73a39f
commit b482d56e89
6 changed files with 1257 additions and 12 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ authors = ["Manuel Raimann <raimannma@outlook.de"]
description = "A Rust library for optimization algorithms." description = "A Rust library for optimization algorithms."
repository = "https://github.com/raimannma/rust-optimizer" repository = "https://github.com/raimannma/rust-optimizer"
documentation = "https://docs.rs/optimizer" documentation = "https://docs.rs/optimizer"
keywords = ["optimization", "algorithms", "rust"] keywords = ["optimization", "hyperparameter", "tpe", "grid-search", "bayesian"]
categories = ["algorithm", "science", "data-structures"] categories = ["algorithm", "science", "data-structures"]
readme = "README.md" readme = "README.md"
+52 -3
View File
@@ -1,6 +1,6 @@
# optimizer # optimizer
A Rust library for black-box optimization using Tree-Parzen Estimator (TPE). A Rust library for black-box optimization with multiple sampling strategies.
[![Docs](https://docs.rs/optimizer/badge.svg)](https://docs.rs/optimizer) [![Docs](https://docs.rs/optimizer/badge.svg)](https://docs.rs/optimizer)
[![Crates.io](https://img.shields.io/crates/v/optimizer.svg)](https://crates.io/crates/optimizer) [![Crates.io](https://img.shields.io/crates/v/optimizer.svg)](https://crates.io/crates/optimizer)
@@ -9,6 +9,10 @@ A Rust library for black-box optimization using Tree-Parzen Estimator (TPE).
## Features ## Features
- Optuna-like API for hyperparameter optimization - Optuna-like API for hyperparameter optimization
- Multiple sampling strategies:
- **Random Search** - Simple random sampling for baseline comparisons
- **TPE (Tree-Parzen Estimator)** - Bayesian optimization for efficient search
- **Grid Search** - Exhaustive search over a specified parameter grid
- Float, integer, and categorical parameter types - Float, integer, and categorical parameter types
- Log-scale and stepped parameter sampling - Log-scale and stepped parameter sampling
- Sync and async optimization with parallel trial evaluation - Sync and async optimization with parallel trial evaluation
@@ -16,9 +20,10 @@ A Rust library for black-box optimization using Tree-Parzen Estimator (TPE).
## Quick Start ## Quick Start
```rust ```rust
use optimizer::{Direction, Study, TpeSampler}; use optimizer::{Direction, Study};
use optimizer::sampler::tpe::TpeSampler;
let sampler = TpeSampler::builder().seed(42).build(); let sampler = TpeSampler::builder().seed(42).build().unwrap();
let study: Study<f64> = Study::with_sampler(Direction::Minimize, sampler); let study: Study<f64> = Study::with_sampler(Direction::Minimize, sampler);
study study
@@ -32,6 +37,50 @@ let best = study.best_trial().unwrap();
println!("Best value: {} at x={:?}", best.value, best.params); println!("Best value: {} at x={:?}", best.value, best.params);
``` ```
## Samplers
### Random Search
```rust
use optimizer::{Direction, Study};
use optimizer::sampler::random::RandomSampler;
let study: Study<f64> = Study::with_sampler(
Direction::Minimize,
RandomSampler::with_seed(42),
);
```
### TPE (Tree-Parzen Estimator)
```rust
use optimizer::{Direction, Study};
use optimizer::sampler::tpe::TpeSampler;
let sampler = TpeSampler::builder()
.gamma(0.15) // Quantile for good/bad split
.n_startup_trials(20) // Random trials before TPE kicks in
.n_ei_candidates(32) // Candidates to evaluate
.seed(42)
.build()
.unwrap();
let study: Study<f64> = Study::with_sampler(Direction::Minimize, sampler);
```
### Grid Search
```rust
use optimizer::{Direction, Study};
use optimizer::sampler::grid::GridSearchSampler;
let sampler = GridSearchSampler::builder()
.n_points_per_param(10) // Number of points per parameter dimension
.build();
let study: Study<f64> = Study::with_sampler(Direction::Minimize, sampler);
```
## Feature Flags ## Feature Flags
- `async` - Enable async optimization methods (requires tokio) - `async` - Enable async optimization methods (requires tokio)
+38 -4
View File
@@ -9,10 +9,16 @@
#![deny(clippy::pedantic)] #![deny(clippy::pedantic)]
#![deny(clippy::std_instead_of_core)] #![deny(clippy::std_instead_of_core)]
//! A Tree-Parzen Estimator (TPE) library for black-box optimization. //! A black-box optimization library with multiple sampling strategies.
//! //!
//! This library provides an Optuna-like API for hyperparameter optimization //! This library provides an Optuna-like API for hyperparameter optimization
//! using the Tree-Parzen Estimator algorithm. It supports: //! with support for multiple sampling algorithms:
//!
//! - **Random Search** - Simple random sampling for baseline comparisons
//! - **TPE (Tree-Parzen Estimator)** - Bayesian optimization for efficient search
//! - **Grid Search** - Exhaustive search over a specified parameter grid
//!
//! Additional features include:
//! //!
//! - Float, integer, and categorical parameter types //! - Float, integer, and categorical parameter types
//! - Log-scale and stepped parameter sampling //! - Log-scale and stepped parameter sampling
@@ -91,9 +97,22 @@
//! .unwrap(); //! .unwrap();
//! ``` //! ```
//! //!
//! # Configuring TPE //! # Available Samplers
//! //!
//! The [`sampler::tpe::TpeSampler`] can be configured using the builder pattern: //! ## Random Search
//!
//! The simplest sampling strategy, useful for baselines:
//!
//! ```
//! use optimizer::sampler::random::RandomSampler;
//! use optimizer::{Direction, Study};
//!
//! let study: Study<f64> = Study::with_sampler(Direction::Minimize, RandomSampler::with_seed(42));
//! ```
//!
//! ## TPE (Tree-Parzen Estimator)
//!
//! Bayesian optimization that learns from previous trials:
//! //!
//! ``` //! ```
//! use optimizer::sampler::tpe::TpeSampler; //! use optimizer::sampler::tpe::TpeSampler;
@@ -107,6 +126,21 @@
//! .unwrap(); //! .unwrap();
//! ``` //! ```
//! //!
//! ## Grid Search
//!
//! Exhaustive search over a discretized parameter space:
//!
//! ```
//! use optimizer::sampler::grid::GridSearchSampler;
//! use optimizer::{Direction, Study};
//!
//! let sampler = GridSearchSampler::builder()
//! .n_points_per_param(10) // Points per parameter dimension
//! .build();
//!
//! let study: Study<f64> = Study::with_sampler(Direction::Minimize, sampler);
//! ```
//!
//! # Async and Parallel Optimization //! # Async and Parallel Optimization
//! //!
//! With the `async` feature enabled, you can run trials asynchronously: //! With the `async` feature enabled, you can run trials asynchronously:
+1157
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1,5 +1,6 @@
//! Sampler trait and implementations for parameter sampling. //! Sampler trait and implementations for parameter sampling.
pub mod grid;
pub mod random; pub mod random;
pub mod tpe; pub mod tpe;
+8 -4
View File
@@ -152,7 +152,7 @@ fn test_random_sampler_uniform_float_distribution() {
fn test_random_sampler_uniform_int_distribution() { fn test_random_sampler_uniform_int_distribution() {
let study: Study<f64> = Study::with_sampler(Direction::Minimize, RandomSampler::with_seed(123)); let study: Study<f64> = Study::with_sampler(Direction::Minimize, RandomSampler::with_seed(123));
let n_samples = 1000; let n_samples = 5000;
let mut counts = [0u32; 10]; // counts for values 1-10 let mut counts = [0u32; 10]; // counts for values 1-10
study study
@@ -165,11 +165,13 @@ fn test_random_sampler_uniform_int_distribution() {
.unwrap(); .unwrap();
// Each value should appear roughly n_samples / 10 times // Each value should appear roughly n_samples / 10 times
// With 5000 samples, expected ~500 per bucket, std dev ~21
// 20% tolerance allows for ~4.5 std devs which is very safe
let expected = n_samples as f64 / 10.0; let expected = n_samples as f64 / 10.0;
for (i, &count) in counts.iter().enumerate() { for (i, &count) in counts.iter().enumerate() {
let diff = (count as f64 - expected).abs() / expected; let diff = (count as f64 - expected).abs() / expected;
assert!( assert!(
diff < 0.3, diff < 0.2,
"value {} appeared {} times, expected ~{}, diff = {:.1}%", "value {} appeared {} times, expected ~{}, diff = {:.1}%",
i + 1, i + 1,
count, count,
@@ -183,7 +185,7 @@ fn test_random_sampler_uniform_int_distribution() {
fn test_random_sampler_uniform_categorical_distribution() { fn test_random_sampler_uniform_categorical_distribution() {
let study: Study<f64> = Study::with_sampler(Direction::Minimize, RandomSampler::with_seed(456)); let study: Study<f64> = Study::with_sampler(Direction::Minimize, RandomSampler::with_seed(456));
let n_samples = 1000; let n_samples = 2000;
let mut counts = [0u32; 4]; let mut counts = [0u32; 4];
let choices = ["a", "b", "c", "d"]; let choices = ["a", "b", "c", "d"];
@@ -197,11 +199,13 @@ fn test_random_sampler_uniform_categorical_distribution() {
.unwrap(); .unwrap();
// Each category should appear roughly n_samples / 4 times // Each category should appear roughly n_samples / 4 times
// With 2000 samples, expected ~500 per bucket, std dev ~19
// 15% tolerance allows for ~4 std devs which is very safe
let expected = n_samples as f64 / 4.0; let expected = n_samples as f64 / 4.0;
for (i, &count) in counts.iter().enumerate() { for (i, &count) in counts.iter().enumerate() {
let diff = (count as f64 - expected).abs() / expected; let diff = (count as f64 - expected).abs() / expected;
assert!( assert!(
diff < 0.25, diff < 0.15,
"category {} appeared {} times, expected ~{}, diff = {:.1}%", "category {} appeared {} times, expected ~{}, diff = {:.1}%",
i, i,
count, count,