Skip to content
Open
Changes from all commits
Commits
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
150 changes: 137 additions & 13 deletions smac/acquisition/function/confidence_bound.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from abc import abstractmethod
from typing import Any

import numpy as np
Expand All @@ -9,16 +10,18 @@
)
from smac.utils.logging import get_logger

__copyright__ = "Copyright 2025, Leibniz University Hanover, Institute of AI"
__copyright__ = "Copyright 2022, automl.org"
__license__ = "3-clause BSD"

logger = get_logger(__name__)


class LCB(AbstractAcquisitionFunction):
r"""Computes the lower confidence bound for a given x over the best so far value as acquisition value.
class AbstractConfidenceBound(AbstractAcquisitionFunction):
r"""Computes the lower or upper confidence bound for a given x over the best so far value as acquisition value.

Example for LCB (UCB adds the variance term instead of subtracting it):

:math:`LCB(X) = \mu(\mathbf{X}) - \sqrt(\beta_t)\sigma(\mathbf{X})` [[SKKS10][SKKS10]]
:math:`LCB(X) = \mu(\mathbf{X}) - \sqrt(\beta_t)\sigma(\mathbf{X})` [SKKS10]_

with

Expand All @@ -42,26 +45,50 @@ class LCB(AbstractAcquisitionFunction):
Exploration-exploitation trade-off parameter.
_num_data : int
Number of data points seen so far.
_bound_type: str
Type of Confidence Bound. Either UCB or LCB. Set in child class.
_update_beta : bool
Whether to update beta or not.
_beta_scaling_srinivas : bool
Whether to use the beta scaling according to [0, 1].

References
----------
[0] Srinivas, Niranjan, et al. "Gaussian process optimization in the bandit setting: No regret and experimental
design." arXiv preprint arXiv:0912.3995 (2009). or not.
[1] Makarova, Anastasia, et al. "Automatic Termination for Hyperparameter Optimization." First Conference on
Automated Machine Learning (Main Track). 2022.

"""

def __init__(self, beta: float = 1.0) -> None:
super(LCB, self).__init__()
def __init__(
self, beta: float = 1.0, nu: float = 1.0, update_beta: bool = True, beta_scaling_srinivas: bool = False
) -> None:
super(AbstractConfidenceBound, self).__init__()
self._beta: float = beta
self._nu: float = nu
self._num_data: int | None = None
self._update_beta = update_beta
self._beta_scaling_srinivas = beta_scaling_srinivas

@property
@abstractmethod
def bound_type(self) -> str: # noqa: D102
...

@property
def name(self) -> str: # noqa: D102
return "Lower Confidence Bound"
return "Confidence Bound"

@property
def meta(self) -> dict[str, Any]: # noqa: D102
meta = super().meta
meta.update({"beta": self._beta})
meta.update({"beta": self._beta, "nu": self._nu})

return meta

def _update(self, **kwargs: Any) -> None:
"""Update acsquisition function attributes
"""Update acquisition function attributes

Parameters
----------
Expand All @@ -82,15 +109,24 @@ def _compute(self, X: np.ndarray) -> np.ndarray:

Returns
-------
np.ndarray [N,1]
Acquisition function values wrt X.
np.ndarray
Acquisition function values wrt X; shape [N,1].

Raises
------
ValueError
If `update` has not been called before. Number of data points is unspecified in this case.
"""
assert self._model is not None

if self.bound_type == "LCB":
sign = -1
elif self.bound_type == "UCB":
sign = 1
else:
raise ValueError(
f"Which confidence bound is supposed to be used? Use LCB or UCB. bound_type is {self.bound_type}."
)
if self._num_data is None:
raise ValueError(
"No current number of data points specified. Call `update` to inform the acquisition function."
Expand All @@ -101,6 +137,94 @@ def _compute(self, X: np.ndarray) -> np.ndarray:

m, var_ = self._model.predict_marginalized(X)
std = np.sqrt(var_)
beta_t = 2 * np.log((X.shape[1] * self._num_data**2) / self._beta)
if self._update_beta and not self._beta_scaling_srinivas:
beta_t = 2 * np.log((X.shape[1] * self._num_data**2) / self._beta)
elif self._update_beta and self._beta_scaling_srinivas:
beta_t = (2 * np.log((X.shape[1] * self._num_data**2 * np.pi**2) / (6 * self._beta))) / 5
else:
beta_t = self._beta

return -(m - np.sqrt(beta_t) * std)
return -(m + sign * np.sqrt(self._nu * beta_t) * std)


# Order of parents is important (priorities). This way _bound_type is correctly overwritten by the Mixin
class LCB(AbstractConfidenceBound):
r"""Computes the lower confidence bound for a given x over the best so far value as acquisition value.

:math:`LCB(X) = \mu(\mathbf{X}) - \sqrt(\beta_t)\sigma(\mathbf{X})` [SKKS10]_

with

:math:`\beta_t = 2 \log( |D| t^2 / \beta)`

:math:`\text{Input space} D`
:math:`\text{Number of input dimensions} |D|`
:math:`\text{Number of data points} t`
:math:`\text{Exploration/exploitation tradeoff} \beta`

Returns -LCB(X) as the acquisition_function optimizer maximizes the acquisition value.

Parameters
----------
beta : float, defaults to 1.0
Controls the balance between exploration and exploitation of the acquisition function.

Attributes
----------
_beta : float
Exploration-exploitation trade-off parameter.
_num_data : int
Number of data points seen so far.
_bound_type: str
Type of Confidence Bound. Either UCB or LCB.

"""

@property
def bound_type(self) -> str: # noqa: D102
return "LCB"

@property
def name(self) -> str: # noqa: D102
return "Lower Confidence Bound"


class UCB(AbstractConfidenceBound):
r"""Computes the upper confidence bound for a given x over the best so far value as acquisition value.

:math:`UCB(X) = \mu(\mathbf{X}) + \sqrt(\beta_t)\sigma(\mathbf{X})` [SKKS10]_

with

:math:`\beta_t = 2 \log( |D| t^2 / \beta)`

:math:`\text{Input space} D`
:math:`\text{Number of input dimensions} |D|`
:math:`\text{Number of data points} t`
:math:`\text{Exploration/exploitation tradeoff} \beta`

Returns -UCB(X) as the acquisition_function optimizer maximizes the acquisition value.

Parameters
----------
beta : float, defaults to 1.0
Controls the balance between exploration and exploitation of the acquisition function.

Attributes
----------
_beta : float
Exploration-exploitation trade-off parameter.
_num_data : int
Number of data points seen so far.
_bound_type: str
Type of Confidence Bound. Either UCB or LCB.

"""

@property
def bound_type(self) -> str: # noqa: D102
return "UCB"

@property
def name(self) -> str: # noqa: D102
return "Upper Confidence Bound"
Loading