Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Currenty **Xopt** provides:
optimization
- `multi_fidelity` Multi-fidelity single or multi objective optimization
- `BAX` Bayesian algorithm execution using virtual measurements
- `baxus` High-dimensional BO in adaptively expanding random subspaces
- BO customization:
- Trust region BO
- Heteroskedastic noise specification
Expand Down
18 changes: 17 additions & 1 deletion docs/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,27 @@ Bayesian Generators
All of the generators here use Bayesian optimization (BO) type methods to solve single
objective, multi objective and characterization problems. Bayesian generators
incorperate unknown constrianing functions into optimization based on what is
specified in `VOCS`
specified in `VOCS` (with the exception of `BAxUSGenerator`, see below).

- [`ExpectedImprovementGenerator`](examples/single_objective_bayes_opt/constrained_bo_tutorial.ipynb): implements Expected Improvement single
objective BO. Automatically balances trade-offs between exploration and
exploitation and is thus useful for general purpose optimization.
- [`BAxUSGenerator`](examples/single_objective_bayes_opt/baxus.ipynb): implements
Bayesian Optimization with Adaptively Expanding Subspaces (BAxUS,
Papenmeier et al., NeurIPS 2022) for high-dimensional single objective
problems. Optimization happens inside a low-dimensional random embedding of
the input space that expands adaptively when a trust region collapses,
making it effective when many input dimensions are irrelevant.
Because the model lives in the embedded subspace rather than in `VOCS` space,
this generator is more restricted than the others: it supports a single
objective over continuous variables only, and rejects constraints,
observables, discrete and contextual variables, `fixed_features`,
`max_travel_distances`, `custom_objective` and `n_interpolate_points`. It
generates one candidate at a time and does not support `visualize_model`.
Set `eval_budget` to the total number of planned evaluations - the reference
expansion schedule is derived from it, and without it a slower fallback
heuristic is used. Note also that the acquisition function is analytic LogEI
over the trust region rather than the Thompson sampling used in the paper.
- [`UpperConfidenceBoundGenerator`](examples/single_objective_bayes_opt/upper_confidence_bound.ipynb): implements Upper Confidence Bound single
objective BO. Requires a hyperparameter `beta` that explicitly sets the tradeoff
between exploration and exploitation. Default value of `beta=2` is a good
Expand Down
2 changes: 2 additions & 0 deletions docs/api/generators/bayesian/baxus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# BAxUS Generator
::: xopt.generators.bayesian.baxus
235 changes: 235 additions & 0 deletions docs/examples/single_objective_bayes_opt/baxus.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0776d0a0",
"metadata": {},
"source": [
"# High-dimensional Bayesian optimization with BAxUS\n",
"\n",
"BAxUS (Bayesian Optimization with Adaptively Expanding Subspaces,\n",
"[Papenmeier et al., NeurIPS 2022](https://arxiv.org/abs/2304.11468)) optimizes\n",
"high-dimensional problems by working in a low-dimensional random embedding of\n",
"the input space. A trust region controls local search in the embedded space;\n",
"when it collapses without progress, the embedding *expands*, adding dimensions\n",
"until (in the limit) it recovers the full input space. This makes BAxUS\n",
"effective when many input dimensions are irrelevant.\n",
"\n",
"This example optimizes a 100-dimensional problem in which only 3 dimensions\n",
"actually affect the objective.\n",
"\n",
"**Scope.** Because the model lives in the embedded subspace rather than in\n",
"`VOCS` space, this generator is more restricted than the other Bayesian\n",
"generators. It handles a single objective over continuous variables and rejects\n",
"constraints, observables, discrete and contextual variables, `fixed_features`,\n",
"`max_travel_distances`, `custom_objective` and `n_interpolate_points`. It\n",
"generates one candidate at a time, and `visualize_model` is not available.\n",
"\n",
"**Differences from the paper.** The acquisition function here is the analytic\n",
"LogEI inherited from `ExpectedImprovementGenerator`, evaluated over the trust\n",
"region, whereas the paper uses Thompson sampling over a masked discrete\n",
"candidate set. The initial subspace dimensionality is the user-set\n",
"`target_dim_init` rather than being derived from the input dimensionality.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e822fcb3",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"from xopt import Xopt, Evaluator\n",
"from xopt.vocs import VOCS\n",
"from xopt.generators.bayesian import BAxUSGenerator\n",
"\n",
"N_DIM = 100\n",
"N_EFFECTIVE = 3\n",
"N_STEPS = 60\n",
"\n",
"\n",
"def sphere_embedded(inputs: dict) -> dict:\n",
" \"\"\"Sphere on the first 3 coordinates; the other 97 are irrelevant.\"\"\"\n",
" x = np.array([inputs[f\"x{i}\"] for i in range(N_EFFECTIVE)])\n",
" return {\"f\": float(np.sum(x**2))}\n",
"\n",
"\n",
"vocs = VOCS(\n",
" variables={f\"x{i}\": [-1.0, 1.0] for i in range(N_DIM)},\n",
" objectives={\"f\": \"MINIMIZE\"},\n",
")\n",
"vocs"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "08e06987",
"metadata": {},
"outputs": [],
"source": [
"generator = BAxUSGenerator(\n",
" vocs=vocs,\n",
" target_dim_init=2,\n",
" seed=0,\n",
" # total planned evaluations: the expansion schedule is paced against this,\n",
" # so it should match the number of steps actually run below\n",
" eval_budget=N_STEPS,\n",
")\n",
"evaluator = Evaluator(function=sphere_embedded)\n",
"X = Xopt(evaluator=evaluator, generator=generator)\n",
"X"
]
},
{
"cell_type": "markdown",
"id": "6837a540",
"metadata": {},
"source": [
"## Running the optimization\n",
"\n",
"The generator draws its own Sobol seed points in the embedded space (no\n",
"`random_evaluate` needed), then switches to trust-region LogEI. The embedding\n",
"expands automatically when the trust region collapses."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59ce976b",
"metadata": {},
"outputs": [],
"source": [
"target_dims = []\n",
"for _ in range(N_STEPS):\n",
" X.step()\n",
" target_dims.append(X.generator.embedding.target_dim)\n",
"\n",
"print(f\"best f: {X.data['f'].min():.3e}\")\n",
"print(f\"embedding grew: {sorted(set(target_dims))}\")\n",
"X.data[[\"f\"]].tail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c081907",
"metadata": {},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"\n",
"fig, (ax0, ax1) = plt.subplots(2, 1, sharex=True, figsize=(6, 6))\n",
"best = X.data[\"f\"].cummin()\n",
"ax0.plot(best.values)\n",
"ax0.set_ylabel(\"best f\")\n",
"ax0.set_yscale(\"log\")\n",
"\n",
"ax1.step(range(len(target_dims)), target_dims, where=\"post\")\n",
"ax1.set_ylabel(\"embedding target_dim\")\n",
"ax1.set_xlabel(\"evaluation\")\n",
"\n",
"# mark each embedding expansion on both panels\n",
"for i in np.flatnonzero(np.diff(target_dims)) + 1:\n",
" for ax in (ax0, ax1):\n",
" ax.axvline(i, color=\"grey\", ls=\"--\", lw=0.8)\n",
"ax0.set_title(\"BAxUS: convergence and subspace growth\")\n",
"plt.tight_layout()"
]
},
{
"cell_type": "markdown",
"id": "b39a3c12",
"metadata": {},
"source": [
"## Serialization and resuming\n",
"\n",
"`BAxUSGenerator` owns its own state (the embedding, the trust region, and how\n",
"much history has already been folded into it), so it is a `StateOwner`: saving\n",
"and reloading through `Xopt` reattaches the data without replaying that history.\n",
"Round-tripping a checkpoint therefore leaves the run exactly where it was.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c0483bd",
"metadata": {},
"outputs": [],
"source": [
"checkpoint = X.yaml()\n",
"resumed = Xopt.from_yaml(checkpoint)\n",
"\n",
"print(\n",
" \"target_dim :\",\n",
" X.generator.embedding.target_dim,\n",
" \"->\",\n",
" resumed.generator.embedding.target_dim,\n",
")\n",
"print(\n",
" \"tr length :\",\n",
" X.generator.trust_region.length,\n",
" \"->\",\n",
" resumed.generator.trust_region.length,\n",
")\n",
"print(\n",
" \"rows folded:\",\n",
" X.generator.tr_observed_rows,\n",
" \"->\",\n",
" resumed.generator.tr_observed_rows,\n",
")\n",
"\n",
"resumed.step() # continues the run\n",
"len(resumed.data)"
]
},
{
"cell_type": "markdown",
"id": "fbb00bc6",
"metadata": {},
"source": [
"Dumping the generator on its own works too, but a trained generator's dump\n",
"carries the live botorch `model` and a `computation_time` frame, neither of\n",
"which is YAML-safe -- drop both before serializing by hand.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b36961c9",
"metadata": {},
"outputs": [],
"source": [
"import yaml\n",
"\n",
"dump = X.generator.model_dump()\n",
"dump.pop(\"model\", None)\n",
"dump.pop(\"computation_time\", None)\n",
"yaml.safe_dump(dump)[:200]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "xopt-dev",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Currenty **Xopt** provides:
optimization
- [`multi_fidelity`](examples/single_objective_bayes_opt/multi_fidelity_simple.ipynb) Multi-fidelity single or multi objective optimization
- [`BAX`](examples/single_objective_bayes_opt/bax_tutorial.ipynb) Bayesian algorithm execution using virtual measurements
- [`baxus`](examples/single_objective_bayes_opt/baxus.ipynb) High-dimensional BO in adaptively expanding random subspaces
- BO customization:
- [Trust region BO](examples/trust_region_bo/turbo_basics.ipynb)
- [Heteroskedastic noise specification](examples/single_objective_bayes_opt/heteroskedastic_noise_tutorial.ipynb)
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ nav:
- Fast model evaluation: examples/single_objective_bayes_opt/fast_model_eval.ipynb
- Hessian kernel: examples/single_objective_bayes_opt/hessian_kernel.ipynb
- Contextual Bayesian optimization: examples/single_objective_bayes_opt/contextual_bo.ipynb
- BAxUS high-dimensional optimization: examples/single_objective_bayes_opt/baxus.ipynb
- Trust Region Bayesian Optimization:
- Basic example: examples/trust_region_bo/turbo_basics.ipynb
- Optimization: examples/trust_region_bo/turbo_optimize.ipynb
Expand Down Expand Up @@ -82,6 +83,7 @@ nav:
- Time-Dependent Bayesian Generators: api/generators/bayesian/time_dependent.md
- Bayesian Exploration: api/generators/bayesian/bayesian_exploration.md
- Expected Improvement: api/generators/bayesian/expected_improvement.md
- BAxUS (high-dimensional): api/generators/bayesian/baxus.md
- Multi-Objective BO: api/generators/bayesian/mobo.md
- Multi-Generation Gaussian Process Optimization: api/generators/bayesian/mggpo.md
- Multi-Fidelity: api/generators/bayesian/multi_fidelity.md
Expand Down
3 changes: 3 additions & 0 deletions xopt/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"expected_improvement",
"multi_fidelity",
"bax",
"baxus",
},
"ga": {"cnsga", "nsga2"},
"es": {"extremum_seeking"},
Expand Down Expand Up @@ -88,6 +89,7 @@ def get_generator_dynamic(name: str) -> type[Generator]:
UpperConfidenceBoundGenerator,
)
from xopt.generators.bayesian.bax_generator import BaxGenerator
from xopt.generators.bayesian.baxus import BAxUSGenerator

registered_generators: list[type[Generator]] = [
UpperConfidenceBoundGenerator,
Expand All @@ -97,6 +99,7 @@ def get_generator_dynamic(name: str) -> type[Generator]:
ExpectedImprovementGenerator,
MultiFidelityGenerator,
BaxGenerator,
BAxUSGenerator,
]
for gen in registered_generators:
generators[gen.name] = gen
Expand Down
2 changes: 2 additions & 0 deletions xopt/generators/bayesian/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import torch

from xopt.generators.bayesian.bax_generator import BaxGenerator
from xopt.generators.bayesian.baxus import BAxUSGenerator
from xopt.generators.bayesian.bayesian_exploration import BayesianExplorationGenerator
from xopt.generators.bayesian.bayesian_generator import BayesianGenerator
from xopt.generators.bayesian.expected_improvement import (
Expand Down Expand Up @@ -35,6 +36,7 @@
"TDExpectedImprovementGenerator",
"MGGPOGenerator",
"BaxGenerator",
"BAxUSGenerator",
"BayesianGenerator",
"TimeDependentBayesianGenerator",
"TurboController",
Expand Down
Loading
Loading