Getting started#
Installation#
pip install BayesHalvingSearchCV
bayes_halving_search_cv has exactly three runtime dependencies: numpy,
scipy, and scikit-learn — that holds for both estimators, including
BayesHalvingSearchCV’s Gaussian Process search
(built on sklearn.gaussian_process.GaussianProcessRegressor plus a
hand-rolled Expected Improvement acquisition — no Optuna, no torch).
For development (running the test suite from a source checkout), from the repository root:
python -m venv .venv
.venv/Scripts/pip install -e .[test]
.venv/Scripts/python -m pytest
Specifying param_grid#
param_grid is a plain dict. Each value can be either an explicit
list of values, or a (low, high, num) tuple that gets expanded into
an evenly-spaced grid (like numpy.linspace) — and you can freely mix
both forms in the same grid. Both estimators build their search space from
param_grid in exactly the same way — this is not something you
configure per-estimator.
# Form 1: explicit lists - use when you know exactly which values matter
param_grid = {
"max_features": [2, 3, 4],
"criterion": ["squared_error", "absolute_error"],
}
# Form 2: (low, high, num) tuples - use for a regular sweep across a range
param_grid = {
"n_estimators": (10, 260, 26), # -> 10, 20, 30, ..., 260 (26 values)
"max_depth": (5, 17, 13), # -> 5, 6, 7, ..., 17 (13 values)
}
# Both forms together, in one grid:
param_grid = {
"max_features": [2, 3, 4], # explicit list
"n_estimators": (10, 260, 26), # tuple spec
"max_depth": (5, 17, 13), # tuple spec
}
Integer-endpoint tuples (like the two above) produce an integer grid automatically; float endpoints produce a float grid.
Pattern search (PatternSearchCV)#
from bayes_halving_search_cv import PatternSearchCV
from sklearn.model_selection import TimeSeriesSplit
search = PatternSearchCV(
estimator,
{"max_depth": [3, 5, 7, 9, 12, 16], "min_samples_leaf": [1, 2, 4, 8]},
cv=TimeSeriesSplit(n_splits=5),
scoring="neg_mean_absolute_error",
n_starts=4, # scatter-search multi-start
subsample="stratified", # transition sampling for time-series data
random_state=0,
)
search.fit(X, y)
search.best_params_ # chosen ONLY from full-data evaluations
search.local_optima_ # the map: every distinct optimum found
search.cv_results_ # every point evaluated, and its score
search.search_history_ # every confirmed-improving move across every start
Bayesian search (BayesHalvingSearchCV)#
from bayes_halving_search_cv import BayesHalvingSearchCV
from sklearn.model_selection import TimeSeriesSplit
search = BayesHalvingSearchCV(
estimator,
{"max_depth": [3, 5, 7, 9, 12, 16], "min_samples_leaf": [1, 2, 4, 8]},
cv=TimeSeriesSplit(n_splits=5),
scoring="neg_mean_absolute_error",
n_iter=25, # per-start budget of genuine evaluations
subsample="stratified",
random_state=0,
)
search.fit(X, y)
search.best_params_
search.local_optima_ # the map: every distinct optimum found
search.cv_results_ # every point evaluated, and its score
search.search_history_ # every trial: start index, params, fraction, score
param_grid, subsample/subsample_columns, and multi-start
(n_starts/start_points) follow the same standard on both
estimators — see API Reference for the full parameter list, or the design specs
PatternSearchCV_SPEC.md / BAYESHALVINGSearchCV_SPEC.md in the repository
root for the reasoning behind each default.
Logging#
Both estimators log every algorithmic decision (moves, contractions, ring
calibrations and crossings, data climbs, cache statistics) to the
SearchCV logger. verbose=1 attaches a stream handler at
INFO, verbose=2 at DEBUG (also cascades into scikit-learn’s own
native per-fold [CV] END ... printing, since verbose is passed
through to BaseSearchCV).