Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NoisyExpectedImprovementMixin #1939

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions botorch/acquisition/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from botorch.acquisition.logei import (
LogImprovementMCAcquisitionFunction,
qLogExpectedImprovement,
qLogNoisyExpectedImprovement,
)
from botorch.acquisition.max_value_entropy_search import (
MaxValueBase,
Expand Down Expand Up @@ -96,6 +97,7 @@
"qExpectedImprovement",
"LogImprovementMCAcquisitionFunction",
"qLogExpectedImprovement",
"qLogNoisyExpectedImprovement",
"qKnowledgeGradient",
"MaxValueBase",
"qMultiFidelityKnowledgeGradient",
Expand Down
152 changes: 149 additions & 3 deletions botorch/acquisition/input_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@
qKnowledgeGradient,
qMultiFidelityKnowledgeGradient,
)
from botorch.acquisition.logei import qLogExpectedImprovement
from botorch.acquisition.logei import (
qLogExpectedImprovement,
qLogNoisyExpectedImprovement,
TAU_MAX,
TAU_RELU,
)
from botorch.acquisition.max_value_entropy_search import (
qMaxValueEntropy,
qMultiFidelityMaxValueEntropy,
Expand Down Expand Up @@ -450,7 +455,7 @@ def construct_inputs_qSimpleRegret(
)


@acqf_input_constructor(qExpectedImprovement, qLogExpectedImprovement)
@acqf_input_constructor(qExpectedImprovement)
def construct_inputs_qEI(
model: Model,
training_data: MaybeDict[SupervisedDataset],
Expand Down Expand Up @@ -508,6 +513,72 @@ def construct_inputs_qEI(
return {**base_inputs, "best_f": best_f, "constraints": constraints, "eta": eta}


@acqf_input_constructor(qLogExpectedImprovement)
def construct_inputs_qLogEI(
model: Model,
training_data: MaybeDict[SupervisedDataset],
objective: Optional[MCAcquisitionObjective] = None,
posterior_transform: Optional[PosteriorTransform] = None,
X_pending: Optional[Tensor] = None,
sampler: Optional[MCSampler] = None,
best_f: Optional[Union[float, Tensor]] = None,
constraints: Optional[List[Callable[[Tensor], Tensor]]] = None,
eta: Union[Tensor, float] = 1e-3,
fat: bool = True,
tau_max: float = TAU_MAX,
tau_relu: float = TAU_RELU,
**ignored: Any,
) -> Dict[str, Any]:
r"""Construct kwargs for the `qExpectedImprovement` constructor.

Args:
model: The model to be used in the acquisition function.
training_data: Dataset(s) used to train the model.
objective: The objective to be used in the acquisition function.
posterior_transform: The posterior transform to be used in the
acquisition function.
X_pending: A `m x d`-dim Tensor of `m` design points that have been
submitted for function evaluation but have not yet been evaluated.
Concatenated into X upon forward call.
sampler: The sampler used to draw base samples. If omitted, uses
the acquisition functions's default sampler.
best_f: Threshold above (or below) which improvement is defined.
constraints: A list of constraint callables which map a Tensor of posterior
samples of dimension `sample_shape x batch-shape x q x m`-dim to a
`sample_shape x batch-shape x q`-dim Tensor. The associated constraints
are considered satisfied if the output is less than zero.
eta: Temperature parameter(s) governing the smoothness of the sigmoid
approximation to the constraint indicators. For more details, on this
parameter, see the docs of `compute_smoothed_feasibility_indicator`.
fat: Toggles the logarithmic / linear asymptotic behavior of the smooth
approximation to the ReLU.
tau_max: Temperature parameter controlling the sharpness of the smooth
approximations to max.
tau_relu: Temperature parameter controlling the sharpness of the smooth
approximations to ReLU.
ignored: Not used.

Returns:
A dict mapping kwarg names of the constructor to values.
"""
return {
**construct_inputs_qEI(
model=model,
training_data=training_data,
objective=objective,
posterior_transform=posterior_transform,
X_pending=X_pending,
sampler=sampler,
best_f=best_f,
constraints=constraints,
eta=eta,
),
"fat": fat,
"tau_max": tau_max,
"tau_relu": tau_relu,
}


@acqf_input_constructor(qNoisyExpectedImprovement)
def construct_inputs_qNEI(
model: Model,
Expand Down Expand Up @@ -570,7 +641,6 @@ def construct_inputs_qNEI(
assert_shared=True,
first_only=True,
)

return {
**base_inputs,
"X_baseline": X_baseline,
Expand All @@ -581,6 +651,82 @@ def construct_inputs_qNEI(
}


@acqf_input_constructor(qLogNoisyExpectedImprovement)
def construct_inputs_qLogNEI(
model: Model,
training_data: MaybeDict[SupervisedDataset],
objective: Optional[MCAcquisitionObjective] = None,
posterior_transform: Optional[PosteriorTransform] = None,
X_pending: Optional[Tensor] = None,
sampler: Optional[MCSampler] = None,
X_baseline: Optional[Tensor] = None,
prune_baseline: Optional[bool] = True,
cache_root: Optional[bool] = True,
constraints: Optional[List[Callable[[Tensor], Tensor]]] = None,
eta: Union[Tensor, float] = 1e-3,
fat: bool = True,
tau_max: float = TAU_MAX,
tau_relu: float = TAU_RELU,
**ignored: Any,
):
r"""Construct kwargs for the `qNoisyExpectedImprovement` constructor.

Args:
model: The model to be used in the acquisition function.
training_data: Dataset(s) used to train the model.
objective: The objective to be used in the acquisition function.
posterior_transform: The posterior transform to be used in the
acquisition function.
X_pending: A `m x d`-dim Tensor of `m` design points that have been
submitted for function evaluation but have not yet been evaluated.
Concatenated into X upon forward call.
sampler: The sampler used to draw base samples. If omitted, uses
the acquisition functions's default sampler.
X_baseline: A `batch_shape x r x d`-dim Tensor of `r` design points
that have already been observed. These points are considered as
the potential best design point. If omitted, checks that all
training_data have the same input features and take the first `X`.
prune_baseline: If True, remove points in `X_baseline` that are
highly unlikely to be the best point. This can significantly
improve performance and is generally recommended.
constraints: A list of constraint callables which map a Tensor of posterior
samples of dimension `sample_shape x batch-shape x q x m`-dim to a
`sample_shape x batch-shape x q`-dim Tensor. The associated constraints
are considered satisfied if the output is less than zero.
eta: Temperature parameter(s) governing the smoothness of the sigmoid
approximation to the constraint indicators. For more details, on this
parameter, see the docs of `compute_smoothed_feasibility_indicator`.
fat: Toggles the logarithmic / linear asymptotic behavior of the smooth
approximation to the ReLU.
tau_max: Temperature parameter controlling the sharpness of the smooth
approximations to max.
tau_relu: Temperature parameter controlling the sharpness of the smooth
approximations to ReLU.
ignored: Not used.

Returns:
A dict mapping kwarg names of the constructor to values.
"""
return {
**construct_inputs_qNEI(
model=model,
training_data=training_data,
objective=objective,
posterior_transform=posterior_transform,
X_pending=X_pending,
sampler=sampler,
X_baseline=X_baseline,
prune_baseline=prune_baseline,
cache_root=cache_root,
constraint=constraints,
eta=eta,
),
"fat": fat,
"tau_max": tau_max,
"tau_relu": tau_relu,
}


@acqf_input_constructor(qProbabilityOfImprovement)
def construct_inputs_qPI(
model: Model,
Expand Down
137 changes: 134 additions & 3 deletions botorch/acquisition/logei.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
Batch implementations of the LogEI family of improvements-based acquisition functions.
"""


from __future__ import annotations

from functools import partial

from typing import Callable, List, Optional, TypeVar, Union
from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union

import torch
from botorch.acquisition.monte_carlo import SampleReducingMCAcquisitionFunction
from botorch.acquisition.monte_carlo import (
NoisyExpectedImprovementMixin,
SampleReducingMCAcquisitionFunction,
)
from botorch.acquisition.objective import (
ConstrainedMCObjective,
MCAcquisitionObjective,
Expand Down Expand Up @@ -219,6 +221,135 @@ def _sample_forward(self, obj: Tensor) -> Tensor:
return li


class qLogNoisyExpectedImprovement(
LogImprovementMCAcquisitionFunction, NoisyExpectedImprovementMixin
):
r"""MC-based batch Log Noisy Expected Improvement.

This function does not assume a `best_f` is known (which would require
noiseless observations). Instead, it uses samples from the joint posterior
over the `q` test points and previously observed points. A smooth approximation
to the canonical improvement over previously observed points is computed
for each sample and the logarithm of the average is returned.

`qLogNEI(X) ~ log(qNEI(X)) = Log E(max(max Y - max Y_baseline, 0))`, where
`(Y, Y_baseline) ~ f((X, X_baseline)), X = (x_1,...,x_q)`

Example:
>>> model = SingleTaskGP(train_X, train_Y)
>>> sampler = SobolQMCNormalSampler(1024)
>>> qLogNEI = qLogNoisyExpectedImprovement(model, train_X, sampler)
>>> acqval = qLogNEI(test_X)
"""

def __init__(
self,
model: Model,
X_baseline: Tensor,
sampler: Optional[MCSampler] = None,
objective: Optional[MCAcquisitionObjective] = None,
posterior_transform: Optional[PosteriorTransform] = None,
X_pending: Optional[Tensor] = None,
constraints: Optional[List[Callable[[Tensor], Tensor]]] = None,
eta: Union[Tensor, float] = 1e-3,
fat: bool = True,
prune_baseline: bool = False,
cache_root: bool = True,
tau_max: float = TAU_MAX,
tau_relu: float = TAU_RELU,
**kwargs: Any,
) -> None:
r"""q-Noisy Expected Improvement.

Args:
model: A fitted model.
X_baseline: A `batch_shape x r x d`-dim Tensor of `r` design points
that have already been observed. These points are considered as
the potential best design point.
sampler: The sampler used to draw base samples. See `MCAcquisitionFunction`
more details.
objective: The MCAcquisitionObjective under which the samples are
evaluated. Defaults to `IdentityMCObjective()`.
posterior_transform: A PosteriorTransform (optional).
X_pending: A `batch_shape x m x d`-dim Tensor of `m` design points
that have points that have been submitted for function evaluation
but have not yet been evaluated. Concatenated into `X` upon
forward call. Copied and set to have no gradient.
constraints: A list of constraint callables which map a Tensor of posterior
samples of dimension `sample_shape x batch-shape x q x m`-dim to a
`sample_shape x batch-shape x q`-dim Tensor. The associated constraints
are satisfied if `constraint(samples) < 0`.
eta: Temperature parameter(s) governing the smoothness of the sigmoid
approximation to the constraint indicators. See the docs of
`compute_(log_)smoothed_constraint_indicator` for details.
fat: Toggles the logarithmic / linear asymptotic behavior of the smooth
approximation to the ReLU.
prune_baseline: If True, remove points in `X_baseline` that are
highly unlikely to be the best point. This can significantly
improve performance and is generally recommended. In order to
customize pruning parameters, instead manually call
`botorch.acquisition.utils.prune_inferior_points` on `X_baseline`
before instantiating the acquisition function.
cache_root: A boolean indicating whether to cache the root
decomposition over `X_baseline` and use low-rank updates.
tau_max: Temperature parameter controlling the sharpness of the smooth
approximations to max.
tau_relu: Temperature parameter controlling the sharpness of the smooth
approximations to ReLU.
kwargs: Here for qNEI for compatibility.

TODO: similar to qNEHVI, when we are using sequential greedy candidate
selection, we could incorporate pending points X_baseline and compute
the incremental q(Log)NEI from the new point. This would greatly increase
efficiency for large batches.
"""
LogImprovementMCAcquisitionFunction.__init__(
self,
model=model,
sampler=sampler,
objective=objective,
posterior_transform=posterior_transform,
X_pending=X_pending,
constraints=constraints,
eta=eta,
fat=fat,
tau_max=tau_max,
)
self.tau_relu = tau_relu
NoisyExpectedImprovementMixin.__init__(
self,
model=model,
X_baseline=X_baseline,
sampler=sampler,
objective=objective,
posterior_transform=posterior_transform,
prune_baseline=prune_baseline,
cache_root=cache_root,
**kwargs,
)

def _sample_forward(self, obj: Tensor) -> Tensor:
r"""Evaluate qLogNoisyExpectedImprovement per sample on the candidate set `X`.

Args:
obj: `mc_shape x batch_shape x q`-dim Tensor of MC objective values.

Returns:
A `sample_shape x batch_shape x q`-dim Tensor of log noisy expected smoothed
improvement values.
"""
return _log_improvement(
Y=obj,
best_f=self.compute_best_f(obj),
tau=self.tau_relu,
fat=self._fat,
)

def _get_samples_and_objectives(self, X: Tensor) -> Tuple[Tensor, Tensor]:
# Explicit, as both parent classes have this method, so no MRO magic required.
return NoisyExpectedImprovementMixin._get_samples_and_objectives(self, X)


"""
###################################### utils ##########################################
"""
Expand Down
Loading