Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bdc62b4
chore(uv): add optuna to toml
fleur-petit Feb 6, 2026
1c126d8
feat(tuning): hyperparameter tuning with Optuna
fleur-petit Mar 17, 2026
39ad783
chore: add backwards compatibility new SampleWeighter method (#810)
lschilders Feb 16, 2026
45c1885
feature(STEF-2717): save last valid rolling aggregate during training…
lschilders Feb 16, 2026
81b3e9c
fix: add getter method for _is_fitted in RollingAggregatesAdder (#812)
lschilders Feb 17, 2026
c91f510
Implement-median-model-v4 (#793)
JanMaartenvanDoorn Feb 17, 2026
3e921d7
fix(STEF-2802): skip RollingAggregatesAdder when no aggregation funct…
egordm Feb 20, 2026
233a6f9
fix(STEF-2802): anchor mlflow gitignore patterns to repo root (#814)
egordm Feb 20, 2026
e9b5eb8
feature: Added (openstef-meta) meta models package (#771)
Lars800 Mar 3, 2026
089160e
Add openstef-meta to cmds (#819)
MvLieshout Mar 4, 2026
1db295c
feat(STEF-2702): openstef-meta cleanup & release pipeline fixes (#822)
egordm Mar 5, 2026
e0b862b
fix(STEF-2702): normalize MLflow tracking URI for Windows compatibili…
egordm Mar 5, 2026
3510da4
fix: apply flatliner value to all quantiles (#828)
lschilders Mar 6, 2026
082b374
chore: OpenSTEF-meta Workflow Improvements (#829)
MvLieshout Mar 6, 2026
8e16ed3
fix: Ensemble Forecasting Model postprocessing fit and transform (#830)
MvLieshout Mar 9, 2026
e88cdd2
feature: add apply() and timezone-awareness to AvailableAt (#831)
lschilders Mar 9, 2026
c8871a9
fix: timezone handling for offset in AvailableAt apply() (matching ap…
lschilders Mar 10, 2026
ea3658d
fix: Fix length mismatch bug, add test (#834)
MvLieshout Mar 12, 2026
1a17320
feature: add metrics and providers MAE, Completeness and extend rMAE …
lschilders Mar 12, 2026
b18544c
chore: GHA workflow security updates (#836)
MvLieshout Mar 12, 2026
aabfa50
fix(STEF-2854): handle backtest robustness issues (#837)
egordm Mar 19, 2026
4903e84
add shifter transform and tests (#839)
MentReeze Mar 19, 2026
ef65a0f
refactor(tuning): adjust tuning such that it can be used with ensembl…
fleur-petit Mar 25, 2026
7f7177c
refactor(tuning): renaming and tests
fleur-petit Mar 26, 2026
4e9d3dc
refactor: remove unused HyperParams import from gblinear/xgboost fore…
fleur-petit Mar 26, 2026
3691893
Add openstef-meta to cmds (#819)
MvLieshout Mar 4, 2026
fa87feb
feat(STEF-2702): openstef-meta cleanup & release pipeline fixes (#822)
egordm Mar 5, 2026
b49139e
chore: OpenSTEF-meta Workflow Improvements (#829)
MvLieshout Mar 6, 2026
3401a04
fix: Ensemble Forecasting Model postprocessing fit and transform (#830)
MvLieshout Mar 9, 2026
6fb25eb
Merge release/v4.0.0 into feature/684-openstef-40-support-hyperparame…
fleur-petit Mar 26, 2026
3d4e166
style: complexity reduction, pytest.approx for float assert, commente…
fleur-petit Mar 27, 2026
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
1,159 changes: 1,159 additions & 0 deletions examples/tutorials/try_optuna.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
)
from openstef_models.utils.data_split import DataSplitter
from openstef_models.utils.feature_selection import Exclude, FeatureSelection, Include
from openstef_models.utils.tuning import TuningConfigMixin
from openstef_models.workflows.custom_forecasting_workflow import (
CustomForecastingWorkflow,
ForecastingCallback,
Expand All @@ -74,7 +75,7 @@
from openstef_core.datasets import ForecastDataset


class EnsembleForecastingWorkflowConfig(BaseConfig):
class EnsembleForecastingWorkflowConfig(TuningConfigMixin, BaseConfig):
"""Configuration for ensemble forecasting workflows."""

kind: Literal["ensemble"] = Field(default="ensemble", description="Discriminator tag for config type.")
Expand Down Expand Up @@ -279,6 +280,15 @@ class EnsembleForecastingWorkflowConfig(BaseConfig):
default_factory=dict,
description="Optional metadata tags for experiment tracking.",
)
# Hyperparameter tuning (Optuna)
optuna_n_trials: int = Field(
default=50,
description="Number of Optuna trials to run during hyperparameter tuning.",
)
optuna_seed: int | None = Field(
default=None,
description="Random seed for Optuna sampler reproducibility. None disables seeding.",
)


def _checks(config: EnsembleForecastingWorkflowConfig) -> list[Transform[TimeSeriesDataset, TimeSeriesDataset]]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
to predict values outside the range of the training data.
"""

from typing import ClassVar, Literal, override
from typing import Annotated, ClassVar, Literal, override

import numpy as np
import pandas as pd
Expand All @@ -22,7 +22,6 @@
from openstef_core.datasets.mixins import LeadTime
from openstef_core.datasets.validated_datasets import ForecastDataset, ForecastInputDataset
from openstef_core.exceptions import InputValidationError, MissingExtraError, NotFittedError
from openstef_core.mixins.predictor import HyperParams
from openstef_core.utils.pandas import normalize_to_unit_sum
from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster
from openstef_models.models.forecasting.forecaster import Forecaster
Expand All @@ -32,26 +31,27 @@
get_objective_function,
xgb_prepare_target_for_objective,
)
from openstef_models.utils.tuning import CategoricalRange, FloatRange, IntRange, TunableHyperParams

try:
import xgboost as xgb
except ImportError as e:
raise MissingExtraError("xgboost", "openstef-models") from e


class GBLinearHyperParams(HyperParams):
class GBLinearHyperParams(TunableHyperParams):
"""Hyperparameter configuration for GBLinear forecaster."""

# Learning Parameters
n_steps: int = Field(
n_steps: Annotated[int, IntRange(50, 1000)] = Field(
default=500,
description="Number for steps (boosting rounds) to train the GBLinear model.",
)
updater: str = Field(
updater: Annotated[str, CategoricalRange(("shotgun", "coord_descent"))] = Field(
default="shotgun",
description="The updater to use for the GBLinear booster.",
)
learning_rate: float = Field(
learning_rate: Annotated[float, FloatRange(0.01, 0.5, log=True)] = Field(
default=0.15,
description="Step size shrinkage used to prevent overfitting. Range: [0,1]. Lower values require more boosting "
"rounds.",
Expand All @@ -68,15 +68,15 @@ class GBLinearHyperParams(HyperParams):
)

# Regularization
reg_alpha: float = Field(
reg_alpha: Annotated[float, FloatRange(1e-8, 1.0, log=True)] = Field(
default=0.0001, description="L1 regularization on weights. Higher values increase regularization. Range: [0,∞]"
)
reg_lambda: float = Field(
reg_lambda: Annotated[float, FloatRange(1e-8, 1.0, log=True)] = Field(
default=0.1, description="L2 regularization on weights. Higher values increase regularization. Range: [0,∞]"
)

# Feature selection
feature_selector: str = Field(
feature_selector: Annotated[str, CategoricalRange(("cyclic", "shuffle", "random", "greedy", "thrifty"))] = Field(
default="shuffle",
description="Feature selection method.",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
comprehensive hyperparameter control for production forecasting workflows.
"""

from typing import ClassVar, Literal, override
from typing import Annotated, ClassVar, Literal, override

import numpy as np
import pandas as pd
Expand All @@ -18,7 +18,6 @@

from openstef_core.datasets import ForecastDataset, ForecastInputDataset, TimeSeriesDataset
from openstef_core.exceptions import MissingExtraError, NotFittedError
from openstef_core.mixins import HyperParams
from openstef_core.utils.pandas import normalize_to_unit_sum
from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster
from openstef_models.models.forecasting.forecaster import Forecaster
Expand All @@ -28,14 +27,15 @@
get_objective_function,
xgb_prepare_target_for_objective,
)
from openstef_models.utils.tuning import CategoricalRange, FloatRange, IntRange, TunableHyperParams

try:
import xgboost as xgb
except ImportError as e:
raise MissingExtraError("xgboost", "openstef-models") from e


class XGBoostHyperParams(HyperParams):
class XGBoostHyperParams(TunableHyperParams):
"""XGBoost hyperparameters for gradient boosting tree models.

Configures tree-specific parameters for XGBoost gbtree booster. Provides
Expand Down Expand Up @@ -65,28 +65,28 @@ class XGBoostHyperParams(HyperParams):
"""

# Core Tree Boosting Parameters
n_estimators: int = Field(
n_estimators: Annotated[int, IntRange(50, 500)] = Field(
default=100,
description="Number of boosting rounds/trees to fit. Higher values may improve performance but "
"increase training time and risk overfitting.",
)
learning_rate: float = Field(
learning_rate: Annotated[float, FloatRange(0.01, 0.5, log=True)] = Field(
default=0.3,
alias="eta",
description="Step size shrinkage used to prevent overfitting. Range: [0,1]. Lower values require "
"more boosting rounds.",
)
max_depth: int = Field(
max_depth: Annotated[int, IntRange(1, 15)] = Field(
default=6,
description="Maximum depth of trees. Higher values capture more complex patterns but risk "
"overfitting. Range: [1,∞]",
)
min_child_weight: float = Field(
min_child_weight: Annotated[float, FloatRange(1.0, 10.0)] = Field(
default=1,
description="Minimum sum of instance weight (hessian) needed in a child. Higher values prevent "
"overfitting. Range: [0,∞]",
)
gamma: float = Field(
gamma: Annotated[float, FloatRange(0.0, 5.0)] = Field(
default=0,
alias="min_split_loss",
description="Minimum loss reduction required to make a split. Higher values make algorithm more "
Expand All @@ -103,10 +103,10 @@ class XGBoostHyperParams(HyperParams):
)

# Regularization
reg_alpha: float = Field(
reg_alpha: Annotated[float, FloatRange(1e-8, 10.0, log=True)] = Field(
default=0, description="L1 regularization on leaf weights. Higher values increase regularization. Range: [0,∞]"
)
reg_lambda: float = Field(
reg_lambda: Annotated[float, FloatRange(1e-8, 10.0, log=True)] = Field(
default=1, description="L2 regularization on leaf weights. Higher values increase regularization. Range: [0,∞]"
)
max_delta_step: float = Field(
Expand All @@ -119,7 +119,7 @@ class XGBoostHyperParams(HyperParams):
max_leaves: int = Field(
default=0, description="Maximum number of leaves. 0 means no limit. Only relevant when grow_policy='lossguide'."
)
grow_policy: Literal["depthwise", "lossguide"] = Field(
grow_policy: Annotated[Literal["depthwise", "lossguide"], CategoricalRange(("depthwise", "lossguide"))] = Field(
default="depthwise",
description="Controls how new nodes are added. 'depthwise' grows level by level, 'lossguide' adds leaves "
"with highest loss reduction.",
Expand All @@ -136,11 +136,11 @@ class XGBoostHyperParams(HyperParams):
)

# Subsampling Parameters
subsample: float = Field(
subsample: Annotated[float, FloatRange(0.5, 1.0)] = Field(
default=1.0,
description="Fraction of training samples used for each tree. Lower values prevent overfitting. Range: (0,1]",
)
colsample_bytree: float = Field(
colsample_bytree: Annotated[float, FloatRange(0.5, 1.0)] = Field(
default=1.0, description="Fraction of features used when constructing each tree. Range: (0,1]"
)
colsample_bylevel: float = Field(
Expand All @@ -151,7 +151,10 @@ class XGBoostHyperParams(HyperParams):
)

# Tree Construction Method
tree_method: Literal["auto", "exact", "hist", "approx", "gpu_hist"] = Field(
tree_method: Annotated[
Literal["auto", "exact", "hist", "approx", "gpu_hist"],
CategoricalRange(("auto", "hist", "approx")),
] = Field(
default="auto",
description="Tree construction algorithm. 'hist' is fastest for large datasets, 'exact' for small "
"datasets, 'approx' is deprecated.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@
Provides configurations and utilities for setting up forecasting workflows.
"""

from .forecasting_workflow import ForecastingWorkflowConfig, create_forecasting_workflow
from openstef_models.utils.tuning import TuningResult, fit_with_tuning, tune

from .forecasting_workflow import (
ForecastingWorkflowConfig,
create_forecasting_workflow,
)

__all__ = [
"ForecastingWorkflowConfig",
"TuningResult",
"create_forecasting_workflow",
"fit_with_tuning",
"tune",
]
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
)
from openstef_models.utils.data_split import DataSplitter
from openstef_models.utils.feature_selection import Exclude, FeatureSelection, Include
from openstef_models.utils.tuning import TuningConfigMixin
from openstef_models.workflows.custom_forecasting_workflow import (
CustomForecastingWorkflow,
ForecastingCallback,
Expand Down Expand Up @@ -100,7 +101,7 @@ def tags(self) -> dict[str, str]:
}


class ForecastingWorkflowConfig(BaseConfig): # PredictionJob
class ForecastingWorkflowConfig(TuningConfigMixin, BaseConfig): # PredictionJob
"""Configuration for forecasting workflows.

Defines all parameters needed to set up a forecasting model, including model type,
Expand Down Expand Up @@ -278,6 +279,16 @@ class ForecastingWorkflowConfig(BaseConfig): # PredictionJob
default=0, description="Verbosity level. 0=silent, 1=warning, 2=info, 3=debug"
)

# Hyperparameter tuning (Optuna)
optuna_n_trials: int = Field(
default=20,
description="Number of Optuna trials to run when any search-space field has tune=True.",
)
optuna_seed: int | None = Field(
default=42,
description="Random seed for the Optuna TPE sampler. Set to None to disable seeding.",
)

# Metadata
tags: dict[str, str] = Field(
default_factory=dict,
Expand Down
Loading
Loading