From 2ee5ae1708ea30a7168e4f5ffdf79c44bce0fa79 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Sat, 13 Jul 2024 15:45:13 -0400 Subject: [PATCH 01/13] Add examples from BOA-Paper repo to docs --- docs/examples/index.rst | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 9ab6c1c..ee6e353 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -2,6 +2,24 @@ Examples ######## +.. toctree:: + :caption: Examples with Synthetic Functions in R + :maxdepth: 2 + + Branin + Branin Saasbo - High Dimensional Optimization + Hartmann6 + Hartmann6 with Constraints + Hartmann6 with Global Stopping Strategy + + +.. toctree:: + :caption: Examples with Environmental Models + :maxdepth: 2 + + SWAT+ + FERTCH3.14 + .. toctree:: :caption: Running Through the CLI (Command Line Interface) :maxdepth: 2 @@ -22,7 +40,7 @@ Examples .. toctree:: - :caption: Examples from Models Using BOA + :caption: Processing Model Outputs from a Optimization of a Real Model :maxdepth: 2 - cached_notebooks/example_optimization_results \ No newline at end of file + cached_notebooks/example_optimization_results From 6c1ff6f0bd97cb2720341e0e4b87ede468ebf74e Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Sat, 13 Jul 2024 16:05:06 -0400 Subject: [PATCH 02/13] update docs to differentiate between this repo and BOA-paper repo --- .readthedocs.yaml | 4 ++-- docs/examples/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 38bdee6..060cb54 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,9 +1,9 @@ version: 2 build: - os: "ubuntu-20.04" + os: "ubuntu-latest" tools: - python: "mambaforge-4.10" + python: "mambaforge-latest" jobs: post_install: - conda list diff --git a/docs/examples/index.rst b/docs/examples/index.rst index ee6e353..1dc9ba2 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -3,7 +3,7 @@ Examples ######## .. toctree:: - :caption: Examples with Synthetic Functions in R + :caption: Examples with Synthetic Functions in R on the BOA Paper Examples Page :maxdepth: 2 Branin @@ -14,7 +14,7 @@ Examples .. toctree:: - :caption: Examples with Environmental Models + :caption: Examples with Environmental Models on the BOA Paper Examples Page :maxdepth: 2 SWAT+ From aacac6be4d4054679a44e97e0570e3209e4f7c3d Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Sat, 13 Jul 2024 16:06:49 -0400 Subject: [PATCH 03/13] Fix rtd yaml error --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 060cb54..4349b9d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,7 @@ version: 2 build: - os: "ubuntu-latest" + os: "ubuntu-lts-latest" tools: python: "mambaforge-latest" jobs: From 2ce9dd1b5b0aa4af0b017bf68cbe4571fa4f96a5 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Sat, 13 Jul 2024 16:13:35 -0400 Subject: [PATCH 04/13] minor docs update --- docs/examples/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 1dc9ba2..a89b69c 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -3,7 +3,7 @@ Examples ######## .. toctree:: - :caption: Examples with Synthetic Functions in R on the BOA Paper Examples Page + :caption: Examples with Synthetic Functions in R on the `BOA Paper Examples Page `_ :maxdepth: 2 Branin @@ -14,7 +14,7 @@ Examples .. toctree:: - :caption: Examples with Environmental Models on the BOA Paper Examples Page + :caption: Examples with Environmental Models on the `BOA Paper Examples Page `_ :maxdepth: 2 SWAT+ From 21e36375842c8751829ba82d869348c69219ee41 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Fri, 19 Jul 2024 10:56:14 -0400 Subject: [PATCH 05/13] Add boa and boa-plot entry points to pyproject.toml Allow users to start BOA with `boa -c config` or `pythong -m boa -c config` and start plotting with `boa-plot -sp scheduler` or `pythong -m boa.plot -sp scheduler` --- boa/__main__.py | 220 ++------------------------------------------ boa/cli/__init__.py | 219 +++++++++++++++++++++++++++++++++++++++++++ boa/logger.py | 3 - boa/runner.py | 8 +- pyproject.toml | 4 + 5 files changed, 234 insertions(+), 220 deletions(-) create mode 100644 boa/cli/__init__.py diff --git a/boa/__main__.py b/boa/__main__.py index 8914c09..137d9bf 100644 --- a/boa/__main__.py +++ b/boa/__main__.py @@ -1,218 +1,10 @@ -import os -import sys -import tempfile -from pathlib import Path +def main(): + from boa.cli import main as _main -import click -from attrs import fields_dict -from ax.storage.json_store.decoder import object_from_json - -from boa.config import BOAScriptOptions -from boa.controller import Controller -from boa.storage import scheduler_from_json_file -from boa.wrappers.script_wrapper import ScriptWrapper -from boa.wrappers.wrapper_utils import cd_and_cd_back, load_jsonlike - - -@click.command() -@click.option( - "-c", - "--config-path", - type=click.Path(exists=True, dir_okay=False, path_type=Path), - help="Path to configuration YAML file.", -) -@click.option( - "-sp", - "--scheduler-path", - type=click.Path(), - default="", - help="Path to scheduler json file.", -) -@click.option( - "-wp", - "--wrapper-path", - type=click.Path(), - default="", - help="Path to where file where your wrapper is located. Used when loaded from scheduler json file," - " and the path to your wrapper has changed (such as when loading on a different computer then" - " originally ran from).", -) -@click.option( - "-wn", - "--wrapper-name", - type=str, - default="", - help="Name of the wrapper class to use. Used when loaded from scheduler json file,", -) -@click.option( - "-td", - "--temporary-dir", - is_flag=True, - show_default=True, - default=False, - help="Modify/add to the config file a temporary directory as the experiment_dir that will get deleted after running" - " (useful for testing)." - " This requires your Wrapper to have the ability to take experiment_dir as an argument" - " to ``load_config``. The default ``load_config`` does support this." - " This is also only done for initial run, not for reloading from scheduler json file.", -) -@click.option( - "--rel-to-config/--rel-to-here", # more cli friendly name for config option of rel_to_launch - default=None, - help="Define all path and dir options in your config file relative to where boa is launched from" - " instead of relative to the config file location (the default)" - " ex:" - " given working_dir=path/to/dir" - " if you don't pass --rel-to-here then path/to/dir is defined in terms of where your config file is" - " if you do pass --rel-to-here then path/to/dir is defined in terms of where you launch boa from", -) -def main(config_path, scheduler_path, wrapper_path, wrapper_name, temporary_dir, rel_to_config): - """Run experiment run from config path or scheduler path""" - - if temporary_dir: - with tempfile.TemporaryDirectory() as temp_dir: - experiment_dir = Path(temp_dir) - return run( - config_path, - scheduler_path=scheduler_path, - wrapper_path=wrapper_path, - wrapper_name=wrapper_name, - rel_to_config=rel_to_config, - experiment_dir=experiment_dir, - ) - return run(config_path, scheduler_path=scheduler_path, wrapper_path=wrapper_path, rel_to_config=rel_to_config) - - -def run(config_path, scheduler_path, rel_to_config, wrapper_path=None, wrapper_name=None, experiment_dir=None): - """Run experiment run from config path or scheduler path - - Parameters - ---------- - config_path - Path to configuration YAML file. - scheduler_path - Path to scheduler json file. - wrapper_path - Path to where file where your wrapper is located. Used when loaded from scheduler json file, - and the path to your wrapper has changed (such as when loading on a different computer then - originally ran from). - rel_to_config - Define all path and dir options in your config file relative to to your config file location - or rel_to_here (relative to cli launch) - experiment_dir - experiment output directory to save BOA run to, can only be specified during an initial run - (when passing in a config_path, not a scheduler_path) - - Returns - ------- - Scheduler - """ - config = {} - script_options = {} - if config_path: - config_path = Path(config_path).resolve() - config = load_jsonlike(config_path) - script_options = config.get("script_options", {}) - if rel_to_config is None: - rel_to_config = get_rel_from_script_options(script_options) - if scheduler_path: - scheduler_path = Path(scheduler_path).resolve() - if not config: - sch_jsn = load_jsonlike(scheduler_path) - config = object_from_json(sch_jsn["wrapper"]["config"]) - config_path = object_from_json(sch_jsn["wrapper"]["config_path"]) - script_options = config.get("script_options", {}) - if rel_to_config is None: - rel_to_config = get_rel_from_script_options(script_options) - - if experiment_dir: - experiment_dir = Path(experiment_dir).resolve() - wrapper_path = Path(wrapper_path).resolve() if wrapper_path else None - - if config_path and rel_to_config: - rel_path = config_path.parent - else: - rel_path = os.getcwd() - - options = get_config_options( - experiment_dir=experiment_dir, rel_path=rel_path, script_options=script_options, wrapper_path=wrapper_path - ) - - if wrapper_name: - options["wrapper_name"] = wrapper_name - - with cd_and_cd_back(options["working_dir"]): - if scheduler_path: - scheduler = scheduler_from_json_file(filepath=scheduler_path, wrapper_path=options["wrapper_path"]) - controller = Controller.from_scheduler(scheduler=scheduler, **options) - else: - if options["wrapper_path"] and Path(options["wrapper_path"]).exists(): - options["wrapper"] = options["wrapper_path"] - else: - options["wrapper"] = ScriptWrapper - controller = Controller( - config_path=config_path, - **options, - ) - controller.initialize_scheduler() - - scheduler = controller.run() - return scheduler - - -def get_rel_from_script_options(script_options): - rel_to_config = script_options.get("rel_to_config", None) or not script_options.get("rel_to_launch", None) - if rel_to_config is None: - rel_to_config = ( - fields_dict(BOAScriptOptions)["rel_to_config"].default - or not fields_dict(BOAScriptOptions)["rel_to_launch"].default - ) - return rel_to_config - - -def get_config_options(experiment_dir, rel_path, script_options: dict = None, wrapper_path=None): - script_options = script_options if script_options is not None else {} - wrapper_name = script_options.get("wrapper_name", fields_dict(BOAScriptOptions)["wrapper_name"].default) - append_timestamp = ( - script_options.get("append_timestamp", None) - if script_options.get("append_timestamp", None) is not None - else fields_dict(BOAScriptOptions)["append_timestamp"].default - ) - - wrapper_path = ( - wrapper_path - if wrapper_path is not None - else script_options.get("wrapper_path", fields_dict(BOAScriptOptions)["wrapper_path"].default) - ) - wrapper_path = _prepend_rel_path(rel_path, wrapper_path) if wrapper_path else wrapper_path - - working_dir = script_options.get("working_dir", fields_dict(BOAScriptOptions)["working_dir"].default) - working_dir = _prepend_rel_path(rel_path, working_dir) - - experiment_dir = experiment_dir or script_options.get( - "experiment_dir", fields_dict(BOAScriptOptions)["experiment_dir"].default - ) - experiment_dir = _prepend_rel_path(rel_path, experiment_dir) if experiment_dir else experiment_dir - - if working_dir: - sys.path.append(str(working_dir)) - - return dict( - append_timestamp=append_timestamp, - experiment_dir=experiment_dir, - working_dir=working_dir, - wrapper_name=wrapper_name, - wrapper_path=wrapper_path, - ) - - -def _prepend_rel_path(rel_path, path): - if not path: - return path - path = Path(path) - if not path.is_absolute(): - path = rel_path / path - return path.resolve() + # Main entry point + # Can be invoked with `python -m boa` + # or just `boa` (see pypoject.toml` + _main() if __name__ == "__main__": diff --git a/boa/cli/__init__.py b/boa/cli/__init__.py new file mode 100644 index 0000000..8914c09 --- /dev/null +++ b/boa/cli/__init__.py @@ -0,0 +1,219 @@ +import os +import sys +import tempfile +from pathlib import Path + +import click +from attrs import fields_dict +from ax.storage.json_store.decoder import object_from_json + +from boa.config import BOAScriptOptions +from boa.controller import Controller +from boa.storage import scheduler_from_json_file +from boa.wrappers.script_wrapper import ScriptWrapper +from boa.wrappers.wrapper_utils import cd_and_cd_back, load_jsonlike + + +@click.command() +@click.option( + "-c", + "--config-path", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help="Path to configuration YAML file.", +) +@click.option( + "-sp", + "--scheduler-path", + type=click.Path(), + default="", + help="Path to scheduler json file.", +) +@click.option( + "-wp", + "--wrapper-path", + type=click.Path(), + default="", + help="Path to where file where your wrapper is located. Used when loaded from scheduler json file," + " and the path to your wrapper has changed (such as when loading on a different computer then" + " originally ran from).", +) +@click.option( + "-wn", + "--wrapper-name", + type=str, + default="", + help="Name of the wrapper class to use. Used when loaded from scheduler json file,", +) +@click.option( + "-td", + "--temporary-dir", + is_flag=True, + show_default=True, + default=False, + help="Modify/add to the config file a temporary directory as the experiment_dir that will get deleted after running" + " (useful for testing)." + " This requires your Wrapper to have the ability to take experiment_dir as an argument" + " to ``load_config``. The default ``load_config`` does support this." + " This is also only done for initial run, not for reloading from scheduler json file.", +) +@click.option( + "--rel-to-config/--rel-to-here", # more cli friendly name for config option of rel_to_launch + default=None, + help="Define all path and dir options in your config file relative to where boa is launched from" + " instead of relative to the config file location (the default)" + " ex:" + " given working_dir=path/to/dir" + " if you don't pass --rel-to-here then path/to/dir is defined in terms of where your config file is" + " if you do pass --rel-to-here then path/to/dir is defined in terms of where you launch boa from", +) +def main(config_path, scheduler_path, wrapper_path, wrapper_name, temporary_dir, rel_to_config): + """Run experiment run from config path or scheduler path""" + + if temporary_dir: + with tempfile.TemporaryDirectory() as temp_dir: + experiment_dir = Path(temp_dir) + return run( + config_path, + scheduler_path=scheduler_path, + wrapper_path=wrapper_path, + wrapper_name=wrapper_name, + rel_to_config=rel_to_config, + experiment_dir=experiment_dir, + ) + return run(config_path, scheduler_path=scheduler_path, wrapper_path=wrapper_path, rel_to_config=rel_to_config) + + +def run(config_path, scheduler_path, rel_to_config, wrapper_path=None, wrapper_name=None, experiment_dir=None): + """Run experiment run from config path or scheduler path + + Parameters + ---------- + config_path + Path to configuration YAML file. + scheduler_path + Path to scheduler json file. + wrapper_path + Path to where file where your wrapper is located. Used when loaded from scheduler json file, + and the path to your wrapper has changed (such as when loading on a different computer then + originally ran from). + rel_to_config + Define all path and dir options in your config file relative to to your config file location + or rel_to_here (relative to cli launch) + experiment_dir + experiment output directory to save BOA run to, can only be specified during an initial run + (when passing in a config_path, not a scheduler_path) + + Returns + ------- + Scheduler + """ + config = {} + script_options = {} + if config_path: + config_path = Path(config_path).resolve() + config = load_jsonlike(config_path) + script_options = config.get("script_options", {}) + if rel_to_config is None: + rel_to_config = get_rel_from_script_options(script_options) + if scheduler_path: + scheduler_path = Path(scheduler_path).resolve() + if not config: + sch_jsn = load_jsonlike(scheduler_path) + config = object_from_json(sch_jsn["wrapper"]["config"]) + config_path = object_from_json(sch_jsn["wrapper"]["config_path"]) + script_options = config.get("script_options", {}) + if rel_to_config is None: + rel_to_config = get_rel_from_script_options(script_options) + + if experiment_dir: + experiment_dir = Path(experiment_dir).resolve() + wrapper_path = Path(wrapper_path).resolve() if wrapper_path else None + + if config_path and rel_to_config: + rel_path = config_path.parent + else: + rel_path = os.getcwd() + + options = get_config_options( + experiment_dir=experiment_dir, rel_path=rel_path, script_options=script_options, wrapper_path=wrapper_path + ) + + if wrapper_name: + options["wrapper_name"] = wrapper_name + + with cd_and_cd_back(options["working_dir"]): + if scheduler_path: + scheduler = scheduler_from_json_file(filepath=scheduler_path, wrapper_path=options["wrapper_path"]) + controller = Controller.from_scheduler(scheduler=scheduler, **options) + else: + if options["wrapper_path"] and Path(options["wrapper_path"]).exists(): + options["wrapper"] = options["wrapper_path"] + else: + options["wrapper"] = ScriptWrapper + controller = Controller( + config_path=config_path, + **options, + ) + controller.initialize_scheduler() + + scheduler = controller.run() + return scheduler + + +def get_rel_from_script_options(script_options): + rel_to_config = script_options.get("rel_to_config", None) or not script_options.get("rel_to_launch", None) + if rel_to_config is None: + rel_to_config = ( + fields_dict(BOAScriptOptions)["rel_to_config"].default + or not fields_dict(BOAScriptOptions)["rel_to_launch"].default + ) + return rel_to_config + + +def get_config_options(experiment_dir, rel_path, script_options: dict = None, wrapper_path=None): + script_options = script_options if script_options is not None else {} + wrapper_name = script_options.get("wrapper_name", fields_dict(BOAScriptOptions)["wrapper_name"].default) + append_timestamp = ( + script_options.get("append_timestamp", None) + if script_options.get("append_timestamp", None) is not None + else fields_dict(BOAScriptOptions)["append_timestamp"].default + ) + + wrapper_path = ( + wrapper_path + if wrapper_path is not None + else script_options.get("wrapper_path", fields_dict(BOAScriptOptions)["wrapper_path"].default) + ) + wrapper_path = _prepend_rel_path(rel_path, wrapper_path) if wrapper_path else wrapper_path + + working_dir = script_options.get("working_dir", fields_dict(BOAScriptOptions)["working_dir"].default) + working_dir = _prepend_rel_path(rel_path, working_dir) + + experiment_dir = experiment_dir or script_options.get( + "experiment_dir", fields_dict(BOAScriptOptions)["experiment_dir"].default + ) + experiment_dir = _prepend_rel_path(rel_path, experiment_dir) if experiment_dir else experiment_dir + + if working_dir: + sys.path.append(str(working_dir)) + + return dict( + append_timestamp=append_timestamp, + experiment_dir=experiment_dir, + working_dir=working_dir, + wrapper_name=wrapper_name, + wrapper_path=wrapper_path, + ) + + +def _prepend_rel_path(rel_path, path): + if not path: + return path + path = Path(path) + if not path.is_absolute(): + path = rel_path / path + return path.resolve() + + +if __name__ == "__main__": + main() diff --git a/boa/logger.py b/boa/logger.py index 078e369..7d4d5d7 100644 --- a/boa/logger.py +++ b/boa/logger.py @@ -1,15 +1,12 @@ import logging import logging.config import logging.handlers -import multiprocessing from boa.definitions import PathLike DEFAULT_LOG_LEVEL: int = logging.INFO ROOT_LOGGER_NAME = "boa" -queue = multiprocessing.Manager().Queue() - def get_logger(name: str = ROOT_LOGGER_NAME, level: int = DEFAULT_LOG_LEVEL, filename=None) -> logging.Logger: """Get a logger. diff --git a/boa/runner.py b/boa/runner.py index 7e26547..bbd46d1 100644 --- a/boa/runner.py +++ b/boa/runner.py @@ -9,6 +9,7 @@ import concurrent.futures import logging +import multiprocessing from collections import defaultdict from typing import Any, Dict, Iterable, Set @@ -16,7 +17,7 @@ from ax.core.runner import Runner from ax.core.trial import Trial -from boa.logger import get_logger, queue +from boa.logger import get_logger from boa.metaclasses import RunnerRegister from boa.utils import serialize_init_args from boa.wrappers.base_wrapper import BaseWrapper @@ -28,6 +29,7 @@ class WrappedJobRunner(Runner, metaclass=RunnerRegister): def __init__(self, wrapper: BaseWrapper = None, *args, **kwargs): self.wrapper = wrapper or BaseWrapper() + self.queue = multiprocessing.Manager().Queue() super().__init__(*args, **kwargs) def run(self, trial: Trial) -> Dict[str, Any]: @@ -42,7 +44,7 @@ def run(self, trial: Trial) -> Dict[str, Any]: Returns: Dict of run metadata from the deployment process. """ - qh = logging.handlers.QueueHandler(queue) + qh = logging.handlers.QueueHandler(self.queue) logger = logging.getLogger() logger.addHandler(qh) ax_logger = logging.getLogger("ax") @@ -119,7 +121,7 @@ def to_dict(self) -> dict: parents = self.__class__.mro()[1:] # index 0 is the class itself - properties = serialize_init_args(self, parents=parents, match_private=True, exclude_fields=["wrapper"]) + properties = serialize_init_args(self, parents=parents, match_private=True, exclude_fields=["wrapper", "queue"]) properties["__type"] = self.__class__.__name__ return properties diff --git a/pyproject.toml b/pyproject.toml index 775de7a..f1813f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,10 @@ dynamic = ["version", "dependencies", "optional-dependencies"] "Bug Tracker" = "https://github.com/madeline-scyphers/boa/issues" Documentation = "http://boa-framework.readthedocs.io" +[project.scripts] +boa = "boa.cli:main" +"boa-plot" = "boa.plot:main" + [tool.setuptools.packages.find] include = ["boa*"] From 979df83567f057d558ba63bc18a45b9569e5b423 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Fri, 19 Jul 2024 11:19:29 -0400 Subject: [PATCH 06/13] Fix tests to point to new CLI entry func from dunder main --- tests/conftest.py | 12 ++++++------ .../{test_dunder_main.py => test_cli.py} | 12 ++++++------ tests/integration_tests/test_storage.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) rename tests/integration_tests/{test_dunder_main.py => test_cli.py} (92%) diff --git a/tests/conftest.py b/tests/conftest.py index 6983ab7..4ef90c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,10 +5,10 @@ import pytest -import boa.__main__ as dunder_main import boa.scripts.moo as run_moo import boa.scripts.run_branin as run_branin from boa import BOAConfig, cd_and_cd_back, split_shell_command +from boa.cli import main as cli_main from boa.definitions import ROOT, TEST_SCRIPTS_DIR logger = logging.getLogger(__file__) @@ -192,7 +192,7 @@ def denormed_custom_wrapper_run(tmp_path_factory, cd_to_root_and_back_session): config_path = temp_dir / "different_name_config.json" with open(Path(config_path), "w") as file: json.dump(config, file) - scheduler = dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + scheduler = cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) os.remove(config_path) yield scheduler @@ -211,25 +211,25 @@ def moo_main_run(tmp_path_factory, cd_to_root_and_back_session): def stand_alone_opt_package_run(tmp_path_factory, cd_to_root_and_back_session): config_path = TEST_DIR / "scripts/stand_alone_opt_package/stand_alone_pkg_config.yaml" args = f"--config-path {config_path} -td" - yield dunder_main.main(split_shell_command(args), standalone_mode=False) + yield cli_main(split_shell_command(args), standalone_mode=False) @pytest.fixture(scope="session") def r_full(tmp_path_factory, cd_to_root_and_back_session): config_path = TEST_DIR / f"scripts/other_langs/r_package_full/config.yaml" - yield dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + yield cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) @pytest.fixture(scope="session") def r_light(tmp_path_factory, cd_to_root_and_back_session): config_path = TEST_DIR / f"scripts/other_langs/r_package_light/config.yaml" - yield dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + yield cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) @pytest.fixture(scope="session") def r_streamlined(tmp_path_factory, cd_to_root_and_back_session): config_path = TEST_DIR / f"scripts/other_langs/r_package_streamlined/config.yaml" - yield dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + yield cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) diff --git a/tests/integration_tests/test_dunder_main.py b/tests/integration_tests/test_cli.py similarity index 92% rename from tests/integration_tests/test_dunder_main.py rename to tests/integration_tests/test_cli.py index 009bd3f..7f0fe2c 100644 --- a/tests/integration_tests/test_dunder_main.py +++ b/tests/integration_tests/test_cli.py @@ -5,7 +5,6 @@ import pytest from ax.service.scheduler import FailureRateExceededError -import boa.__main__ as dunder_main from boa import ( BaseWrapper, BOAConfig, @@ -16,6 +15,7 @@ scheduler_to_json_file, split_shell_command, ) +from boa.cli import main as cli_main from boa.definitions import ROOT try: @@ -110,14 +110,14 @@ def test_calling_command_line_r_test_scripts(r_scripts_run, request): def test_cli_interface_with_failing_test_that_sends_back_failed_trial_status(): with pytest.raises(FailureRateExceededError): config_path = ROOT / "tests" / f"scripts/other_langs/r_package_streamlined/config_fail.yaml" - dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) @pytest.mark.skipif(not R_INSTALLED, reason="requires R to be installed") def test_cli_interface_with_failing_test_that_sends_back_failed_trial_status(): with pytest.raises(FailureRateExceededError): config_path = ROOT / "tests" / f"scripts/other_langs/r_pass_back_fail_trial_status/config.yaml" - dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) def test_wrapper_with_custom_load_config(): @@ -125,7 +125,7 @@ def test_wrapper_with_custom_load_config(): config_path = ROOT / "tests" / f"scripts/other_langs/r_package_streamlined/config_fail.yaml" # But we override the failing config in our wrapper with a working one in a custom load_config wrapper_path = Path(__file__) - dunder_main.main( + cli_main( split_shell_command( f"--config-path {config_path}" f" --wrapper-path {wrapper_path}" @@ -153,10 +153,10 @@ def test_parallelism(r_light, caplog): def test_non_zero_exit_code_fails_trial(): with pytest.raises(FailureRateExceededError): config_path = ROOT / "tests" / f"scripts/other_langs/r_failure_exit_code/config.yaml" - dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) def test_return_nan_fails_trial(): with pytest.raises(FailureRateExceededError): config_path = ROOT / "tests" / f"scripts/other_langs/r_failure_nan/config.yaml" - dunder_main.main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) diff --git a/tests/integration_tests/test_storage.py b/tests/integration_tests/test_storage.py index 265c5ad..9e23f72 100644 --- a/tests/integration_tests/test_storage.py +++ b/tests/integration_tests/test_storage.py @@ -15,7 +15,6 @@ CORE_ENCODER_REGISTRY, ) -import boa.__main__ as dunder_main from boa import ( BaseWrapper, BOAConfig, @@ -31,6 +30,7 @@ split_shell_command, ) from boa.__version__ import __version__ +from boa.cli import main as cli_main from boa.definitions import ROOT TEST_DIR = ROOT / "tests" @@ -200,7 +200,7 @@ def test_can_pass_custom_wrapper_path_when_loading_scheduler_from_cli(stand_alon pre_num_trials = len(scheduler.experiment.trials) - scheduler = dunder_main.main( + scheduler = cli_main( split_shell_command(f"--scheduler-path {file_out} --wrapper-path {orig_wrapper_path} -td"), standalone_mode=False, ) From 28244f54857fac1ea4ba1ba5de4db31917d87656 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Fri, 19 Jul 2024 11:48:22 -0400 Subject: [PATCH 07/13] Update docs to say just `boa -c path` in most cases --- boa/__main__.py | 2 +- boa/plot.py | 4 +++- docs/examples/1run_r_streamlined.ipynb | 6 +++--- docs/examples/2EDA_from_r_run.ipynb | 4 ++-- docs/examples/example_py_run.rst | 2 +- docs/index.rst | 4 ---- docs/user_guide/getting_started.rst | 18 ++++++++++++++---- docs/user_guide/package_overview.rst | 12 ++++++------ 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/boa/__main__.py b/boa/__main__.py index 137d9bf..fd5d664 100644 --- a/boa/__main__.py +++ b/boa/__main__.py @@ -3,7 +3,7 @@ def main(): # Main entry point # Can be invoked with `python -m boa` - # or just `boa` (see pypoject.toml` + # or just `boa` (see pypoject.toml`) _main() diff --git a/boa/plot.py b/boa/plot.py index 8c71ea8..ab892e7 100644 --- a/boa/plot.py +++ b/boa/plot.py @@ -3,9 +3,11 @@ Plotting & EDA CLI ################################### -You can launch a basic EDA plot view +You can launch a basic EDA plot dashboard view of your optimization with:: + boa.plot path/to/scheduler.json + or python -m boa.plot path/to/scheduler.json diff --git a/docs/examples/1run_r_streamlined.ipynb b/docs/examples/1run_r_streamlined.ipynb index 32802a5..bfaab34 100644 --- a/docs/examples/1run_r_streamlined.ipynb +++ b/docs/examples/1run_r_streamlined.ipynb @@ -1507,13 +1507,13 @@ "To run our script we just need to path the config file to BOA's CLI\n", "\n", "```python\n", - "python -m boa --config-file path/to/config.yaml\n", + "boa --config-file path/to/config.yaml\n", "```\n", "\n", "or\n", "\n", "```python\n", - "python -m boa -c path/to/config.yaml\n", + "boa -c path/to/config.yaml\n", "```" ] }, @@ -1891,7 +1891,7 @@ } ], "source": [ - "output = !python -m boa -c {config_path} # we capture ipython terminal output to python variable\n", + "output = !boa -c {config_path} # we capture ipython terminal output to python variable\n", "o = \"\\n\".join(ln for ln in output) # it comes in as a list, we convert to string\n", "o = o.replace(str(r_dir), \"[/path/to/your/dir/]\") # replace the actual dir with a stand in for privacy reasons\n", "Code(o)" diff --git a/docs/examples/2EDA_from_r_run.ipynb b/docs/examples/2EDA_from_r_run.ipynb index 16d146d..1819583 100644 --- a/docs/examples/2EDA_from_r_run.ipynb +++ b/docs/examples/2EDA_from_r_run.ipynb @@ -24,11 +24,11 @@ "From the command line, we can issue\n", "\n", "```python\n", - "python -m boa.plot --scheduler-path path/to/scheduler.json\n", + "boa.plot --scheduler-path path/to/scheduler.json\n", "\n", "or\n", "\n", - "python -m boa.plot -sp path/to/scheduler.json\n", + "boa.plot -sp path/to/scheduler.json\n", "```\n", "\n", "```{attention} \n", diff --git a/docs/examples/example_py_run.rst b/docs/examples/example_py_run.rst index bde2faa..16865d9 100644 --- a/docs/examples/example_py_run.rst +++ b/docs/examples/example_py_run.rst @@ -30,7 +30,7 @@ You can start and run your optimization like this: .. code-block:: console - $ python -m boa -c config.json + $ boa -c config.json Start time: 20221026T210522 [INFO 10-26 21:05:22] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x0. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. [INFO 10-26 21:05:22] ax.service.utils.instantiation: Inferred value type of ParameterType.FLOAT for parameter x1. If that is not the expected value type, you can explicity specify 'value_type' ('int', 'float', 'bool' or 'str') in parameter dict. diff --git a/docs/index.rst b/docs/index.rst index 2723a82..926162b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,10 +24,6 @@ Key features - Customizable objective functions, multi-objective optimization, acquisition functions, etc - Choice of built-in evaluation metrics, but it’s also easy to implement custom metrics -.. important:: - - This site is still under construction. More content will be added soon! - Contents -------- .. toctree:: diff --git a/docs/user_guide/getting_started.rst b/docs/user_guide/getting_started.rst index 926b726..fb4f837 100644 --- a/docs/user_guide/getting_started.rst +++ b/docs/user_guide/getting_started.rst @@ -30,7 +30,7 @@ Here are instructions on how to install python through Anaconda or miniconda: Install boa =========== -If using conda and you don't already have a dedicated conda environment for your model:: +If using conda and you don't already have a dedicated conda environment for your model (if using mamba, replace all conda calls with mamba):: conda create -n boa conda activate boa @@ -42,11 +42,11 @@ or if not using Python, make sure you have a virtual environment create:: and activate the virtual environment, on windows:: - venv\Scripts\activate.bat + . venv\Scripts\activate.bat on linux and mac:: - source tutorial-env/bin/activate + . venv/bin/activate Once your environment is activated, if using conda, run:: @@ -78,6 +78,10 @@ so if on either of those, it should install pytorch>2 by default but if not and something doesn't work, upgrade pytorch, torchvision, and torchaudio +and then activate this environment:: + + conda activate boa-dev + :doc:`/contributing` ******** @@ -86,10 +90,16 @@ Test run Once everything is installed, run the test script to ensure everything is install properly:: - python -m boa.scripts.run_branin + boa.scripts.run_branin If this test case runs successfully, you can move on to the next steps. +If you encounter a problem, make sure you environment that you install boa into is activated (`conda activate boa`, `. venv/bin/activate`, or `. venv\Scripts\activate.bat` for conda or pip (unix or windows), see above)), and then try again. If it still doesn't work, you can try:: + + python -m boa.scripts.run_branin + +If this works, it may mean it installed an older version of BOA or that the boa command is not found on your path. If this works, you should be able to run boa commands by using the `python -m boa` instead of just `boa` and the plot command with `python -m boa.plot`. + ********** Update BOA ********** diff --git a/docs/user_guide/package_overview.rst b/docs/user_guide/package_overview.rst index e55849e..3c53c49 100644 --- a/docs/user_guide/package_overview.rst +++ b/docs/user_guide/package_overview.rst @@ -45,7 +45,7 @@ Creating a Python launch script (Usually Not Needed) **************************************************** Most of the time you won't need to write a launch script because BOA has an built-in launch script in -its :mod:`.controller` that is called when calling `python -m boa`. But if you do need more control over your launch script than the default +its :mod:`.controller` that is called when calling `boa`. But if you do need more control over your launch script than the default provides, you can either subclass :class:`.Controller` or write your own launch script. Subclassing :class:`.Controller` might be easier if you just need to modify :meth:`.Controller.run` or :meth:`.Controller.initialize_wrapper` or :meth:`.Controller.initialize_scheduler` but can utilize the rest of the functions. If you need a lot of customization, writing your own script might be @@ -63,24 +63,24 @@ you can start your run easily from the command line. With your conda environment for boa activated, run:: - python -m boa --config-path path/to/your/config/file + boa --config-path path/to/your/config/file or:: - python -m boa -c path/to/your/config/file + boa -c path/to/your/config/file :doc:`BOA's ` will save the its current state automatically to a `scheduler.json` file in your output experiment directory every 1-few trials (depending on parallelism settings). It will also save a optimization.csv at the end of your run with the trial information as well in the same directory as scheduler.json. The console will output the Output directory at the start and end of your runs to the console, it will also throughout the run, whenever it saves the `scheduler.json` file, output to the console the location where the file is being saved. You can resume a stopped run from a scheduler file:: - python -m boa --scheduler-path path/to/your/scheduler.json + boa --scheduler-path path/to/your/scheduler.json or:: - python -m boa -sp path/to/your/scheduler.json + boa -sp path/to/your/scheduler.json For a list of options and descriptions, type:: - python -m boa --help + boa --help A fuller example using the command line interface can be found :doc:`here ` From 76dd1c931e1738751e9d75cfff60c6ad286db72a Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Fri, 19 Jul 2024 13:31:10 -0400 Subject: [PATCH 08/13] Add BO overview tutorial page to docs --- CITATION.cff | 4 +- docs/assets/BO_workflow_diagram.png | Bin 0 -> 144356 bytes docs/index.rst | 30 +++------ docs/user_guide/bo_overview.md | 100 ++++++++++++++++++++++++++++ docs/user_guide/getting_started.rst | 7 ++ docs/user_guide/index.rst | 1 + 6 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 docs/assets/BO_workflow_diagram.png create mode 100644 docs/user_guide/bo_overview.md diff --git a/CITATION.cff b/CITATION.cff index 5111c5f..2d91c6c 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -9,7 +9,7 @@ type: software authors: - given-names: Madeline family-names: Scyphers - email: scyphers.3@osu.edu + email: mescyphers@gmail.com orcid: 'https://orcid.org/0000-0003-3733-4330' - given-names: Justine family-names: Missik @@ -54,4 +54,4 @@ references: title: 'BoTorch: A Framework for Efficient Monte-Carlo Bayesian Optimization' journal: 'Advances in Neural Information Processing Systems 33' year: 2020 - url: 'http://arxiv.org/abs/1910.06403' \ No newline at end of file + url: 'http://arxiv.org/abs/1910.06403' diff --git a/docs/assets/BO_workflow_diagram.png b/docs/assets/BO_workflow_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..f6d7163f1ed020dcd97e376f34338a86b72d90bb GIT binary patch literal 144356 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 r*$)AQ015>Z3Mdp%D4` is a high-level Bayesian optimization framework and model wrapping tool. It provides an easy-to-use interface -between models and the python libraries `Ax `_ and `BoTorch `_. +BOA is a high-level Bayesian optimization framework and model-wrapping toolkit. It is designed to be highly flexible and easy-to-use. BOA is built upon the lower-level packages `Ax `_ (Adaptive Experimentation Platform, https://ax.dev//index.html) and `BoTorch `_ to do the heavy lifting of the BO process and subsequent analysis. It supplements these lower-level packages with model-wrapping tools, language-agnostic features, and a flexible interface framework. + Key features ------------ -- **Model agnostic** - - - Can be used for models in any language (not just python) - - Can be used for Wrappers in any language (You don't even need to write any python!) See :mod:`Script Wrapper <.script_wrapper>` for details on how to do that. - - Simple to implement for new models, with minimal coding required - -- **Scalable** - - - Can be used for simple models or complex models that require a lot of computational resources - - Scheduler to manage individual model runs - - Supports parallelization - -- **Modular & customizable** - - - Can take advantages of the many features of Ax/BoTorch - - Customizable objective functions, multi-objective optimization, acquisition functions, etc - - Choice of built-in evaluation metrics, but it’s also easy to implement custom metrics +- **Language-Agnostic**: Although BOA itself is written in Python, users do not need to write any Python code in order to use it. The user’s model, as well as the model wrapper, can be written in any programming language. Users can configure and run an optimization, save outputs, and view results entirely without writing any Python code. This allows the user to write their code in any language they want, or even reuse processing code they already have, and still have access to two of the most full-featured BO (BoTorch) and GP (GPyTorch) libraries available today. +- **Scalability and Parallelization**: BOA handles optimization tasks of any size, from small problems to large, complex models. It supports parallel evaluations, allowing multiple optimization trials to run at the same time. This greatly reduces optimization time, especially when using powerful computing resources like supercomputing clusters. In many other BO packages, even if batched trial evaluation is supported, the actual parallelization implementation is left as an exercise to the user. +- **Reducing Boilerplate Code**: BOA aims to reduce the amount of boilerplate code often needed to set up and launch optimizations. BOA does this by providing an application programming interface (API) to the lower-level BO libraries BoTorch and Ax that it is built upon. This API is responsible for initializing, starting, and controlling the user’s optimization. The BOA API can be accessed and controlled almost entirely through a human readable, text based, YAML configuration file, reducing the need to write boilerplate setup code. +- **Automatic Saving and Resuming**: BOA automatically saves the state of an optimization process, allowing users to pause and resume optimizations easily. This ensures continuous progress and makes it easy to recover and retrieve results, even if there are interruptions or system crashes, making the workflow more resilient and user-friendly. Users can also add additional trials to a completed optimization or explore incoming results as the optimization is still running. +- **Support for Multi-Objective Optimization**: Streamlined and customizable support for multi-objective optimization. +- **Handling High-Dimensional and Complex Models**: Support for high-dimensional problems. +- **Customizability**: BOA allows customization of the optimization process as needed, including adding constraints, adjusting the kernel or acquisition function, or incorporating an early stopping criterion. + +Head over to the :doc:`Bayesian Optimization overview page ` to read about Bayesian Optimization and how it works. Contents -------- diff --git a/docs/user_guide/bo_overview.md b/docs/user_guide/bo_overview.md new file mode 100644 index 0000000..8377ef0 --- /dev/null +++ b/docs/user_guide/bo_overview.md @@ -0,0 +1,100 @@ +# Basics of Bayesian Optimization + +## Fundamental concepts + +Bayesian Optimization (BO) is a statistical method to optimize an objective function f over some feasible search space 𝕏. For example, f could be the difference between model predictions and observed values of a particular variable. BO relies on constructing a probabilistic surrogate model for f that is leveraged to make decisions about where to query from 𝕏 for future evaluations (Brochu et al., 2010; Frazier 2018). BO builds the surrogate model using all previous evaluations, resulting in a process that can find optima of non-convex problems in relatively few evaluations compared to methods that rely on more local information like gradients or more exhaustive approaches like grid search (Snoek et al. 2012). With this approach, the trade-off is that building the surrogate model is more computationally expensive than other optimization methods, resulting in the time between evaluations being larger (Snoek et al. 2012). However, when evaluation time of f is large, which is the case with many environmental models, the trade-off of some extra computation time to build the surrogate model is well worth it for the benefit of fewer overall evaluations (Snoek et al. 2012). + +BO is particularly well-suited to the following application challenges: + +- Black-Box Functions: Functions without a known closed-form expression or where the derivative is not readily available (Phan-Trong et al., 2023). +- Expensive Functions: Functions where each evaluation incurs a high computational or financial cost, typically due to long computation time and large requirement of computational resources (Snoek et al., 2012). +- Noisy Functions: Functions where evaluations are noisy and non-deterministic (Daulton et al., 2021). +- Multi-modal Functions: Functions that are non-convex with potentially a large number of local optima where traditional gradient-based methods can get stuck (Riche and Picheny, 2021). +- Limited Budget for Evaluations: When the number of times the function can be evaluated is limited (Frazier 2018). +- High-Dimensional Spaces: Although more challenging, recent advancement have allowed BO to be applied to problems with in high-dimensional spaces (Eriksson and Jankowiak 2021; Moriconi et al. 2020). + +Given a finite computational resource, we can only afford a limited number of evaluations in the process of optimizing f. Therefore, we want to evaluate f a relatively small number of times. Because f is expensive (as is the case with many environmental models), we cannot use an evaluation “heavy” method like grid search or genetic algorithms. We need a way to extrapolate relatively few evaluations to a prediction about f as a whole. To do this, we build a surrogate model, a simpler, cheaper to evaluate approximation of the actual function f (Brochu et al., 2010; Frazier 2018; Riche and Picheny, 2021; Snoek et al. 2012). + +In BO, the surrogate model is most typically modeled as a Gaussian Process (GP) (Gardner et al., 2018; Rasmussen and Williams, 2006). A GP is a powerful and flexible tool which uses a multivariate normal distribution to quantify the prediction uncertainty for any finite set of observations, and is specified by: + +1. A mean function, m(x), that represents the expected value of the f at any given point in the domain (Frazier 2018; Snoek et al. 2012).. +1. A covariance function, usually called a kernel, which defines the covariance (or similarity) between any given input pairs in the search space. The kernel encodes assumptions we have about f, such as smoothness and periodicity (Frazier 2018; Snoek et al. 2012). + +Typically, an optimization starts with a small number of sampling trials representing a good initial spread across the input space. A commonly used method for this initial sampling is Sobol sequences (Sobol’, 1967), a quasi-random, low-discrepancy sequence generating algorithm. Sobol generates points to fill the search space more randomly than a grid but more evenly than random sampling, which guarantees coverage of the search space while avoiding sampling biases that can occur with grids. These initial samples provide a basis for building the surrogate model, which then predicts f’s output and provides an estimate of the uncertainty of those predictions, resulting in a posterior distribution (Balandat et al., 2020; Frazier 2018; Snoek et al. 2012). + +Next, we need to select a “best” point from our posterior distribution to query from f next. There is no universally correct method for selecting this point; options include picking the point most likely to improve, the point expected to provide the largest average improvement, or a point that balances potential improvement with uncertainty about that improvement. We use heuristics called acquisition functions to make this selection. An acquisition function defines the search strategy and must balance the trade-off between exploration and exploitation trade-off. Exploration refers to querying points with high uncertainty, which often means higher risk but higher reward and more information gathered (Frazier 2018; Snoek et al. 2012). Exploitation refers to sampling points in regions the surrogate model already has high confidence in improving the objective function f (Balandat et al., 2020; Frazier 2018; Snoek et al. 2012). The three most common acquisition functions used in BO are Probability of Improvement (PI), Expected Improvement (EI), and Upper Confidence Bound (UCB). + + +![BO_workflow_diagram.png](../assets/BO_workflow_diagram.png) + +This showcases an example of BO on a 1D toy problem. The figure shows 3 iterations of an in-progress BO experiment (potentially after an initial Sobol sampling). It shows a GP approximation of the objective function (the posterior mean and posterior uncertainty) as well as the acquisition function. We can see that the acquisition function is high near current high points (exploitation) as well as where there is a high degree of uncertainty (exploration). + +## BO Algorithm Outline +To optimize f using BO: + +1. Query initial Points: Evaluate f at a set of initial points {x1, x2, …,xn} +1. Build Surrogate Model: Build a GP model using the initial points and fit a posterior distribution +1. Calculate Acquisition Function: Calculate the acquisition function over the posterior to find the next evaluation point xn+1 +1. Evaluate: Evaluate the objective function at xn+1, obtaining f(xn+1) +1. Update the Surrogate Model: Update the surrogate model with the new data point (xn+1, f(xn+1)) +1. Repeat: Repeat steps 3-5 until the stopping criteria are met. Stopping criteria could be a convergence stopping condition or a specified number of trials. + +## Constraints + +Constraints are often used to ensure that solutions meet specific criteria or stay within feasible regions defined by the problem's requirements. The set 𝕏 can be easily restricted by applying linear constraints to the parameters. The problem can be further generalized by adding additional constraining functions, gi ≥ 0 for i = 1, …, n, where gi are constraints that can be as expensive to evaluate as f (Frazier 2018). These additional constraints are known as black-box constraints (Balandat et al., 2020). + +## Multi-objective optimization + +Many problems suitable for optimization (e.g., complex physical models) lack an obvious single objective to optimize. In single-objective optimization, the goal is to find the best solution based on one criterion, such as maximizing profit or minimizing cost. However, real-world scenarios often require balancing multiple, conflicting objectives. For example, designing a car involves considering speed, fuel efficiency, and safety, where improving one aspect can worsen another. + +In multi-objective optimization, there isn't a single 'best' solution. Instead, the aim is to find a set of Pareto-optimal solutions, where no improvement can be made in one objective without degrading another (Emmerich et al., 2006). The collection of all Pareto-optimal solutions constitutes the Pareto front. + +A common metric used to estimate the quality of a Pareto set is the hypervolume (Balandat et al., 2020; Chugh, 2020; Emmerich et al., 2006). The hypervolume indicator calculates the n-dimensional volume (with n being the number of objective functions) of the region in the objective space that is dominated by the solution set and bounded by a reference point. This reference point is typically chosen to be worse than the worst possible values for each objective, but if unknown, can be inferred (Balandat et al., 2020). The hypervolume measure approaches its maximum if the set it covers is the true Pareto set (Fleischer 2003). + +Due to the challenges in ranking and evaluating the optimal point from the Pareto front (Rao and Lakshmi, 2021; Wang and Rangaiah, 2017), which involves assessing the feasibility region, the reasonableness of parameters, and often employing techniques like multicriteria decision analysis (MCDA), some users opt to scalarize their multi-objective optimization into a single-objective optimization (Chugh, 2020; Rasmussen and Williams, 2006) by creating a scalarized (linear) combination of the multiple objectives (e.g., by using the hypervolume). This scalarization simplifies the decision-making process by reducing the multi-dimensional objective space into a single dimension, making it easier to identify and select a single optimal solution. + +## High Dimensionality + +BO is often limited to 10-20 parameters, which in the optimization space are the equivalent of dimensions (Frazier 2018; Moriconi et al. 2020). However, with recent algorithms such as Sparse Axis-Aligned Subspace Bayesian Optimization (SAASBO) (Eriksson and Jankowiak 2021), that number can be pushed into the hundreds of dimensions. SAASBO is a high-dimensional BO algorithm that acts as structured priors over the kernel hyperparameters, expressly assuming a hierarchy of feature relevance. That is, given the dimensions d∈D, some subset of dimensions impacts the objective significantly, while others moderately, and the rest negligibly. + +SAASBO’s length scales are proportional to the half-Cauchy distribution, an inverse squared distribution concentrating around 0. Though the half-Cauchy distribution favors near 0 values, it is heavy-tailed. Because the length scales dictate how correlated values in the search space are, the pooling around 0 has the effect of most parameters being “turned off”, while because of the heavy tails the few most sensitive parameters can escape and become “turned-on” (Eriksson and Jankowiak 2021). + +## Additional Resources + +For more tutorials on Bayesian Optimization, please refer to the following resources: + +- Botorch has a comprehensive tutorial on Bayesian Optimization: https://botorch.org/docs/overview +- This is a great interactive tutorial on Gaussian Processes and kernels: https://distill.pub/2019/visual-exploration-gaussian-processes/ +- University of Toronto has a good slide show tutorial on Bayesian Optimization: https://www.cs.toronto.edu/~rgrosse/courses/csc411_f18/tutorials/tut8_adams_slides.pdf + +Additionally, the following papers provide a more in-depth look at Bayesian Optimization: + +## References + +Balandat, M., Karrer, B., Jiang, D.R., Daulton, S., Letham, B., Gordon Wilson, A., Bakshy, E., 2020. BOTORCH: a framework for efficient Monte-Carlo Bayesian optimization, Proceedings of the 34th International Conference on Neural Information Processing Systems. Curran Associates Inc.: Vancouver, BC, Canada, pp. 21524–21538. + +Brochu, E., Cora, V.M., de Freitas, N., 2010. A Tutorial on Bayesian Optimization of Expensive Cost Functions, with Application to Active User Modeling and Hierarchical Reinforcement Learning. + +Chugh, T., 2020. Scalarizing functions in Bayesian multiobjective optimization, 2020 IEEE Congress on Evolutionary Computation (CEC). IEEE Press, pp. 1-8. + +Daulton, S., Balandat, M., Bakshy, E., 2021. Parallel Bayesian optimization of multiple noisy objectives with expected hypervolume improvement, 35th Conference on Neural Information Processing Systems. + +Emmerich, M.T.M., Giannakoglou, K.C., Naujoks, B., 2006. Single- and multiobjective evolutionary optimization assisted by Gaussian random field metamodels. IEEE Transactions on Evolutionary Computation 10(4) 421-439. + +Eriksson, D., Jankowiak, M., 2021. High-dimensional Bayesian optimization with sparse axis-aligned subspaces, In: Cassio de, C., Marloes, H.M. (Eds.), Proceedings of the Thirty-Seventh Conference on Uncertainty in Artificial Intelligence. PMLR: Proceedings of Machine Learning Research, pp. 493-503. + +Fleischer, M., 2003. The measure of Pareto optima applications to multi-objective metaheuristics. Springer Berlin Heidelberg: Berlin, Heidelberg, pp. 519-533. + +Frazier, P.I., 2018. Bayesian Optimization, Recent Advances in Optimization and Modeling of Contemporary Problems, pp. 255-278. + +Gardner, J.R., Pleiss, G., Bindel, D., Weinberger, K.Q., Wilson, A.G., 2018. GPyTorch: blackbox matrix-matrix Gaussian process inference with GPU acceleration, Proceedings of the 32nd International Conference on Neural Information Processing Systems. Curran Associates Inc.: Montréal, Canada, pp. 7587–7597. + +Moriconi, R., Deisenroth, M.P., Sesh Kumar, K.S., 2020. High-dimensional Bayesian optimization using low-dimensional feature spaces. Machine Learning 109(9) 1925-1943. + +Phan-Trong, D., Tran-The, H., Gupta, S., 2023. NeuralBO: A black-box optimization algorithm using deep neural networks. Neurocomputing 559 126776. + +Rasmussen, C.E., Williams, C.K.I., 2006. Gaussian Processes for Machine Learning. MIT Press, Cambridge, MA. + +Riche, R.L., Picheny, V., 2021. Revisiting Bayesian Optimization in the light of the COCO benchmark. Struct Multidisc Optim 64, 3063–3087. https://doi.org/10.1007/s00158-021-02977-1 + +Snoek, J., Larochelle, H., Adams, R.P., 2012. Practical Bayesian optimization of machine learning algorithms. Advances in Neural Information Processing Systems 25 2960-2968. diff --git a/docs/user_guide/getting_started.rst b/docs/user_guide/getting_started.rst index fb4f837..9c3de50 100644 --- a/docs/user_guide/getting_started.rst +++ b/docs/user_guide/getting_started.rst @@ -115,3 +115,10 @@ if using pip to install BOA, run:: pip install -U boa-framework If you have errors, see the :doc:`/troubleshooting` section. + + +****************************** +Bayesian Optimization Overview +****************************** + +Head over to the :doc:`/user_guide/bo_overview` to read about Bayesian Optimization and how it works. diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst index 1848a53..2ae9167 100644 --- a/docs/user_guide/index.rst +++ b/docs/user_guide/index.rst @@ -6,5 +6,6 @@ User guide :maxdepth: 2 getting_started + bo_overview package_overview /api/boa.wrappers From 70876f5465adda823c3487674c977450a386c71a Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Mon, 22 Jul 2024 12:34:10 -0400 Subject: [PATCH 09/13] Add BoTorch_Modular to config, giving custom GP and acq func ability Now users can customize their GP (kernel, MLL, GP class) as well as the acq func in the config without writing any code. Giving users a lot more control and power from any language with little setup --- boa/config/converters.py | 35 +++++++++++ boa/registry.py | 42 ++++++++++++- tests/1unit_tests/test_generation_strategy.py | 33 ++++++++++ tests/conftest.py | 12 ++++ tests/integration_tests/test_cli.py | 7 ++- .../config_modular_botorch.yaml | 60 +++++++++++++++++++ 6 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml diff --git a/boa/config/converters.py b/boa/config/converters.py index 875c3fa..543139e 100644 --- a/boa/config/converters.py +++ b/boa/config/converters.py @@ -5,8 +5,13 @@ import ax.early_stopping.strategies as early_stopping_strats import ax.global_stopping.strategies as global_stopping_strats +import botorch.acquisition +import botorch.models +import gpytorch.kernels +import gpytorch.mlls from ax.modelbridge.generation_node import GenerationStep from ax.modelbridge.registry import Models +from ax.models.torch.botorch_modular.surrogate import Surrogate from ax.service.utils.instantiation import TParameterRepresentation from ax.service.utils.scheduler_options import SchedulerOptions @@ -49,6 +54,36 @@ def _gen_strat_converter(gs: Optional[dict] = None) -> dict: gs["steps"][i] = step steps.append(step) continue + if "model_kwargs" in step: + if "botorch_acqf_class" in step["model_kwargs"] and not isinstance( + step["model_kwargs"]["botorch_acqf_class"], botorch.acquisition.AcquisitionFunction + ): + step["model_kwargs"]["botorch_acqf_class"] = getattr( + botorch.acquisition, step["model_kwargs"]["botorch_acqf_class"] + ) + + if "surrogate" in step["model_kwargs"]: + if "mll_class" in step["model_kwargs"]["surrogate"] and not isinstance( + step["model_kwargs"]["surrogate"]["mll_class"], gpytorch.mlls.MarginalLogLikelihood + ): + step["model_kwargs"]["surrogate"]["mll_class"] = getattr( + gpytorch.mlls, step["model_kwargs"]["surrogate"]["mll_class"] + ) + if "botorch_model_class" in step["model_kwargs"]["surrogate"] and not isinstance( + step["model_kwargs"]["surrogate"]["botorch_model_class"], botorch.models.model.Model + ): + step["model_kwargs"]["surrogate"]["botorch_model_class"] = getattr( + botorch.models, step["model_kwargs"]["surrogate"]["botorch_model_class"] + ) + if "covar_module_class" in step["model_kwargs"]["surrogate"] and not isinstance( + step["model_kwargs"]["surrogate"]["covar_module_class"], gpytorch.kernels.Kernel + ): + step["model_kwargs"]["surrogate"]["covar_module_class"] = getattr( + gpytorch.kernels, step["model_kwargs"]["surrogate"]["covar_module_class"] + ) + + step["model_kwargs"]["surrogate"] = Surrogate(**step["model_kwargs"]["surrogate"]) + try: step["model"] = Models[step["model"]] except KeyError: diff --git a/boa/registry.py b/boa/registry.py index e7b4773..1b083b4 100644 --- a/boa/registry.py +++ b/boa/registry.py @@ -1,4 +1,22 @@ -from ax.storage.json_store.registry import CORE_DECODER_REGISTRY, CORE_ENCODER_REGISTRY +from __future__ import annotations + +from typing import Type + +import botorch.acquisition +import gpytorch.kernels +from ax.storage.botorch_modular_registry import ( + ACQUISITION_FUNCTION_REGISTRY, + CLASS_TO_REGISTRY, + CLASS_TO_REVERSE_REGISTRY, +) +from ax.storage.json_store.registry import ( + CORE_CLASS_DECODER_REGISTRY, + CORE_CLASS_ENCODER_REGISTRY, + CORE_DECODER_REGISTRY, + CORE_ENCODER_REGISTRY, + botorch_modular_to_dict, + class_from_json, +) def config_to_dict(inst): @@ -15,3 +33,25 @@ def _add_common_encodes_and_decodes(): CORE_ENCODER_REGISTRY[BOAConfig] = config_to_dict # CORE_DECODER_REGISTRY[BOAConfig.__name__] = BOAConfig CORE_DECODER_REGISTRY[MetricType.__name__] = MetricType + + CORE_CLASS_DECODER_REGISTRY["Type[Kernel]"] = class_from_json + CORE_CLASS_ENCODER_REGISTRY[gpytorch.kernels.Kernel] = botorch_modular_to_dict + + KERNEL_REGISTRY = {getattr(gpytorch.kernels, kernel): kernel for kernel in gpytorch.kernels.__all__} + + REVERSE_KERNEL_REGISTRY: dict[str, Type[gpytorch.kernels.Kernel]] = {v: k for k, v in KERNEL_REGISTRY.items()} + + CLASS_TO_REGISTRY[gpytorch.kernels.Kernel] = KERNEL_REGISTRY + CLASS_TO_REVERSE_REGISTRY[gpytorch.kernels.Kernel] = REVERSE_KERNEL_REGISTRY + + for acq_func_name in botorch.acquisition.__all__: + acq_func = getattr(botorch.acquisition, acq_func_name) + if acq_func not in ACQUISITION_FUNCTION_REGISTRY: + ACQUISITION_FUNCTION_REGISTRY[acq_func] = acq_func_name + + REVERSE_ACQUISITION_FUNCTION_REGISTRY: dict[str, Type[botorch.acquisition.AcquisitionFunction]] = { + v: k for k, v in ACQUISITION_FUNCTION_REGISTRY.items() + } + + CLASS_TO_REGISTRY[botorch.acquisition.AcquisitionFunction] = ACQUISITION_FUNCTION_REGISTRY + CLASS_TO_REVERSE_REGISTRY[botorch.acquisition.AcquisitionFunction] = REVERSE_ACQUISITION_FUNCTION_REGISTRY diff --git a/tests/1unit_tests/test_generation_strategy.py b/tests/1unit_tests/test_generation_strategy.py index 1ac1e57..faf5a06 100644 --- a/tests/1unit_tests/test_generation_strategy.py +++ b/tests/1unit_tests/test_generation_strategy.py @@ -1,3 +1,7 @@ +import botorch.acquisition +import botorch.models +import gpytorch.kernels +import gpytorch.mlls from ax.modelbridge.generation_strategy import GenerationStep, GenerationStrategy from ax.modelbridge.registry import Models @@ -41,3 +45,32 @@ def test_auto_gen_use_saasbo(saasbo_config, tmp_path): assert "SAASBO" in gs.name else: assert "FullyBayesian" in gs.name + + +def test_modular_botorch(gen_strat_modular_botorch_config, tmp_path): + controller = Controller( + config=gen_strat_modular_botorch_config, + wrapper=ScriptWrapper(config=gen_strat_modular_botorch_config, experiment_dir=tmp_path), + ) + exp = get_experiment( + config=controller.config, runner=WrappedJobRunner(wrapper=controller.wrapper), wrapper=controller.wrapper + ) + gs = get_generation_strategy(config=controller.config, experiment=exp) + cfg_botorch_modular = gen_strat_modular_botorch_config.orig_config["generation_strategy"]["steps"][-1] + step = gs._steps[-1] + assert step.model == Models.BOTORCH_MODULAR + mdl_kw = step.model_kwargs + assert mdl_kw["botorch_acqf_class"] == getattr( + botorch.acquisition, cfg_botorch_modular["model_kwargs"]["botorch_acqf_class"] + ) + assert mdl_kw["acquisition_options"] == cfg_botorch_modular["model_kwargs"]["acquisition_options"] + + assert mdl_kw["surrogate"].mll_class == getattr( + gpytorch.mlls, cfg_botorch_modular["model_kwargs"]["surrogate"]["mll_class"] + ) + assert mdl_kw["surrogate"].botorch_model_class == getattr( + botorch.models, cfg_botorch_modular["model_kwargs"]["surrogate"]["botorch_model_class"] + ) + assert mdl_kw["surrogate"].covar_module_class == getattr( + gpytorch.kernels, cfg_botorch_modular["model_kwargs"]["surrogate"]["covar_module_class"] + ) diff --git a/tests/conftest.py b/tests/conftest.py index 4ef90c1..c59e7f2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,6 +85,12 @@ def gen_strat1_config(): return BOAConfig.from_jsonlike(file=config_path) +@pytest.fixture +def gen_strat_modular_botorch_config(): + config_path = TEST_DIR / f"scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml" + return BOAConfig.from_jsonlike(file=config_path) + + @pytest.fixture def synth_config(): config_path = TEST_CONFIG_DIR / "test_config_synth.yaml" @@ -233,3 +239,9 @@ def r_streamlined(tmp_path_factory, cd_to_root_and_back_session): config_path = TEST_DIR / f"scripts/other_langs/r_package_streamlined/config.yaml" yield cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) + + +@pytest.fixture(scope="session") +def r_streamlined_botorch_modular(tmp_path_factory, cd_to_root_and_back_session): + config_path = TEST_DIR / f"scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml" + return cli_main(split_shell_command(f"--config-path {config_path} -td"), standalone_mode=False) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 7f0fe2c..230d963 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -75,9 +75,12 @@ def test_calling_command_line_test_script_doesnt_error_out_and_produces_correct_ # parametrize the test to use the full version (all scripts) or the light version (only run_model.R) # or parametrize the test to use the streamlined version (doesn't use trial_status.json, only use output.json) +# the botorch modular version is the same as the streamlined version, but also uses botorch modular +# which uses a custom kernel, acquisition function, mll and botorch model class +# (which can customize the GP process even more) @pytest.mark.parametrize( "r_scripts_run", - ["r_full", "r_light", "r_streamlined"], + ["r_full", "r_light", "r_streamlined", "r_streamlined_botorch_modular"], ) @pytest.mark.skipif(not R_INSTALLED, reason="requires R to be installed") def test_calling_command_line_r_test_scripts(r_scripts_run, request): @@ -92,7 +95,7 @@ def test_calling_command_line_r_test_scripts(r_scripts_run, request): assert "param_names" in data assert "metric_properties" in data - if "r_streamlined" == r_scripts_run: + if r_scripts_run in ("r_streamlined", "r_streamlined_botorch_modular"): with cd_and_cd_back(scheduler.wrapper.config_path.parent): pre_num_trials = len(scheduler.experiment.trials) diff --git a/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml b/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml new file mode 100644 index 0000000..f0b4aa1 --- /dev/null +++ b/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml @@ -0,0 +1,60 @@ +objective: + metrics: + - name: metric +scheduler: + n_trials: 15 + +parameters: + x0: + 'bounds': [ 0, 1 ] + 'type': 'range' + 'value_type': 'float' + x1: + 'bounds': [ 0, 1] + 'type': 'range' + 'value_type': 'float' + x2: + 'bounds': [ 0, 1 ] + 'type': 'range' + 'value_type': 'float' + x3: + 'bounds': [ 0, 1] + 'type': 'range' + 'value_type': 'float' + x4: + 'bounds': [ 0, 1 ] + 'type': 'range' + 'value_type': 'float' + x5: + 'bounds': [ 0, 1] + 'type': 'range' + 'value_type': 'float' + +script_options: + # notice here that this is a shell command + # this is what BOA will do to launch your script + # it will also pass as a command line argument the current trial directory + # that is being parameterized + + # This can either be a relative path or absolute path + # (by default when BOA launches from a config file + # it uses the config file directory as your working directory) + # here config.yaml and run_model.R are in the same directory + run_model: Rscript run_model.R + exp_name: "r_streamlined_botorch_modular" + +generation_strategy: + steps: + - model: SOBOL + num_trials: 5 + - model: BOTORCH_MODULAR + num_trials: -1 # No limitation on how many trials should be produced from this step + model_kwargs: + surrogate: + botorch_model_class: SingleTaskGP # BoTorch model class name + + covar_module_class: RBFKernel # GPyTorch kernel class name + mll_class: LeaveOneOutPseudoLikelihood + botorch_acqf_class: qUpperConfidenceBound # BoTorch acquisition function class name + acquisition_options: + beta: 0.5 From 3d3257100a30ed0a3cd634454028884e27d5675b Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Mon, 22 Jul 2024 13:07:29 -0400 Subject: [PATCH 10/13] Add some documentation notes about customizing GP and acq func --- docs/user_guide/customizing_gp_acq.md | 60 +++++++++++++++++++++++++++ docs/user_guide/index.rst | 1 + 2 files changed, 61 insertions(+) create mode 100644 docs/user_guide/customizing_gp_acq.md diff --git a/docs/user_guide/customizing_gp_acq.md b/docs/user_guide/customizing_gp_acq.md new file mode 100644 index 0000000..d4b7eb3 --- /dev/null +++ b/docs/user_guide/customizing_gp_acq.md @@ -0,0 +1,60 @@ +# Customizing Your Gaussian Process and Acquisition Function + +BOA is designed with flexibility of options for selecting the acquisition function and +kernel. BOA could be used by advanced BO users that want to control detailed aspects of the +optimization. However, for non-domain experts of BO, BOA defaults to common sensible +choices. BOA defers to BoTorch's defaults of a Matern 5/2 kernel, one of the most widely +used and flexible choices for BO and GPs (Frazier, 2018; Riche and Picheny, 2021; Jakeman, +2023). This is considered to be a flexible and broadly applicable kernel (Riche and Picheny,2021) and it is used as the default by many other BO and GP toolkits (Akiba et al., 2019; +Balandat et al., 2020; Brea, 2023; Nogueira, 2014; Jakeman, 2023). Similarly, BOA defaults +to Expected Improvement (EI) for single-objective functions and Expected Hypervolume +Improvement (EHVI) for multi-objective problems, which are well-regarded for their +performance across a broad range of applications (Balandat et al., 2020; Daulton et al., 2021; +Frazier, 2018). Users do have the option to explicitly control steps in the optimization process +in the ‘generation strategy’ section of the configuration, for example, to control the number of +Sobol trials, if SAASBO should be utilized, etc. As an advanced use case, users can also +explicitly set up different kernels and acquisition functions for different stages in the +optimization if they so choose. On this page, we will go over both ways to customize, as well as link to some examples of how to do so. + +## General Control Options + +The `generation_strategy` section of the configuration file is where you can control the optimization process. This includes the number of Sobol trials, the number of initial points, the number of iterations, and whether to use SAASBO. The `generation_strategy` section is shown below with common options: + +```yaml +generation_strategy: + # Specific number of initialization trials, + # Typically, initialization trials are generated quasi-randomly. + num_initialization_trials: 50 + # Integer, with which to override the default max parallelism setting for all + # steps in the generation strategy returned from this function. Each generation + # step has a ``max_parallelism`` value, which restricts how many trials can run + # simultaneously during a given generation step. + max_parallelism_override: 10 + # Whether to use SAAS prior for any GPEI generation steps. + # See, BO overview page, high dimensionality section for more details. + use_saasbo: true + random_seed: 42 # Fixed random seed for the Sobol generator. +``` + +## Utilizing Ax's Predefined Kernels and Acquisition Functions + + + + +## References + +Akiba, T., Sano, S., Yanase, T., Ohta, T., Koyama, M., 2019. Optuna: A next-generation hyperparameter optimization framework, Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. Association for Computing Machinery: Anchorage, AK, USA, pp. 2623–2631. + +Balandat, M., Karrer, B., Jiang, D.R., Daulton, S., Letham, B., Gordon Wilson, A., Bakshy, E., 2020. BOTORCH: a framework for efficient Monte-Carlo Bayesian optimization, Proceedings of the 34th International Conference on Neural Information Processing Systems. Curran Associates Inc.: Vancouver, BC, Canada, pp. 21524–21538. + +Brea, J., 2023. BayesianOptimization.jl. https://github.com/jbrea/BayesianOptimization.jl (accessed 17 July 2024) + +Daulton, S., Balandat, M., Bakshy, E., 2021. Parallel Bayesian optimization of multiple noisy objectives with expected hypervolume improvement, 35th Conference on Neural Information Processing Systems. + +Frazier, P.I., 2018. Bayesian Optimization, Recent Advances in Optimization and Modeling of Contemporary Problems, pp. 255-278. + +Jakeman, J.D., 2023. PyApprox: A software package for sensitivity analysis, Bayesian inference, optimal experimental design, and multi-fidelity uncertainty quantification and surrogate modeling. Environmental Modelling & Software 170 105825. + +Nogueira, F., 2014. Bayesian Optimization: Open source constrained global optimization tool for Python. https://github.com/bayesian-optimization/BayesianOptimization (accessed 17 July 2024) + +Riche, R.L., Picheny, V., 2021. Revisiting Bayesian Optimization in the light of the COCO benchmark. Struct Multidisc Optim 64, 3063–3087. https://doi.org/10.1007/s00158-021-02977-1 diff --git a/docs/user_guide/index.rst b/docs/user_guide/index.rst index 2ae9167..240078d 100644 --- a/docs/user_guide/index.rst +++ b/docs/user_guide/index.rst @@ -8,4 +8,5 @@ User guide getting_started bo_overview package_overview + customizing_gp_acq /api/boa.wrappers From de06e691a6db36306b478cb2e4f32d0df598848b Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Mon, 22 Jul 2024 14:35:53 -0400 Subject: [PATCH 11/13] Make modular botorch config require Ax 0.3.5 and above --- boa/config/converters.py | 8 ++++++++ tests/1unit_tests/test_generation_strategy.py | 4 ++++ tests/integration_tests/test_cli.py | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/boa/config/converters.py b/boa/config/converters.py index 543139e..0b42b9d 100644 --- a/boa/config/converters.py +++ b/boa/config/converters.py @@ -15,6 +15,8 @@ from ax.service.utils.instantiation import TParameterRepresentation from ax.service.utils.scheduler_options import SchedulerOptions +from boa.utils import check_min_package_version + if TYPE_CHECKING: from .config import BOAMetric @@ -54,6 +56,12 @@ def _gen_strat_converter(gs: Optional[dict] = None) -> dict: gs["steps"][i] = step steps.append(step) continue + if step["model"] == "BOTORCH_MODULAR" and not check_min_package_version("ax-platform", "0.3.5"): + raise ValueError( + "BOTORCH_MODULAR model is not available in BOA with Ax version < 0.3.5. " + "Please upgrade to a newer version of Ax." + ) + if "model_kwargs" in step: if "botorch_acqf_class" in step["model_kwargs"] and not isinstance( step["model_kwargs"]["botorch_acqf_class"], botorch.acquisition.AcquisitionFunction diff --git a/tests/1unit_tests/test_generation_strategy.py b/tests/1unit_tests/test_generation_strategy.py index faf5a06..84a8610 100644 --- a/tests/1unit_tests/test_generation_strategy.py +++ b/tests/1unit_tests/test_generation_strategy.py @@ -2,6 +2,7 @@ import botorch.models import gpytorch.kernels import gpytorch.mlls +import pytest from ax.modelbridge.generation_strategy import GenerationStep, GenerationStrategy from ax.modelbridge.registry import Models @@ -47,6 +48,9 @@ def test_auto_gen_use_saasbo(saasbo_config, tmp_path): assert "FullyBayesian" in gs.name +@pytest.importorskip( + "ax-platform", minversion="0.3.5", reason="BOTORCH_MODULAR model is not available in BOA with Ax version < 0.3.5." +) def test_modular_botorch(gen_strat_modular_botorch_config, tmp_path): controller = Controller( config=gen_strat_modular_botorch_config, diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 230d963..824005d 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -12,7 +12,6 @@ get_trial_dir, load_jsonlike, scheduler_from_json_file, - scheduler_to_json_file, split_shell_command, ) from boa.cli import main as cli_main @@ -80,7 +79,20 @@ def test_calling_command_line_test_script_doesnt_error_out_and_produces_correct_ # (which can customize the GP process even more) @pytest.mark.parametrize( "r_scripts_run", - ["r_full", "r_light", "r_streamlined", "r_streamlined_botorch_modular"], + [ + "r_full", + "r_light", + "r_streamlined", + "r_streamlined_botorch_modular", + pytest.param( + "r_streamlined_botorch_modular", + marks=pytest.importorskip( + "ax-platform", + minversion="0.3.5", + reason="BOTORCH_MODULAR model is not available in BOA with Ax version < 0.3.5.", + ), + ), + ], ) @pytest.mark.skipif(not R_INSTALLED, reason="requires R to be installed") def test_calling_command_line_r_test_scripts(r_scripts_run, request): From 24a279ae652766e4fbf71e167b47eede5aa4ef0f Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Mon, 22 Jul 2024 15:25:32 -0400 Subject: [PATCH 12/13] Docs updates for kernel and acq func --- docs/user_guide/customizing_gp_acq.md | 57 ++++++++++++++++++- docs/user_guide/package_overview.rst | 13 ++++- .../config_modular_botorch.yaml | 2 +- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/customizing_gp_acq.md b/docs/user_guide/customizing_gp_acq.md index d4b7eb3..6064990 100644 --- a/docs/user_guide/customizing_gp_acq.md +++ b/docs/user_guide/customizing_gp_acq.md @@ -3,7 +3,7 @@ BOA is designed with flexibility of options for selecting the acquisition function and kernel. BOA could be used by advanced BO users that want to control detailed aspects of the optimization. However, for non-domain experts of BO, BOA defaults to common sensible -choices. BOA defers to BoTorch's defaults of a Matern 5/2 kernel, one of the most widely +choices. BOA defers to BoTorch's defaults of a Matern 5/2 kernel, one of the most widely used and flexible choices for BO and GPs (Frazier, 2018; Riche and Picheny, 2021; Jakeman, 2023). This is considered to be a flexible and broadly applicable kernel (Riche and Picheny,2021) and it is used as the default by many other BO and GP toolkits (Akiba et al., 2019; Balandat et al., 2020; Brea, 2023; Nogueira, 2014; Jakeman, 2023). Similarly, BOA defaults @@ -38,6 +38,61 @@ generation_strategy: ## Utilizing Ax's Predefined Kernels and Acquisition Functions +Ax has a number of predefined kernels and acquisition function combos that can be used in the optimization process. Each of these sit inside a "step" inside the generation strategy, where your optimization is broken into a number of "steps" and each step can have its own kernel and acquisition function. For example, the first step is usually a Sobol step that does a quasi-random initialization of the optimization process. The second step could be a "GPEI" step (GPEI is the Ax model class name, and is the default used for single objective optimization) that uses the Matern 5/2 kernel and the batched noisy Expected Improvement acquisition function. + +```yaml + +generation_strategy: + steps: + - model: Sobol + num_trials: 50 + # specify the maximum number of trials to run in parallel + # + max_parallelism: 10 + - model: GPEI + num_trials: -1 # -1 means the rest of the trials + max_parallelism: 10 +``` + +Ax does not have a good spot in their docs currently that lists all the available kernels and acquisition functions combo models, but you can find them listed on their [api docs here](https://ax.dev/api/modelbridge.html#ax.modelbridge.registry.Models) and you can see the source code for the models by clicking the source link on the api docs page. Some of the available models are: + +- `GPEI`: Gaussian Process Expected Improvement, the default for single objective optimization, uses the Matern 5/2 kernel +- `GPKG`: Gaussian Process Knowledge Gradient, uses the Matern 5/2 kernel +- `SAASBO`: Sparse Axis-Aligned Subspace Bayesian Optimization, see [BO Overview High Dimensionality](bo_overview.md#high-dimensionality) for more details, uses the Matern 5/2 kernel and the batched noisy Expected Improvement acquisition function +- `Sobol`: Sobol initialization +- `MOO`: Gaussian Process Expected Hypervolume Improvement, uses the Matern 5/2 kernel + +If you want to specify your kernel and acquisition function, you can do so by creating a custom model. The way to do that is with the `BOTORCH_MODULAR` model. This model allows you to specify the kernel and acquisition function you want to use. Here is an example of how to use the `BOTORCH_MODULAR` model: + +```yaml +generation_strategy: + steps: + - model: SOBOL + num_trials: 5 + - model: BOTORCH_MODULAR + num_trials: -1 # No limitation on how many trials should be produced from this step + model_kwargs: + surrogate: + botorch_model_class: SingleTaskGP # BoTorch model class name + covar_module_class: RBFKernel # GPyTorch kernel class name + mll_class: LeaveOneOutPseudoLikelihood # GPyTorch MarginalLogLikelihood class name + botorch_acqf_class: qUpperConfidenceBound # BoTorch acquisition function class name + acquisition_options: + beta: 0.5 +``` + +In the above example, the `BOTORCH_MODULAR` model is used to specify the `SingleTaskGP` model class, the `RBFKernel` kernel class, and the `qUpperConfidenceBound` acquisition function class. The `qUpperConfidenceBound` acquisition function is a batched version of UpperConfidenceBound. The `beta` parameter is a hyperparameter of the acquisition function that controls the trade-off between exploration and exploitation. + +BoTorch model classes can be found in the [BoTorch model api documentation](https://botorch.org/docs/models) and the BoTorch acquisition functions can be found in the [BoTorch acquisition api documentation](https://botorch.org/api/acquisition.html). + +GPyTorch kernel classes can be found in the [GPyTorch kernel api documentation](https://gpytorch.readthedocs.io/en/latest/kernels.html). + +The GPyTorch MarginalLogLikelihood classes can be found in the [GPyTorch MarginalLogLikelihood api documentation](https://gpytorch.readthedocs.io/en/latest/marginal_log_likelihoods.html). But the only MLL class that for sure work currently are `ExactMarginalLogLikelihood` and `LeaveOneOutPseudoLikelihood`. Other MLL classes may work, but they have not been tested and are depended on some other implementation details in Ax. + + +```{caution} +The `BOTORCH_MODULAR` class is an area of Ax's code that is still under active development and a lot of components of it are very dependent on the current implementation of Ax, BoTorch, and GPyTorch, and therefore it is impossible to test every possible combination of kernel and acquisition function. Therefore, it is recommended to use when possible the predefined models that Ax provides. +``` diff --git a/docs/user_guide/package_overview.rst b/docs/user_guide/package_overview.rst index 3c53c49..6ec641c 100644 --- a/docs/user_guide/package_overview.rst +++ b/docs/user_guide/package_overview.rst @@ -26,8 +26,6 @@ Objective functions When specifying your objective function to minimize or maximize, :doc:`BOA ` comes with a number of metrics you can use with your model output, such as MSE, :math:`R^2`, and others. For a list of current list of premade available of metrics, see See :mod:`.metrics.metrics` - - ************************************************************************ Creating a model wrapper (Language Agnostic or Python API) ************************************************************************ @@ -39,6 +37,17 @@ and there is a standard interface to follow. See the :mod:`instructions for creating a model wrapper <.boa.wrappers>` for details. +See the :doc:`examples of model wrappers <.boa.wrappers>` for examples. +See :doc:`tutorials ` for a number of examples of model wrappers in both Python and R. + + +************************************************************************ +Choosing a Custom Kernel and Acquisition Function +************************************************************************ + +BOA tries to make it easy to use the default kernel and acquisition function, but if you need to specify a different kernel or acquisition function, you can do so in the configuration file. + +See :doc:`details on how to specify kernel and acquisition function ` for details. **************************************************** Creating a Python launch script (Usually Not Needed) diff --git a/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml b/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml index f0b4aa1..42808ae 100644 --- a/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml +++ b/tests/scripts/other_langs/r_package_streamlined/config_modular_botorch.yaml @@ -54,7 +54,7 @@ generation_strategy: botorch_model_class: SingleTaskGP # BoTorch model class name covar_module_class: RBFKernel # GPyTorch kernel class name - mll_class: LeaveOneOutPseudoLikelihood + mll_class: LeaveOneOutPseudoLikelihood # GPyTorch MarginalLogLikelihood class name botorch_acqf_class: qUpperConfidenceBound # BoTorch acquisition function class name acquisition_options: beta: 0.5 From f75fe5f07c1b40951370c1721d133aef9549e083 Mon Sep 17 00:00:00 2001 From: Madeline Scyphers Date: Mon, 22 Jul 2024 15:56:35 -0400 Subject: [PATCH 13/13] Change examples toc --- docs/examples/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/examples/index.rst b/docs/examples/index.rst index a89b69c..6d146cb 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -3,7 +3,7 @@ Examples ######## .. toctree:: - :caption: Examples with Synthetic Functions in R on the `BOA Paper Examples Page `_ + :caption: Examples with Synthetic Functions in R on the BOA Paper Examples Page (https://boa-paper.readthedocs.io/en/latest/) :maxdepth: 2 Branin @@ -12,9 +12,8 @@ Examples Hartmann6 with Constraints Hartmann6 with Global Stopping Strategy - .. toctree:: - :caption: Examples with Environmental Models on the `BOA Paper Examples Page `_ + :caption: Examples with Environmental Models on the BOA Paper Examples Page (https://boa-paper.readthedocs.io/en/latest/) :maxdepth: 2 SWAT+