Skip to content

Commit e66e795

Browse files
[ENH] Add LightGBM Experiment integration (#238)
## Description <!-- Briefly describe what this PR does --> Adds LightGBM support to Hyperactive by introducing a LightGBMExperiment class that wraps the existing SklearnCvExperiment class ## Related Issues <!-- Link related issues using keywords: "Fixes #123" or "Related to #456" --> Related to #228 ## Type of Change <!-- Mark the applicable option with [x] --> - [ ] `[BUG]` - Bug fix (non-breaking change fixing an issue) - [x] `[ENH]` - New feature (non-breaking change adding functionality) - [ ] `[DOC]` - Documentation changes - [ ] `[MNT]` - Maintenance ## How was this solved? <!-- Explain your approach to solving the issue --> ## Checklist - [x] PR title includes appropriate tag: `[BUG]`, `[ENH]`, `[DOC]` or `[MNT]` - [x] Linked to related issue (if applicable) - [x] Code passes `make check` (lint, format, isort) - [x] Tests added/updated for changes (if applicable) - [x] Documentation updated (if applicable) ## Testing <!-- How can reviewers verify this change works? --> ## Additional Notes <!-- Any additional context, screenshots, or considerations --> --------- Co-authored-by: Simon Blanke <simon.blanke@yahoo.com>
1 parent 8dbb243 commit e66e795

8 files changed

Lines changed: 239 additions & 2 deletions

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ jobs:
367367
python -m pip install --no-cache-dir build
368368
make install-all-extras-for-test
369369
370+
- name: Remove lightgbm on macOS (native lib requires libomp, tests hang)
371+
if: runner.os == 'macOS'
372+
run: python -m pip uninstall -y lightgbm
373+
370374
- name: Show dependencies
371375
run: python -m pip list
372376

docs/source/_snippets/user_guide/integrations.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,41 @@ def configure_optimizers(self):
226226
best_params = optimizer.solve()
227227
# [end:pytorch_lightning]
228228

229+
# [start:lightgbm_experiment]
230+
from lightgbm import LGBMClassifier
231+
from sklearn.datasets import load_iris
232+
233+
from hyperactive.experiment.integrations import LightGBMExperiment
234+
from hyperactive.opt.gfo import BayesianOptimizer
235+
236+
# Load data
237+
X, y = load_iris(return_X_y=True)
238+
239+
# Create the experiment
240+
experiment = LightGBMExperiment(
241+
estimator=LGBMClassifier(verbosity=-1),
242+
X=X,
243+
y=y,
244+
cv=3,
245+
)
246+
247+
# Define search space
248+
search_space = {
249+
"n_estimators": [50, 100, 200],
250+
"max_depth": [3, 5, 7, -1],
251+
"learning_rate": [0.01, 0.05, 0.1, 0.2],
252+
}
253+
254+
# Optimize
255+
optimizer = BayesianOptimizer(
256+
search_space=search_space,
257+
n_iter=10,
258+
experiment=experiment,
259+
)
260+
best_params = optimizer.solve()
261+
print(f"Best parameters: {best_params}")
262+
# [end:lightgbm_experiment]
263+
229264

230265
# --- Runnable test code below ---
231266
if __name__ == "__main__":

docs/source/api_reference/experiments_integrations.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The :mod:`hyperactive.experiment.integrations` module contains experiment classe
77
for integration with machine learning frameworks.
88

99
These experiments provide seamless hyperparameter optimization for scikit-learn,
10-
sktime, skpro, and PyTorch Lightning models.
10+
sktime, skpro, PyTorch Lightning, and LightGBM models.
1111

1212
Scikit-Learn
1313
------------
@@ -55,3 +55,14 @@ Experiments for PyTorch Lightning models.
5555
:template: class.rst
5656

5757
TorchExperiment
58+
59+
LightGBM
60+
--------
61+
62+
Cross-validation experiments for LightGBM estimators.
63+
64+
.. autosummary::
65+
:toctree: auto_generated/
66+
:template: class.rst
67+
68+
LightGBMExperiment

docs/source/user_guide/integrations.rst

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Framework Integrations
77
Hyperactive integrates with popular ML frameworks, providing drop-in replacements
88
for tools like ``GridSearchCV``. Each ML framework has its own conventions for training and evaluation. The integration
99
classes handle cross-validation setup, scoring metrics, and parameter translation, so
10-
you can use any optimizer with scikit-learn, sktime, skpro, or PyTorch models.
10+
you can use any optimizer with scikit-learn, sktime, skpro, PyTorch, or LightGBM models.
1111

1212
----
1313

@@ -53,6 +53,15 @@ Supported Frameworks
5353

5454
Deep learning models
5555

56+
.. grid-item-card:: LightGBM
57+
:class-card: sd-border-info
58+
:link: #lightgbm-integration
59+
:link-type: url
60+
61+
**LightGBMExperiment**
62+
63+
Gradient boosting models
64+
5665
----
5766

5867
Quick Reference
@@ -237,6 +246,34 @@ For deep learning hyperparameter optimization with PyTorch Lightning:
237246

238247
----
239248

249+
LightGBM Integration
250+
--------------------
251+
252+
For gradient boosting hyperparameter optimization with LightGBM:
253+
254+
.. note::
255+
256+
Requires ``pip install lightgbm``
257+
258+
.. grid:: 1
259+
:gutter: 0
260+
261+
.. grid-item::
262+
:class: sd-bg-light sd-pt-3 sd-pb-1 sd-ps-3 sd-pe-3 sd-rounded-3
263+
264+
**Key Features**
265+
266+
- Optimize LightGBM classifiers and regressors
267+
- LightGBM follows the sklearn API, so cross-validation works out of the box
268+
- Supports all LightGBM hyperparameters (``n_estimators``, ``max_depth``, ``learning_rate``, etc.)
269+
270+
.. literalinclude:: ../_snippets/user_guide/integrations.py
271+
:language: python
272+
:start-after: # [start:lightgbm_experiment]
273+
:end-before: # [end:lightgbm_experiment]
274+
275+
----
276+
240277
Tips
241278
----
242279

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ all_extras = [
9999
"optuna<5",
100100
"cmaes", # Required for CmaEsOptimizer (optuna's CMA-ES sampler)
101101
"lightning",
102+
"lightgbm",
102103
]
103104

104105

src/hyperactive/base/tests/test_endtoend.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,51 @@ def test_endtoend_hillclimbing():
4747
assert isinstance(best_params, dict), "Best parameters should be a dictionary"
4848
assert "C" in best_params, "Best parameters should contain 'C'"
4949
assert "gamma" in best_params, "Best parameters should contain 'gamma'"
50+
51+
52+
def test_endtoend_lightgbm():
53+
"""Test end-to-end usage of HillClimbing optimizer with LightGBM experiment."""
54+
from skbase.utils.dependencies import _check_soft_dependencies
55+
56+
if not _check_soft_dependencies("lightgbm", severity="none"):
57+
return None
58+
59+
# 1. define the experiment
60+
from lightgbm import LGBMClassifier
61+
from sklearn.datasets import load_iris
62+
63+
from hyperactive.experiment.integrations import LightGBMExperiment
64+
65+
X, y = load_iris(return_X_y=True)
66+
67+
lgbm_exp = LightGBMExperiment(
68+
estimator=LGBMClassifier(n_estimators=10, verbosity=-1),
69+
X=X,
70+
y=y,
71+
cv=2,
72+
)
73+
74+
# 2. set up the HillClimbing optimizer
75+
import numpy as np
76+
77+
from hyperactive.opt import HillClimbing
78+
79+
hillclimbing_config = {
80+
"search_space": {
81+
"n_estimators": np.array([5, 10, 20]),
82+
"max_depth": np.array([2, 3, 5]),
83+
},
84+
"n_iter": 10,
85+
}
86+
hill_climbing = HillClimbing(**hillclimbing_config, experiment=lgbm_exp)
87+
88+
# 3. run the HillClimbing optimizer
89+
hill_climbing.solve()
90+
91+
best_params = hill_climbing.best_params_
92+
assert best_params is not None, "Best parameters should not be None"
93+
assert isinstance(best_params, dict), "Best parameters should be a dictionary"
94+
assert (
95+
"n_estimators" in best_params
96+
), "Best parameters should contain 'n_estimators'"
97+
assert "max_depth" in best_params, "Best parameters should contain 'max_depth'"

src/hyperactive/experiment/integrations/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Integrations with packages for tuning."""
2+
23
# copyright: hyperactive developers, MIT License (see LICENSE file)
34

5+
from hyperactive.experiment.integrations.lightgbm_experiment import LightGBMExperiment
46
from hyperactive.experiment.integrations.sklearn_cv import SklearnCvExperiment
57
from hyperactive.experiment.integrations.skpro_probareg import (
68
SkproProbaRegExperiment,
@@ -16,6 +18,7 @@
1618
)
1719

1820
__all__ = [
21+
"LightGBMExperiment",
1922
"SklearnCvExperiment",
2023
"SkproProbaRegExperiment",
2124
"SktimeClassificationExperiment",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Experiment adapter for LightGBM cross-validation experiments."""
2+
3+
# copyright: hyperactive developers, MIT License (see LICENSE file)
4+
5+
from hyperactive.experiment.integrations.sklearn_cv import SklearnCvExperiment
6+
7+
8+
class LightGBMExperiment(SklearnCvExperiment):
9+
"""Experiment adapter for LightGBM cross-validation experiments.
10+
11+
Thin wrapper around ``SklearnCvExperiment`` for LightGBM estimators.
12+
LightGBM's sklearn-compatible API (``LGBMClassifier``, ``LGBMRegressor``)
13+
works without adaptation. This class exists for discoverability, explicit
14+
soft-dependency tracking via the ``python_dependencies`` tag, and as an
15+
extension point for future LightGBM-specific behavior.
16+
17+
Parameters
18+
----------
19+
estimator : LGBMClassifier or LGBMRegressor
20+
The LightGBM estimator to evaluate. Any sklearn-compatible estimator
21+
is accepted, but LightGBM estimators are the intended use case.
22+
X : array-like, shape (n_samples, n_features)
23+
Input data.
24+
y : array-like, shape (n_samples,)
25+
Target values.
26+
scoring : callable or str, default=None
27+
Scoring function. Defaults follow ``SklearnCvExperiment`` conventions:
28+
``accuracy_score`` for classifiers, ``mean_squared_error`` for
29+
regressors.
30+
cv : int or cross-validation generator, default=KFold(n_splits=3, shuffle=True)
31+
Cross-validation strategy.
32+
33+
Notes
34+
-----
35+
LightGBM prints training logs to stdout by default. Pass
36+
``verbosity=-1`` to the estimator constructor to suppress this output.
37+
38+
For all remaining parameter details see ``SklearnCvExperiment``.
39+
40+
Examples
41+
--------
42+
>>> from hyperactive.experiment.integrations import LightGBMExperiment
43+
>>> from lightgbm import LGBMClassifier
44+
>>> from sklearn.datasets import load_iris
45+
>>> X, y = load_iris(return_X_y=True)
46+
>>> exp = LightGBMExperiment(
47+
... estimator=LGBMClassifier(verbosity=-1),
48+
... X=X,
49+
... y=y,
50+
... )
51+
>>> params = {"n_estimators": 50, "max_depth": 3}
52+
>>> score, metadata = exp.score(params)
53+
"""
54+
55+
_tags = {
56+
"authors": ["kajal-jotwani"],
57+
"python_dependencies": "lightgbm",
58+
}
59+
60+
@classmethod
61+
def get_test_params(cls, parameter_set="default"):
62+
"""Return testing parameter settings for the estimator."""
63+
from skbase.utils.dependencies import _check_soft_dependencies
64+
65+
if not _check_soft_dependencies("lightgbm", severity="none"):
66+
return []
67+
68+
from lightgbm import LGBMClassifier, LGBMRegressor
69+
from sklearn.datasets import load_diabetes, load_iris
70+
71+
X, y = load_iris(return_X_y=True)
72+
params0 = {
73+
"estimator": LGBMClassifier(n_estimators=10, verbosity=-1),
74+
"X": X,
75+
"y": y,
76+
"cv": 2,
77+
}
78+
79+
X, y = load_diabetes(return_X_y=True)
80+
params1 = {
81+
"estimator": LGBMRegressor(n_estimators=10, verbosity=-1),
82+
"X": X,
83+
"y": y,
84+
"cv": 2,
85+
}
86+
87+
return [params0, params1]
88+
89+
@classmethod
90+
def _get_score_params(cls):
91+
"""Return parameter settings for score/evaluate tests."""
92+
from skbase.utils.dependencies import _check_soft_dependencies
93+
94+
if not _check_soft_dependencies("lightgbm", severity="none"):
95+
return []
96+
97+
score_params = {"n_estimators": 5, "max_depth": 2}
98+
return [score_params, score_params]

0 commit comments

Comments
 (0)