Skip to content

Commit d9fcea3

Browse files
committed
merge
2 parents 410025b + 75a579b commit d9fcea3

14 files changed

Lines changed: 438 additions & 53 deletions

File tree

.github/workflows/test.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ jobs:
4242
4343
make install-no-extras-for-test
4444
45+
- name: Show dependencies
46+
run: python -m pip list
47+
4548
- name: Test with pytest
4649
run: |
4750
python -m pytest tests -p no:warnings
@@ -77,6 +80,9 @@ jobs:
7780
7881
make install-all-extras-for-test
7982
83+
- name: Show dependencies
84+
run: python -m pip list
85+
8086
- name: Test with pytest
8187
run: |
8288
python -m pytest tests --cov=hyperactive --cov-report=term-missing --cov-report=xml -p no:warnings
@@ -86,7 +92,7 @@ jobs:
8692
python -m pytest src/hyperactive -p no:warnings
8793
8894
test-sklearn-versions:
89-
name: test-sklearn-${{ matrix.sklearn-version }} python-${{ matrix.python-version }}
95+
name: test-sklearn-${{ matrix.sklearn-version }}-python-${{ matrix.python-version }}
9096
runs-on: ubuntu-latest
9197

9298
strategy:
@@ -98,18 +104,19 @@ jobs:
98104
steps:
99105
- uses: actions/checkout@v4
100106

101-
- name: Set up Python 3.12
107+
- name: Set up Python ${{ matrix.python-version }}
102108
uses: actions/setup-python@v5
103109
with:
104110
python-version: ${{ matrix.python-version }}
105111

106112
- name: Install dependencies for scikit-learn ${{ matrix.sklearn-version }}
107113
run: |
108114
python -m pip install --upgrade pip
109-
python -m pip install build pytest
110-
make install
111-
python -m pip install scikit-learn==${{ matrix.sklearn-version }}
115+
python -m pip install .[all_extras,test] scikit-learn==${{ matrix.sklearn-version }}
116+
117+
- name: Show dependencies
118+
run: python -m pip list
112119

113120
- name: Run sklearn integration tests for ${{ matrix.sklearn-version }}
114121
run: |
115-
python -m pytest -x -p no:warnings tests/integrations/sklearn/
122+
python -m pytest -x -p no:warnings tests/integrations/sklearn/

extension_templates/experiments.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ class MyExperiment(BaseExperiment):
7575
#
7676
"property:randomness": "random",
7777
# valid values: "random", "deterministic"
78-
# if "deterministic", two calls of score must result in the same value
78+
# if "deterministic", two calls of "evaluate" must result in the same value
79+
#
80+
"property:higher_or_lower_is_better": "lower",
81+
# valid values: "higher", "lower", "mixed"
82+
# whether higher or lower returns of "evaluate" are better
7983
#
8084
# --------------
8185
# packaging info
@@ -147,25 +151,25 @@ def _paramnames(self):
147151
return ["score_param1", "score_param2"]
148152

149153
# todo: implement this, mandatory
150-
def _score(self, params):
151-
"""Score the parameters.
154+
def _evaluate(self, params):
155+
"""Evaluate the parameters.
152156
153157
Parameters
154158
----------
155159
params : dict with string keys
156-
Parameters to score.
160+
Parameters to evaluate.
157161
158162
Returns
159163
-------
160164
float
161-
The score of the parameters.
165+
The value of the parameters as per evaluation.
162166
dict
163167
Additional metadata about the search.
164168
"""
165169
# params is a dictionary with keys being paramnames or subset thereof
166170
# IMPORTANT: avoid side effects to params!
167171
#
168-
# the method may work if only a subste of the parameters in paramnames is passed
172+
# the method may work if only a subset of the parameters in paramnames is passed
169173
# but this is not necessary
170174
value = 42 # must be numpy.float64
171175
metadata = {"some": "metadata"} # can be any dict
@@ -230,18 +234,19 @@ def get_test_params(cls, parameter_set="default"):
230234

231235
@classmethod
232236
def _get_score_params(self):
233-
"""Return settings for testing the score function. Used in tests only.
237+
"""Return settings for testing score/evaluate functions. Used in tests only.
234238
235-
Returns a list, the i-th element corresponds to self.get_test_params()[i].
236-
It should be a valid call for self.score.
239+
Returns a list, the i-th element should be valid arguments for
240+
self.evaluate and self.score, of an instance constructed with
241+
self.get_test_params()[i].
237242
238243
Returns
239244
-------
240245
list of dict
241246
The parameters to be used for scoring.
242247
"""
243248
# dict keys should be same as paramnames return
244-
# or subset, only if _score allows for subsets of parameters
249+
# or subset, only if _evaluate allows for subsets of parameters
245250
score_params1 = {"score_param1": "foo", "score_param2": "bar"}
246251
score_params2 = {"score_param1": "baz", "score_param2": "qux"}
247252
return [score_params1, score_params2]

extension_templates/optimizers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ def _paramnames(self):
142142
return ["score_param1", "score_param2"]
143143

144144
# optional: implement this to prepare arguments for _run
145-
# the default is all parameters passed to __init__, except ex
145+
# the default is all parameters passed to __init__, minus the experiment
146+
# the result of this is passed to _run as search_config
146147
def get_search_config(self):
147148
"""Get the search configuration.
148149
@@ -153,12 +154,15 @@ def get_search_config(self):
153154
"""
154155
# the default
155156
search_config = super().get_search_config()
157+
# example of adding a new parameter to the search config
158+
# this is optional, but can be useful for clean separation or API interfacing
156159
search_config["one_more_param"] = 42
160+
# this return is available in _run as search_config
157161
return search_config
158162

159163
# todo: implement this, mandatory
160164
def _run(self, experiment, **search_config):
161-
"""Run the optimization search process.
165+
"""Run the optimization search process to maximize the experiment's score.
162166
163167
Parameters
164168
----------
@@ -173,6 +177,8 @@ def _run(self, experiment, **search_config):
173177
The best parameters found during the search.
174178
Must have keys a subset or identical to experiment.paramnames().
175179
"""
180+
# important: the search logic should *maximize* the experiment's score
181+
# this is the main method to implement, it should return the best parameters
176182
best_params = {"write_some_logic_to_get": "best_params"}
177183
return best_params
178184

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ dependencies = [
4242
"pandas <3.0.0",
4343
"gradient-free-optimizers >=1.2.4, <2.0.0",
4444
"scikit-base <1.0.0",
45-
"scikit-learn <1.7.0",
45+
"scikit-learn <1.8.0",
4646
]
4747

4848
[project.optional-dependencies]
4949
sklearn-integration = [
50-
"scikit-learn <1.7.0",
50+
"scikit-learn <1.8.0",
5151
]
5252
build = [
5353
"setuptools",
5454
"build",
5555
"wheel",
5656
]
5757
test = [
58-
"pytest == 8.4.0",
58+
"pytest == 8.4.1",
5959
"flake8",
6060
"pytest-cov",
6161
"pathos",

requirements/requirements-test.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pytest == 8.4.0
1+
pytest == 8.4.1
22
flake8
33
pytest-cov
44
pathos

src/hyperactive/base/_experiment.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ class BaseExperiment(BaseObject):
1414
"property:randomness": "random", # random or deterministic
1515
# if deterministic, two calls of score will result in the same value
1616
# random = two calls may result in different values; same as "stochastic"
17+
"property:higher_or_lower_is_better": "higher", # "higher", "lower", "mixed"
18+
# whether higher or lower scores are better
1719
}
1820

1921
def __init__(self):
2022
super().__init__()
2123

2224
def __call__(self, **kwargs):
23-
"""Score parameters, with kwargs call."""
25+
"""Score parameters, with kwargs call. Same as score call."""
2426
score, _ = self.score(kwargs)
2527
return score
2628

@@ -48,30 +50,55 @@ def _paramnames(self):
4850
"""
4951
raise NotImplementedError
5052

51-
def score(self, params):
52-
"""Score the parameters.
53+
def evaluate(self, params):
54+
"""Evaluate the parameters.
5355
5456
Parameters
5557
----------
5658
params : dict with string keys
57-
Parameters to score.
59+
Parameters to evaluate.
5860
5961
Returns
6062
-------
6163
float
62-
The score of the parameters.
64+
The value of the parameters as per evaluation.
6365
dict
6466
Additional metadata about the search.
6567
"""
6668
paramnames = self.paramnames()
6769
if not set(params.keys()) <= set(paramnames):
6870
raise ValueError("Parameters do not match.")
69-
res, metadata = self._score(params)
71+
res, metadata = self._evaluate(params)
7072
res = np.float64(res)
7173
return res, metadata
7274

73-
def _score(self, params):
74-
"""Score the parameters.
75+
def _evaluate(self, params):
76+
"""Evaluate the parameters.
77+
78+
Parameters
79+
----------
80+
params : dict with string keys
81+
Parameters to evaluate.
82+
83+
Returns
84+
-------
85+
float
86+
The value of the parameters as per evaluation.
87+
dict
88+
Additional metadata about the search.
89+
"""
90+
raise NotImplementedError
91+
92+
def score(self, params):
93+
"""Score the parameters - with sign such that higher is always better.
94+
95+
Same as ``evaluate`` call except for the sign chosen so that higher is better.
96+
97+
If the tag ``property:higher_or_lower_is_better`` is set to
98+
``"lower"``, the result is ``-self.evaluate(params)``.
99+
100+
If the tag is set to ``"higher"``, the result is
101+
identical to ``self.evaluate(params)``.
75102
76103
Parameters
77104
----------
@@ -85,4 +112,14 @@ def _score(self, params):
85112
dict
86113
Additional metadata about the search.
87114
"""
88-
raise NotImplementedError
115+
hib = self.get_tag("property:higher_or_lower_is_better", "lower")
116+
if hib == "higher":
117+
sign = 1
118+
elif hib == "lower":
119+
sign = -1
120+
121+
eval_res = self.evaluate(params)
122+
value = eval_res[0]
123+
metadata = eval_res[1]
124+
125+
return sign * value, metadata

src/hyperactive/base/_optimizer.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,23 @@ def get_experiment(self):
5252
return self._experiment
5353

5454
def run(self):
55-
"""Run the optimization search process.
55+
"""Run the optimization search process to maximize the experiment's score.
56+
57+
The optimization searches for a maximizer of the experiment's
58+
``score`` method.
59+
60+
Depending on the tag ``property:higher_or_lower_is_better`` being
61+
set to ``higher`` or ``lower``, the ``run`` method will search for:
62+
63+
* the minimizer of the ``evaluate`` method if the tag is ``lower``
64+
* the maximizer of the ``evaluate`` method if the tag is ``higher``
5665
5766
Returns
5867
-------
5968
best_params : dict
6069
The best parameters found during the optimization process.
70+
The dict ``best_params`` can be used in ``experiment.score`` or
71+
``experiment.evaluate`` directly.
6172
"""
6273
experiment = self.get_experiment()
6374
search_config = self.get_search_config()

0 commit comments

Comments
 (0)