Skip to content

Commit 626aafe

Browse files
adrianmolzonphi-fridaytill-m
authored
Add logging with state to Bayesian Optimizer (#547)
* Add functionality to save and load state of the BayesianOptimization * Update basic-tour with new save and load functionality * move load stateful path to optional argument in class instantiation * add test for string params, update tests with new load functionality * updated basic tour with updated paths * add the random state to the set of things to list of saved items * move state loading to separate function, add functionality for saving acquisition function state * use new loading schema * update tests, add integration tests for saving and loading acquisition functions * undo abstractmethod implementation for get and set state saving functionality * reorganize state saving and loading for consistency * move integration tests into acquisition * remove unndecessary test, add tests for domain reduction and custom parameters * make test more comprehensive * add test logs * sync execution counts from basic tour * linting, whitespace removal, import structuring * ruff fix for string literal in error message * fix ruff complaints * make all side param comparisons almost equal to account for slight numpy differences * reformat array comparison check * upgrade poetry2.0 & apply pep621 (#545) * chore: upgrade poetry2.0 & apply pep621 * fix: replace poetry action(not support 2.0) * fix: exclude one matrix * chore: split numpy deps * fix: numpy constraints * fix: install root * chore: use install-poetry * Fix coverage report (#552) * remove unnecessary files, have acquisition baseclass functions raise errors * remove duplicate acquisition functions random state * ruff format * add type hints for base acquisition get/set functions * remove noreturn * remove former saving functionality from notebooks * increase legibility of custom acquisition example * explicitly stating the optionality of the saving and loading in custom acq functions --------- Co-authored-by: phi-friday <[email protected]> Co-authored-by: till-m <[email protected]>
1 parent aa9d018 commit 626aafe

7 files changed

+993
-167
lines changed

Diff for: .gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ docsrc/*.ipynb
3636
docsrc/static/*
3737
docsrc/README.md
3838

39-
poetry.lock
39+
poetry.lock
40+
41+
# Add log files and optimizer state files to gitignore
42+
examples/logs.log
43+
examples/optimizer_state.json

Diff for: bayes_opt/acquisition.py

+206
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,33 @@ def __init__(self, random_state: int | RandomState | None = None) -> None:
6969
self.random_state = RandomState()
7070
self.i = 0
7171

72+
def _serialize_random_state(self) -> dict | None:
73+
"""Convert random state to JSON serializable format."""
74+
if self.random_state is not None:
75+
state = self.random_state.get_state()
76+
return {
77+
"bit_generator": state[0],
78+
"state": state[1].tolist(), # Convert numpy array to list
79+
"pos": state[2],
80+
"has_gauss": state[3],
81+
"cached_gaussian": state[4],
82+
}
83+
return None
84+
85+
def _deserialize_random_state(self, state_dict: dict | None) -> None:
86+
"""Restore random state from JSON serializable format."""
87+
if state_dict is not None:
88+
if self.random_state is None:
89+
self.random_state = RandomState()
90+
state = (
91+
state_dict["bit_generator"],
92+
np.array(state_dict["state"], dtype=np.uint32),
93+
state_dict["pos"],
94+
state_dict["has_gauss"],
95+
state_dict["cached_gaussian"],
96+
)
97+
self.random_state.set_state(state)
98+
7299
@abc.abstractmethod
73100
def base_acq(self, *args: Any, **kwargs: Any) -> NDArray[Float]:
74101
"""Provide access to the base acquisition function."""
@@ -82,6 +109,34 @@ def _fit_gp(self, gp: GaussianProcessRegressor, target_space: TargetSpace) -> No
82109
if target_space.constraint is not None:
83110
target_space.constraint.fit(target_space.params, target_space._constraint_values)
84111

112+
def get_acquisition_params(self) -> dict[str, Any]:
113+
"""
114+
Get the parameters of the acquisition function.
115+
116+
Returns
117+
-------
118+
dict
119+
The parameters of the acquisition function.
120+
"""
121+
error_msg = (
122+
"Custom AcquisitionFunction subclasses must implement their own get_acquisition_params method."
123+
)
124+
raise NotImplementedError(error_msg)
125+
126+
def set_acquisition_params(self, **params) -> None:
127+
"""
128+
Set the parameters of the acquisition function.
129+
130+
Parameters
131+
----------
132+
**params : dict
133+
The parameters of the acquisition function.
134+
"""
135+
error_msg = (
136+
"Custom AcquisitionFunction subclasses must implement their own set_acquisition_params method."
137+
)
138+
raise NotImplementedError(error_msg)
139+
85140
def suggest(
86141
self,
87142
gp: GaussianProcessRegressor,
@@ -454,6 +509,34 @@ def decay_exploration(self) -> None:
454509
):
455510
self.kappa = self.kappa * self.exploration_decay
456511

512+
def get_acquisition_params(self) -> dict:
513+
"""Get the current acquisition function parameters.
514+
515+
Returns
516+
-------
517+
dict
518+
Dictionary containing the current acquisition function parameters.
519+
"""
520+
return {
521+
"kappa": self.kappa,
522+
"exploration_decay": self.exploration_decay,
523+
"exploration_decay_delay": self.exploration_decay_delay,
524+
"random_state": self._serialize_random_state(),
525+
}
526+
527+
def set_acquisition_params(self, params: dict) -> None:
528+
"""Set the acquisition function parameters.
529+
530+
Parameters
531+
----------
532+
params : dict
533+
Dictionary containing the acquisition function parameters.
534+
"""
535+
self.kappa = params["kappa"]
536+
self.exploration_decay = params["exploration_decay"]
537+
self.exploration_decay_delay = params["exploration_decay_delay"]
538+
self._deserialize_random_state(params["random_state"])
539+
457540

458541
class ProbabilityOfImprovement(AcquisitionFunction):
459542
r"""Probability of Improvement acqusition function.
@@ -587,6 +670,34 @@ def decay_exploration(self) -> None:
587670
):
588671
self.xi = self.xi * self.exploration_decay
589672

673+
def get_acquisition_params(self) -> dict:
674+
"""Get the current acquisition function parameters.
675+
676+
Returns
677+
-------
678+
dict
679+
Dictionary containing the current acquisition function parameters.
680+
"""
681+
return {
682+
"xi": self.xi,
683+
"exploration_decay": self.exploration_decay,
684+
"exploration_decay_delay": self.exploration_decay_delay,
685+
"random_state": self._serialize_random_state(),
686+
}
687+
688+
def set_acquisition_params(self, params: dict) -> None:
689+
"""Set the acquisition function parameters.
690+
691+
Parameters
692+
----------
693+
params : dict
694+
Dictionary containing the acquisition function parameters.
695+
"""
696+
self.xi = params["xi"]
697+
self.exploration_decay = params["exploration_decay"]
698+
self.exploration_decay_delay = params["exploration_decay_delay"]
699+
self._deserialize_random_state(params["random_state"])
700+
590701

591702
class ExpectedImprovement(AcquisitionFunction):
592703
r"""Expected Improvement acqusition function.
@@ -728,6 +839,34 @@ def decay_exploration(self) -> None:
728839
):
729840
self.xi = self.xi * self.exploration_decay
730841

842+
def get_acquisition_params(self) -> dict:
843+
"""Get the current acquisition function parameters.
844+
845+
Returns
846+
-------
847+
dict
848+
Dictionary containing the current acquisition function parameters.
849+
"""
850+
return {
851+
"xi": self.xi,
852+
"exploration_decay": self.exploration_decay,
853+
"exploration_decay_delay": self.exploration_decay_delay,
854+
"random_state": self._serialize_random_state(),
855+
}
856+
857+
def set_acquisition_params(self, params: dict) -> None:
858+
"""Set the acquisition function parameters.
859+
860+
Parameters
861+
----------
862+
params : dict
863+
Dictionary containing the acquisition function parameters.
864+
"""
865+
self.xi = params["xi"]
866+
self.exploration_decay = params["exploration_decay"]
867+
self.exploration_decay_delay = params["exploration_decay_delay"]
868+
self._deserialize_random_state(params["random_state"])
869+
731870

732871
class ConstantLiar(AcquisitionFunction):
733872
"""Constant Liar acquisition function.
@@ -918,6 +1057,38 @@ def suggest(
9181057

9191058
return x_max
9201059

1060+
def get_acquisition_params(self) -> dict:
1061+
"""Get the current acquisition function parameters.
1062+
1063+
Returns
1064+
-------
1065+
dict
1066+
Dictionary containing the current acquisition function parameters.
1067+
"""
1068+
return {
1069+
"dummies": [dummy.tolist() for dummy in self.dummies],
1070+
"base_acquisition_params": self.base_acquisition.get_acquisition_params(),
1071+
"strategy": self.strategy,
1072+
"atol": self.atol,
1073+
"rtol": self.rtol,
1074+
"random_state": self._serialize_random_state(),
1075+
}
1076+
1077+
def set_acquisition_params(self, params: dict) -> None:
1078+
"""Set the acquisition function parameters.
1079+
1080+
Parameters
1081+
----------
1082+
params : dict
1083+
Dictionary containing the acquisition function parameters.
1084+
"""
1085+
self.dummies = [np.array(dummy) for dummy in params["dummies"]]
1086+
self.base_acquisition.set_acquisition_params(params["base_acquisition_params"])
1087+
self.strategy = params["strategy"]
1088+
self.atol = params["atol"]
1089+
self.rtol = params["rtol"]
1090+
self._deserialize_random_state(params["random_state"])
1091+
9211092

9221093
class GPHedge(AcquisitionFunction):
9231094
"""GPHedge acquisition function.
@@ -1035,3 +1206,38 @@ def suggest(
10351206
self.previous_candidates = np.array(x_max)
10361207
idx = self._sample_idx_from_softmax_gains()
10371208
return x_max[idx]
1209+
1210+
def get_acquisition_params(self) -> dict:
1211+
"""Get the current acquisition function parameters.
1212+
1213+
Returns
1214+
-------
1215+
dict
1216+
Dictionary containing the current acquisition function parameters.
1217+
"""
1218+
return {
1219+
"base_acquisitions_params": [acq.get_acquisition_params() for acq in self.base_acquisitions],
1220+
"gains": self.gains.tolist(),
1221+
"previous_candidates": self.previous_candidates.tolist()
1222+
if self.previous_candidates is not None
1223+
else None,
1224+
"gphedge_random_state": self._serialize_random_state(),
1225+
}
1226+
1227+
def set_acquisition_params(self, params: dict) -> None:
1228+
"""Set the acquisition function parameters.
1229+
1230+
Parameters
1231+
----------
1232+
params : dict
1233+
Dictionary containing the acquisition function parameters.
1234+
"""
1235+
for acq, acq_params in zip(self.base_acquisitions, params["base_acquisitions_params"]):
1236+
acq.set_acquisition_params(acq_params)
1237+
1238+
self.gains = np.array(params["gains"])
1239+
self.previous_candidates = (
1240+
np.array(params["previous_candidates"]) if params["previous_candidates"] is not None else None
1241+
)
1242+
1243+
self._deserialize_random_state(params["gphedge_random_state"])

0 commit comments

Comments
 (0)