Skip to content

stemflow.tuning

Optional sequential tuning for generic estimators, with an XGBoost/local-beta factory.

XGBoost recipe parameters

Install stemflow[tuning] for XGBoost and Optuna. Generic random search with a supplied estimator does not require those optional packages.

Parameter Default Meaning
tuning True Enable the nested hyperparameter search; False keeps local beta calibration with a fixed XGBoost.
ridge 100.0 Identity-centered beta penalty applied to summed loss; not a universal optimum.
n_trials 4 Sequential search trials inside each calibration training fold.
n_estimators 100 Trees in each retained XGBoost fold model.
tuning_n_estimators 40 Trees per search candidate before fitting the retained model.
n_splits 3 Calibration folds and number of retained fold models per fitted stixel.
tuning_n_splits 2 Nested search folds, used only when tuning is enabled.
Spatio_blocks_count, Temporal_blocks_count 5, 5 Block cutpoint counts, forwarded to both calibration and tuning; not degrees/days or fold counts.
Spatio1, Spatio2, Temporal1 longitude, latitude, DOY Columns for CV metadata; the spatial columns are excluded from predictors.
max_trials 10 Maximum blocked-split retries when checking fold support; distinct from tuning trials.
search "optuna" Search backend: Optuna/TPE or explicit "random".
tuning_verbose False Control Optuna study/trial INFO messages independently of estimator/AdaSTEM logs.
random_state 42 Reproducible estimator/search/split seeds.

For other search spaces, classifiers, regressors, maps, CV granularities or rare-fold policies, compose the generic wrappers directly. See local calibration and tuning and the executed example. Regression calibration concerns point means, not predictive intervals.

Small sequential searches, composable with OOF calibration and AdaSTEM.

SelfTuningClassifier

Bases: ClassifierMixin, _SelfTuning, BaseEstimator

Tune a classifier without calibration, or nest it inside a calibrator.

Search is sequential; parallelism belongs to the outer framework. verbose=False hides Optuna study/trial INFO messages while retaining warnings and errors. True enables INFO messages. The previous logging level is restored afterward; estimator verbosity is configured separately.

Source code in stemflow/tuning.py
class SelfTuningClassifier(ClassifierMixin, _SelfTuning, BaseEstimator):
    """Tune a classifier without calibration, or nest it inside a calibrator.

    Search is sequential; parallelism belongs to the outer framework.
    ``verbose=False`` hides Optuna study/trial INFO messages while retaining
    warnings and errors. ``True`` enables INFO messages. The previous logging
    level is restored afterward; estimator verbosity is configured separately.
    """
    _classification = True

    def __init__(self, estimator, param_distributions, cv=None, n_trials=4,
                 random_state=42, search="random", search_estimator_params=None,
                 exclude_features=None, verbose=False):
        self.estimator, self.param_distributions, self.cv = estimator, param_distributions, cv
        self.n_trials, self.random_state, self.search = n_trials, random_state, search
        self.search_estimator_params, self.exclude_features = search_estimator_params, exclude_features
        self.verbose = verbose

    def predict_proba(self, X):
        X = self._input(X)
        return self.estimator_.predict_proba(X)

    def predict(self, X):
        X = self._input(X)
        return self.estimator_.predict(X)

SelfTuningRegressor

Bases: RegressorMixin, _SelfTuning, BaseEstimator

Sequential MSE search; verbose controls Optuna INFO messages only.

Source code in stemflow/tuning.py
class SelfTuningRegressor(RegressorMixin, _SelfTuning, BaseEstimator):
    """Sequential MSE search; ``verbose`` controls Optuna INFO messages only."""
    _classification = False

    def __init__(self, estimator, param_distributions, cv=None, n_trials=4,
                 random_state=42, search="random", search_estimator_params=None,
                 exclude_features=None, verbose=False):
        self.estimator, self.param_distributions, self.cv = estimator, param_distributions, cv
        self.n_trials, self.random_state, self.search = n_trials, random_state, search
        self.search_estimator_params, self.exclude_features = search_estimator_params, exclude_features
        self.verbose = verbose

    def predict(self, X):
        X = self._input(X)
        return self.estimator_.predict(X)

local_beta_xgboost(*, tuning=True, ridge=100.0, n_trials=4, n_estimators=100, tuning_n_estimators=40, random_state=42, max_trials=10, Spatio1='longitude', Spatio2='latitude', Temporal1='DOY', search='optuna', Spatio_blocks_count=5, Temporal_blocks_count=5, n_splits=3, tuning_n_splits=2, tuning_verbose=False)

Compose the supplied pilot's XGBoost search and stixel-local beta map.

XGBoost is imported only when this factory is called; Optuna only during tuning. No classifier balancing or final global probability map is added. Block counts configure both calibration and nested tuning splits. Compose the wrappers directly when those two stages need different granularities. n_splits controls calibration folds; tuning_n_splits controls the nested search folds and is used only when tuning is enabled. tuning_verbose controls Optuna study/trial INFO messages inside each worker. It is independent of AdaSTEM's progress and monitoring logs.

Source code in stemflow/tuning.py
def local_beta_xgboost(*, tuning=True, ridge=100., n_trials=4, n_estimators=100,
                       tuning_n_estimators=40, random_state=42, max_trials=10,
                       Spatio1="longitude", Spatio2="latitude", Temporal1="DOY",
                       search="optuna", Spatio_blocks_count=5, Temporal_blocks_count=5,
                       n_splits=3, tuning_n_splits=2, tuning_verbose=False):
    """Compose the supplied pilot's XGBoost search and stixel-local beta map.

    XGBoost is imported only when this factory is called; Optuna only during
    tuning. No classifier balancing or final global probability map is added.
    Block counts configure both calibration and nested tuning splits. Compose
    the wrappers directly when those two stages need different granularities.
    ``n_splits`` controls calibration folds; ``tuning_n_splits`` controls the
    nested search folds and is used only when tuning is enabled.
    ``tuning_verbose`` controls Optuna study/trial INFO messages inside each
    worker. It is independent of AdaSTEM's progress and monitoring logs.
    """
    for name, count in (("n_splits", n_splits), ("tuning_n_splits", tuning_n_splits)):
        if isinstance(count, (bool, np.bool_)) or not isinstance(count, (int, np.integer)) or count < 2:
            raise ValueError(f"{name} must be an integer >= 2.")
    from xgboost import XGBClassifier
    from .calibration import BetaCalibrator, CrossValidatedCalibratedClassifier
    base = XGBClassifier(n_estimators=n_estimators, max_depth=6, learning_rate=.3,
        min_child_weight=1, reg_lambda=1., n_jobs=1, tree_method="hist", verbosity=0,
        random_state=random_state, eval_metric="logloss")
    outer = SpatioTemporalKFold(int(n_splits), Spatio1=Spatio1, Spatio2=Spatio2,
        Temporal1=Temporal1, random_state=random_state, max_trials=max_trials,
        Spatio_blocks_count=Spatio_blocks_count, Temporal_blocks_count=Temporal_blocks_count)
    if tuning:
        inner = SpatioTemporalKFold(int(tuning_n_splits), Spatio1=Spatio1, Spatio2=Spatio2,
            Temporal1=Temporal1, random_state=random_state+17, max_trials=max_trials,
            Spatio_blocks_count=Spatio_blocks_count, Temporal_blocks_count=Temporal_blocks_count)
        base = SelfTuningClassifier(base, dict(max_depth=[3, 4, 6], learning_rate=[.1, .3],
            min_child_weight=[1, 5, 10], reg_lambda=[1., 5., 10.]), cv=inner,
            n_trials=n_trials, random_state=random_state, search=search,
            search_estimator_params={"n_estimators": tuning_n_estimators}, verbose=tuning_verbose)
    return CrossValidatedCalibratedClassifier(base, BetaCalibrator(ridge), cv=outer)