diff --git a/package/samplers/auto_sampler/README.md b/package/samplers/auto_sampler/README.md index 78ffb581..eaad4307 100644 --- a/package/samplers/auto_sampler/README.md +++ b/package/samplers/auto_sampler/README.md @@ -9,7 +9,11 @@ license: MIT License ## Abstract -This package automatically selects an appropriate sampler for the provided search space based on the developers' recommendation. +This package automatically selects an appropriate sampler for the provided search space based on the developers' recommendation. The following article provides detailed information about AutoSampler. + +- 📰 [AutoSampler: Automatic Selection of Optimization Algorithms in Optuna](https://medium.com/optuna/autosampler-automatic-selection-of-optimization-algorithms-in-optuna-1443875fd8f9) + +![Concept of AutoSampler](images/autosampler.png) ## Class or Function Names diff --git a/package/samplers/auto_sampler/_sampler.py b/package/samplers/auto_sampler/_sampler.py index 55669274..8815ee8f 100644 --- a/package/samplers/auto_sampler/_sampler.py +++ b/package/samplers/auto_sampler/_sampler.py @@ -6,6 +6,7 @@ from typing import Any from typing import TYPE_CHECKING +import cmaes as _ # NOQA from optuna.distributions import CategoricalDistribution from optuna.logging import get_logger from optuna.samplers import BaseSampler @@ -18,6 +19,8 @@ from optuna.samplers._lazy_random_state import LazyRandomState from optuna.search_space import IntersectionSearchSpace from optuna.trial import TrialState +import scipy as _ # NOQA +import torch as _ # NOQA if TYPE_CHECKING: diff --git a/package/samplers/auto_sampler/images/autosampler.png b/package/samplers/auto_sampler/images/autosampler.png new file mode 100644 index 00000000..5f31d397 Binary files /dev/null and b/package/samplers/auto_sampler/images/autosampler.png differ diff --git a/package/samplers/cmamae/LICENSE b/package/samplers/cmamae/LICENSE new file mode 100644 index 00000000..51c2bdfd --- /dev/null +++ b/package/samplers/cmamae/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Bryon Tjanaka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/samplers/cmamae/README.md b/package/samplers/cmamae/README.md new file mode 100644 index 00000000..7801df9f --- /dev/null +++ b/package/samplers/cmamae/README.md @@ -0,0 +1,148 @@ +--- +author: Bryon Tjanaka +title: CMA-MAE Sampler +description: This sampler searches for solutions using CMA-MAE, a quality diversity algorihm implemented in pyribs. +tags: [sampler, quality diversity, pyribs] +optuna_versions: [4.0.0] +license: MIT License +--- + +## Abstract + +This package provides a sampler using CMA-MAE as implemented in pyribs. +[CMA-MAE](https://dl.acm.org/doi/abs/10.1145/3583131.3590389) is a quality +diversity algorithm that has demonstrated state-of-the-art performance in a +variety of domains. [Pyribs](https://pyribs.org) is a bare-bones Python library +for quality diversity optimization algorithms. For a primer on CMA-MAE, quality +diversity, and pyribs, we recommend referring to the series of +[pyribs tutorials](https://docs.pyribs.org/en/stable/tutorials.html). + +For simplicity, this implementation provides a default instantiation of CMA-MAE +with a +[GridArchive](https://docs.pyribs.org/en/stable/api/ribs.archives.GridArchive.html) +and +[EvolutionStrategyEmitter](https://docs.pyribs.org/en/stable/api/ribs.emitters.EvolutionStrategyEmitter.html) +with improvement ranking, all wrapped up in a +[Scheduler](https://docs.pyribs.org/en/stable/api/ribs.schedulers.Scheduler.html). +However, it is possible to implement many variations of CMA-MAE and other +quality diversity algorithms using pyribs. + +## Class or Function Names + +- CmaMaeSampler + +Please take a look at: + +- [GridArchive](https://docs.pyribs.org/en/stable/api/ribs.archives.GridArchive.html), and +- [EvolutionStrategyEmitter](https://docs.pyribs.org/en/stable/api/ribs.emitters.EvolutionStrategyEmitter.html) + for the details of each argument. + +## Installation + +```shell +$ pip install ribs +``` + +## Example + +```python +import optuna +import optunahub + +module = optunahub.load_module("samplers/cmamae") +CmaMaeSampler = module.CmaMaeSampler + + +def objective(trial: optuna.trial.Trial) -> float: + """Returns an objective followed by two measures.""" + x = trial.suggest_float("x", -10, 10) + y = trial.suggest_float("y", -10, 10) + trial.set_user_attr("m0", 2 * x) + trial.set_user_attr("m1", x + y) + return x**2 + y**2 + + +if __name__ == "__main__": + sampler = CmaMaeSampler( + param_names=["x", "y"], + measure_names=["m0", "m1"], + archive_dims=[20, 20], + archive_ranges=[(-1, 1), (-1, 1)], + archive_learning_rate=0.1, + archive_threshold_min=-10, + n_emitters=1, + emitter_x0={ + "x": 0, + "y": 0, + }, + emitter_sigma0=0.1, + emitter_batch_size=20, + ) + study = optuna.create_study(sampler=sampler) + study.optimize(objective, n_trials=10000) +``` + +## Others + +### Reference + +#### CMA-MAE + +Matthew Fontaine and Stefanos Nikolaidis. 2023. Covariance Matrix Adaptation +MAP-Annealing. In Proceedings of the Genetic and Evolutionary Computation +Conference (GECCO '23). Association for Computing Machinery, New York, NY, USA, +456–465. https://doi.org/10.1145/3583131.3590389 + +#### Pyribs + +Bryon Tjanaka, Matthew C Fontaine, David H Lee, Yulun Zhang, Nivedit Reddy +Balam, Nathaniel Dennler, Sujay S Garlanka, Nikitas Dimitri Klapsis, and +Stefanos Nikolaidis. 2023. Pyribs: A Bare-Bones Python Library for Quality +Diversity Optimization. In Proceedings of the Genetic and Evolutionary +Computation Conference (GECCO '23). Association for Computing Machinery, New +York, NY, USA, 220–229. https://doi.org/10.1145/3583131.3590374 + +### Bibtex + +#### CMA-MAE + +``` +@inproceedings{10.1145/3583131.3590389, + author = {Fontaine, Matthew and Nikolaidis, Stefanos}, + title = {Covariance Matrix Adaptation MAP-Annealing}, + year = {2023}, + isbn = {9798400701191}, + publisher = {Association for Computing Machinery}, + address = {New York, NY, USA}, + url = {https://doi.org/10.1145/3583131.3590389}, + doi = {10.1145/3583131.3590389}, + abstract = {Single-objective optimization algorithms search for the single highest-quality solution with respect to an objective. Quality diversity (QD) optimization algorithms, such as Covariance Matrix Adaptation MAP-Elites (CMA-ME), search for a collection of solutions that are both high-quality with respect to an objective and diverse with respect to specified measure functions. However, CMA-ME suffers from three major limitations highlighted by the QD community: prematurely abandoning the objective in favor of exploration, struggling to explore flat objectives, and having poor performance for low-resolution archives. We propose a new quality diversity algorithm, Covariance Matrix Adaptation MAP-Annealing (CMA-MAE), that addresses all three limitations. We provide theoretical justifications for the new algorithm with respect to each limitation. Our theory informs our experiments, which support the theory and show that CMA-MAE achieves state-of-the-art performance and robustness.}, + booktitle = {Proceedings of the Genetic and Evolutionary Computation Conference}, + pages = {456–465}, + numpages = {10}, + location = {Lisbon, Portugal}, + series = {GECCO '23} +} +``` + +#### Pyribs + +``` +@inproceedings{10.1145/3583131.3590374, + author = {Tjanaka, Bryon and Fontaine, Matthew C and Lee, David H and Zhang, Yulun and Balam, Nivedit Reddy and Dennler, Nathaniel and Garlanka, Sujay S and Klapsis, Nikitas Dimitri and Nikolaidis, Stefanos}, + title = {pyribs: A Bare-Bones Python Library for Quality Diversity Optimization}, + year = {2023}, + isbn = {9798400701191}, + publisher = {Association for Computing Machinery}, + address = {New York, NY, USA}, + url = {https://doi.org/10.1145/3583131.3590374}, + doi = {10.1145/3583131.3590374}, + abstract = {Recent years have seen a rise in the popularity of quality diversity (QD) optimization, a branch of optimization that seeks to find a collection of diverse, high-performing solutions to a given problem. To grow further, we believe the QD community faces two challenges: developing a framework to represent the field's growing array of algorithms, and implementing that framework in software that supports a range of researchers and practitioners. To address these challenges, we have developed pyribs, a library built on a highly modular conceptual QD framework. By replacing components in the conceptual framework, and hence in pyribs, users can compose algorithms from across the QD literature; equally important, they can identify unexplored algorithm variations. Furthermore, pyribs makes this framework simple, flexible, and accessible, with a user-friendly API supported by extensive documentation and tutorials. This paper overviews the creation of pyribs, focusing on the conceptual framework that it implements and the design principles that have guided the library's development. Pyribs is available at https://pyribs.org}, + booktitle = {Proceedings of the Genetic and Evolutionary Computation Conference}, + pages = {220–229}, + numpages = {10}, + keywords = {software library, framework, quality diversity}, + location = {Lisbon, Portugal}, + series = {GECCO '23} +} +``` diff --git a/package/samplers/cmamae/__init__.py b/package/samplers/cmamae/__init__.py new file mode 100644 index 00000000..e724cd42 --- /dev/null +++ b/package/samplers/cmamae/__init__.py @@ -0,0 +1,4 @@ +from .sampler import CmaMaeSampler + + +__all__ = ["CmaMaeSampler"] diff --git a/package/samplers/cmamae/example.py b/package/samplers/cmamae/example.py new file mode 100644 index 00000000..2903a81a --- /dev/null +++ b/package/samplers/cmamae/example.py @@ -0,0 +1,35 @@ +import optuna +import optunahub + + +module = optunahub.load_module("samplers/cmamae") +CmaMaeSampler = module.CmaMaeSampler + + +def objective(trial: optuna.trial.Trial) -> float: + """Returns an objective followed by two measures.""" + x = trial.suggest_float("x", -10, 10) + y = trial.suggest_float("y", -10, 10) + trial.set_user_attr("m0", 2 * x) + trial.set_user_attr("m1", x + y) + return x**2 + y**2 + + +if __name__ == "__main__": + sampler = CmaMaeSampler( + param_names=["x", "y"], + measure_names=["m0", "m1"], + archive_dims=[20, 20], + archive_ranges=[(-1, 1), (-1, 1)], + archive_learning_rate=0.1, + archive_threshold_min=-10, + n_emitters=1, + emitter_x0={ + "x": 0, + "y": 0, + }, + emitter_sigma0=0.1, + emitter_batch_size=20, + ) + study = optuna.create_study(sampler=sampler) + study.optimize(objective, n_trials=10000) diff --git a/package/samplers/cmamae/requirements.txt b/package/samplers/cmamae/requirements.txt new file mode 100644 index 00000000..4572f714 --- /dev/null +++ b/package/samplers/cmamae/requirements.txt @@ -0,0 +1,3 @@ +optuna +optunahub +ribs diff --git a/package/samplers/cmamae/sampler.py b/package/samplers/cmamae/sampler.py new file mode 100644 index 00000000..77f119ff --- /dev/null +++ b/package/samplers/cmamae/sampler.py @@ -0,0 +1,229 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import Iterable + +import numpy as np +from optuna.distributions import BaseDistribution +from optuna.distributions import FloatDistribution +from optuna.study import Study +from optuna.study import StudyDirection +from optuna.trial import FrozenTrial +from optuna.trial import TrialState +import optunahub +from ribs.archives import GridArchive +from ribs.emitters import EvolutionStrategyEmitter +from ribs.schedulers import Scheduler + + +class CmaMaeSampler(optunahub.samplers.SimpleBaseSampler): + """A sampler using CMA-MAE as implemented in pyribs. + + `CMA-MAE `_ is a quality + diversity algorithm that has demonstrated state-of-the-art performance in a + variety of domains. `Pyribs `_ is a bare-bones Python + library for quality diversity optimization algorithms. For a primer on + CMA-MAE, quality diversity, and pyribs, we recommend referring to the series + of `pyribs tutorials `_. + + For simplicity, this implementation provides a default instantiation of + CMA-MAE with a `GridArchive + `_ and + `EvolutionStrategyEmitter + `_ + with improvement ranking, all wrapped up in a `Scheduler + `_. + However, it is possible to implement many variations of CMA-MAE and other + quality diversity algorithms using pyribs. + + Note that this sampler assumes the measures are set to user_attrs of each trial. + To do so, please call ``trial.set_user_attr("YOUR MEASURE NAME", measure_value)`` for each + measure. + + Args: + param_names: List of names of parameters to optimize. + measure_names: List of names of measures. + archive_dims: Number of archive cells in each dimension of the measure + space, e.g. ``[20, 30, 40]`` indicates there should be 3 dimensions + with 20, 30, and 40 cells. (The number of dimensions is implicitly + defined in the length of this argument). + archive_ranges: Upper and lower bound of each dimension of the measure + space for the archive, e.g. ``[(-1, 1), (-2, 2)]`` indicates the + first dimension should have bounds :math:`[-1,1]` (inclusive), and + the second dimension should have bounds :math:`[-2,2]` (inclusive). + ``ranges`` should be the same length as ``dims``. + archive_learning_rate: The learning rate for threshold updates in the + archive. + archive_threshold_min: The initial threshold value for all the cells in + the archive. + n_emitters: Number of emitters to use in CMA-MAE. + emitter_x0: Mapping from parameter names to their initial values. + emitter_sigma0: Initial step size / standard deviation of the + distribution from which solutions are sampled in the emitter. + emitter_batch_size: Number of solutions for each emitter to generate on + each iteration. + """ + + def __init__( + self, + *, + param_names: list[str], + measure_names: list[str], + archive_dims: list[int], + archive_ranges: list[tuple[float, float]], + archive_learning_rate: float, + archive_threshold_min: float, + n_emitters: int, + emitter_x0: dict[str, float], + emitter_sigma0: float, + emitter_batch_size: int, + ) -> None: + self._validate_params(param_names, emitter_x0) + self._param_names = param_names.copy() + self._measure_names = measure_names.copy() + if len(set(self._measure_names)) != 2: + raise ValueError( + "measure_names must be a list of two unique measure names, " + f"but got measure_names={measure_names}." + ) + + # NOTE: SimpleBaseSampler must know Optuna search_space information. + search_space = {name: FloatDistribution(-1e9, 1e9) for name in self._param_names} + super().__init__(search_space=search_space) + + emitter_x0_np = self._convert_to_pyribs_params(emitter_x0) + + archive = GridArchive( + solution_dim=len(param_names), + dims=archive_dims, + ranges=archive_ranges, + learning_rate=archive_learning_rate, + threshold_min=archive_threshold_min, + ) + result_archive = GridArchive( + solution_dim=len(param_names), + dims=archive_dims, + ranges=archive_ranges, + ) + emitters = [ + EvolutionStrategyEmitter( + archive, + x0=emitter_x0_np, + sigma0=emitter_sigma0, + ranker="imp", + selection_rule="mu", + restart_rule="basic", + batch_size=emitter_batch_size, + ) + for _ in range(n_emitters) + ] + + # Number of solutions generated in each batch from pyribs. + self._batch_size = n_emitters * emitter_batch_size + + # Public to allow access for, e.g., visualization. + self.scheduler = Scheduler( + archive, + emitters, + result_archive=result_archive, + ) + + self._values_to_tell: list[list[float]] = [] + self._stored_trial_numbers: list[int] = [] + + def _validate_params(self, param_names: list[str], emitter_x0: dict[str, float]) -> None: + dim = len(param_names) + param_set = set(param_names) + if dim != len(param_set): + raise ValueError( + "Some elements in param_names are duplicated. Please make it a unique list." + ) + + if set(param_names) != emitter_x0.keys(): + raise ValueError( + "emitter_x0 does not contain the parameters listed in param_names. " + "Please provide an initial value for each parameter." + ) + + def _validate_param_names(self, given_param_names: Iterable[str]) -> None: + if set(self._param_names) != set(given_param_names): + raise ValueError( + "The given param names must match the param names " + "initially passed to this sampler." + ) + + def _convert_to_pyribs_params(self, params: dict[str, float]) -> np.ndarray: + np_params = np.empty(len(self._param_names), dtype=float) + for i, p in enumerate(self._param_names): + np_params[i] = params[p] + return np_params + + def _convert_to_optuna_params(self, params: np.ndarray) -> dict[str, float]: + dict_params = {} + for i, p in enumerate(self._param_names): + dict_params[p] = params[i] + return dict_params + + def sample_relative( + self, study: Study, trial: FrozenTrial, search_space: dict[str, BaseDistribution] + ) -> dict[str, float]: + self._validate_param_names(search_space.keys()) + + # Note: Batch optimization means we need to enqueue trials. + solutions = self.scheduler.ask() + next_params = self._convert_to_optuna_params(solutions[0]) + for solution in solutions[1:]: + params = self._convert_to_optuna_params(solution) + study.enqueue_trial(params) + + return next_params + + def after_trial( + self, + study: Study, + trial: FrozenTrial, + state: TrialState, + values: Sequence[float] | None, + ) -> None: + self._validate_param_names(trial.params.keys()) + + # Store the trial result. + direction0 = study.directions[0] + minimize_in_optuna = direction0 == StudyDirection.MINIMIZE + if values is None: + raise RuntimeError( + f"{self.__class__.__name__} does not support Failed trials, " + f"but trial#{trial.number} failed." + ) + user_attrs = trial.user_attrs + if any(measure_name not in user_attrs for measure_name in self._measure_names): + raise KeyError( + f"All of measures in measure_names={self._measure_names} must be set to " + "trial.user_attrs. Please call `trial.set_user_attr(, )` " + "for each measure in your objective function." + ) + + self._raise_error_if_multi_objective(study) + modified_values = [ + float(values[0]), + float(user_attrs[self._measure_names[0]]), + float(user_attrs[self._measure_names[1]]), + ] + if minimize_in_optuna: + # The direction of the first objective (pyribs maximizes). + modified_values[0] = -values[0] + self._values_to_tell.append(modified_values) + self._stored_trial_numbers.append(trial.number) + + # If we have not retrieved the whole batch of solutions, then we should + # not tell() the results to the scheduler yet. + if len(self._values_to_tell) != self._batch_size: + return + + # Tell the batch results to external sampler once the batch is ready. + values_to_tell = np.asarray(self._values_to_tell)[np.argsort(self._stored_trial_numbers)] + self.scheduler.tell(objective=values_to_tell[:, 0], measures=values_to_tell[:, 1:]) + + # Empty the results. + self._values_to_tell = [] + self._stored_trial_numbers = [] diff --git a/package/samplers/gp_pims/sampler.py b/package/samplers/gp_pims/sampler.py index 92469fa1..6ee587ff 100644 --- a/package/samplers/gp_pims/sampler.py +++ b/package/samplers/gp_pims/sampler.py @@ -469,10 +469,7 @@ def acq(self, x: np.ndarray) -> np.ndarray: return ((self.maximums - mean) / std).ravel() -SimpleBaseSampler = optunahub.load_module("samplers/simple").SimpleBaseSampler - - -class PIMSSampler(SimpleBaseSampler): # type: ignore +class PIMSSampler(optunahub.samplers.SimpleBaseSampler): def __init__( self, search_space: dict[str, optuna.distributions.BaseDistribution], diff --git a/package/samplers/grey_wolf_optimization/grey_wolf_optimization.py b/package/samplers/grey_wolf_optimization/grey_wolf_optimization.py index edf65987..1017088d 100644 --- a/package/samplers/grey_wolf_optimization/grey_wolf_optimization.py +++ b/package/samplers/grey_wolf_optimization/grey_wolf_optimization.py @@ -10,7 +10,7 @@ import optunahub -class GreyWolfOptimizationSampler(optunahub.load_module("samplers/simple").SimpleBaseSampler): # type: ignore +class GreyWolfOptimizationSampler(optunahub.samplers.SimpleBaseSampler): def __init__( self, search_space: dict[str, BaseDistribution] | None = None, diff --git a/package/samplers/hebo/sampler.py b/package/samplers/hebo/sampler.py index 38423d24..b76cd5bf 100644 --- a/package/samplers/hebo/sampler.py +++ b/package/samplers/hebo/sampler.py @@ -17,10 +17,7 @@ from hebo.optimizers.hebo import HEBO -SimpleBaseSampler = optunahub.load_module("samplers/simple").SimpleBaseSampler - - -class HEBOSampler(SimpleBaseSampler): # type: ignore +class HEBOSampler(optunahub.samplers.SimpleBaseSampler): def __init__(self, search_space: dict[str, BaseDistribution]) -> None: super().__init__(search_space) self._hebo = HEBO(self._convert_to_hebo_design_space(search_space)) diff --git a/package/samplers/nelder_mead/nelder_mead.py b/package/samplers/nelder_mead/nelder_mead.py index 5b39d1e5..887da5b6 100644 --- a/package/samplers/nelder_mead/nelder_mead.py +++ b/package/samplers/nelder_mead/nelder_mead.py @@ -10,10 +10,7 @@ from .generate_initial_simplex import generate_initial_simplex -SimpleBaseSampler = optunahub.load_module("samplers/simple").SimpleBaseSampler - - -class NelderMeadSampler(SimpleBaseSampler): # type: ignore +class NelderMeadSampler(optunahub.samplers.SimpleBaseSampler): """A sampler based on the Nelder-Mead simplex algorithm. This algorithm does not support conditional parameters that make a tree-structured search space. Sampling for such parameters is delegated to independent_sampler (default: RandomSampler). diff --git a/package/samplers/nsgaii_with_tpe_warmup/LICENSE b/package/samplers/nsgaii_with_tpe_warmup/LICENSE new file mode 100644 index 00000000..f6547d35 --- /dev/null +++ b/package/samplers/nsgaii_with_tpe_warmup/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Shuhei Watanabe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/samplers/nsgaii_with_tpe_warmup/README.md b/package/samplers/nsgaii_with_tpe_warmup/README.md new file mode 100644 index 00000000..fe66e995 --- /dev/null +++ b/package/samplers/nsgaii_with_tpe_warmup/README.md @@ -0,0 +1,40 @@ +--- +author: Shuhei Watanabe +title: NSGAIISampler Using TPESampler for the Initialization +description: This sampler uses TPESampler for the initialization to warm start. +tags: [sampler, tpe, nsgaii, warmstart] +optuna_versions: [4.1.0] +license: MIT License +--- + +## Abstract + +This sampler uses `TPESampler` instead of `RandomSampler` for the initialization of `NSGAIISampler`. + +## APIs + +- NSGAIIWithTPEWarmupSampler + +This class takes the identical interface as the Optuna [`NSGAIISampler`](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.NSGAIISampler.html). + +## Example + +```python +from __future__ import annotations + +import optuna +import optunahub + + +def objective(trial: optuna.Trial) -> tuple[float, float]: + x = trial.suggest_float("x", -5, 5) + y = trial.suggest_float("y", -5, 5) + return x**2 + y**2, (x - 2) ** 2 + (y - 2) ** 2 + + +package_name = "samplers/nsgaii_with_tpe_warmup" +sampler = optunahub.load_module(package=package_name).NSGAIIWithTPEWarmupSampler() +study = optuna.create_study(sampler=sampler, directions=["minimize"]*2) +study.optimize(objective, n_trials=60) + +``` diff --git a/package/samplers/nsgaii_with_tpe_warmup/__init__.py b/package/samplers/nsgaii_with_tpe_warmup/__init__.py new file mode 100644 index 00000000..444f6f81 --- /dev/null +++ b/package/samplers/nsgaii_with_tpe_warmup/__init__.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import Any +from typing import TYPE_CHECKING + +from optuna.samplers import NSGAIISampler +from optuna.samplers import TPESampler +from optuna.samplers.nsgaii._sampler import _GENERATION_KEY + + +if TYPE_CHECKING: + from optuna import Study + from optuna.distributions import BaseDistribution + from optuna.trial import FrozenTrial + + +MAX_INT32 = (1 << 31) - 1 + + +class NSGAIIWithTPEWarmupSampler(NSGAIISampler): + def sample_relative( + self, + study: Study, + trial: FrozenTrial, + search_space: dict[str, BaseDistribution], + ) -> dict[str, Any]: + if trial.number < self._population_size: + seed = self._rng.rng.randint(MAX_INT32) + sampler = TPESampler( + multivariate=True, constraints_func=self._constraints_func, seed=seed + ) + study._storage.set_trial_system_attr(trial._trial_id, _GENERATION_KEY, 0) + return sampler.sample_relative(study, trial, search_space) + + return super().sample_relative(study, trial, search_space) diff --git a/package/samplers/nsgaii_with_tpe_warmup/example.py b/package/samplers/nsgaii_with_tpe_warmup/example.py new file mode 100644 index 00000000..07d7a3fe --- /dev/null +++ b/package/samplers/nsgaii_with_tpe_warmup/example.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import optuna +import optunahub + + +def objective(trial: optuna.Trial) -> tuple[float, float]: + x = trial.suggest_float("x", -5, 5) + y = trial.suggest_float("y", -5, 5) + return x**2 + y**2, (x - 2) ** 2 + (y - 2) ** 2 + + +package_name = "samplers/nsgaii_with_tpe_warmup" +sampler = optunahub.load_module(package=package_name).NSGAIIWithTPEWarmupSampler() +study = optuna.create_study(sampler=sampler, directions=["minimize"] * 2) +study.optimize(objective, n_trials=60) diff --git a/package/samplers/simple/README.md b/package/samplers/simple/README.md index eced9191..11bf34a9 100644 --- a/package/samplers/simple/README.md +++ b/package/samplers/simple/README.md @@ -7,6 +7,8 @@ optuna_versions: [3.6.1] license: MIT License --- +`SimpleBaseSampler` has been moved to [`optunahub.samplers`](https://optuna.github.io/optunahub/samplers.html). Please use [`optunahub.samplers.SimpleBaseSampler`](https://optuna.github.io/optunahub/generated/optunahub.samplers.SimpleBaseSampler.html#optunahub.samplers.SimpleBaseSampler) instead of this package. + ## Class or Function Names - SimpleBaseSampler @@ -14,8 +16,10 @@ license: MIT License ## Example ```python +import optunahub + class UserDefinedSampler( - optunahub.load_module("samplers/simple").SimpleBaseSampler + optunahub.samplers.SimpleBaseSampler ): ... ``` diff --git a/package/samplers/simple/example.py b/package/samplers/simple/example.py index 4e536dc3..ed976519 100644 --- a/package/samplers/simple/example.py +++ b/package/samplers/simple/example.py @@ -12,7 +12,7 @@ import optunahub -class UserDefinedSampler(optunahub.load_module("samplers/simple").SimpleBaseSampler): # type: ignore +class UserDefinedSampler(optunahub.samplers.SimpleBaseSampler): def __init__(self, search_space: dict[str, BaseDistribution] | None = None) -> None: super().__init__(search_space) self._rng = np.random.RandomState() diff --git a/package/samplers/smac_sampler/README.md b/package/samplers/smac_sampler/README.md index ac335ab8..460b9caa 100644 --- a/package/samplers/smac_sampler/README.md +++ b/package/samplers/smac_sampler/README.md @@ -20,16 +20,34 @@ pip install -r https://hub.optuna.org/samplers/smac_sampler/requirements.txt ## Example ```python -search_space = { - "x": FloatDistribution(-10, 10), - "y": IntDistribution(0, 10), +import optuna +import optunahub -} -sampler = SMACSampler(search_space) + +module = optunahub.load_module("samplers/smac_sampler") +SMACSampler = module.SMACSampler + + +def objective(trial: optuna.trial.Trial) -> float: + x = trial.suggest_float("x", -10, 10) + y = trial.suggest_int("y", -10, 10) + return x**2 + y**2 + + +n_trials = 100 +sampler = SMACSampler( + { + "x": optuna.distributions.FloatDistribution(-10, 10), + "y": optuna.distributions.IntDistribution(-10, 10), + }, + n_trials=n_trials, +) study = optuna.create_study(sampler=sampler) +study.optimize(objective, n_trials=n_trials) +print(study.best_trial.params) ``` -See [`example.py`](https://github.com/optuna/optunahub-registry/blob/main/package/samplers/hebo/example.py) for a full example. +See [`example.py`](https://github.com/optuna/optunahub-registry/blob/main/package/samplers/smac_sampler/example.py) for a full example. ![History Plot](images/smac_sampler_history.png "History Plot") ## Others diff --git a/package/samplers/smac_sampler/sampler.py b/package/samplers/smac_sampler/sampler.py index 10c43204..8c3f412a 100644 --- a/package/samplers/smac_sampler/sampler.py +++ b/package/samplers/smac_sampler/sampler.py @@ -34,12 +34,11 @@ from smac.scenario import Scenario -SimpleBaseSampler = optunahub.load_module("samplers/simple").SimpleBaseSampler _SMAC_INSTANCE_KEY = "smac:instance" _SMAC_SEED_KEY = "smac:seed" -class SMACSampler(SimpleBaseSampler): # type: ignore +class SMACSampler(optunahub.samplers.SimpleBaseSampler): """ A sampler that uses SMAC3 v2.2.0. @@ -56,6 +55,9 @@ class SMACSampler(SimpleBaseSampler): # type: ignore initial design. This argument does not have to be precise, but it is better to be exact for better performance. + seed: + Seed for random number generator. + If ``None`` is given, seed is generated randomly. surrogate_model_type: What model to use for the probabilistic model. Either "gp" (Gaussian process), "gp_mcmc" (Gaussian process with MCMC), or "rf" @@ -100,6 +102,7 @@ def __init__( self, search_space: dict[str, BaseDistribution], n_trials: int = 100, + seed: int | None = None, *, surrogate_model_type: str = "rf", acq_func_type: str = "ei_log", @@ -114,7 +117,9 @@ def __init__( ) -> None: super().__init__(search_space) self._cs, self._hp_scale_value = self._convert_to_config_space_design_space(search_space) - scenario = Scenario(configspace=self._cs, deterministic=True, n_trials=n_trials) + scenario = Scenario( + configspace=self._cs, deterministic=True, n_trials=n_trials, seed=seed or -1 + ) surrogate_model = self._get_surrogate_model( scenario, surrogate_model_type, diff --git a/package/samplers/whale_optimization/whale_optimization.py b/package/samplers/whale_optimization/whale_optimization.py index b8337ac0..7f805968 100644 --- a/package/samplers/whale_optimization/whale_optimization.py +++ b/package/samplers/whale_optimization/whale_optimization.py @@ -7,10 +7,7 @@ import optunahub -SimpleBaseSampler = optunahub.load_module("samplers/simple").SimpleBaseSampler - - -class WhaleOptimizationSampler(SimpleBaseSampler): # type: ignore +class WhaleOptimizationSampler(optunahub.samplers.SimpleBaseSampler): def __init__( self, search_space: dict[str, optuna.distributions.BaseDistribution] | None = None, diff --git a/recipes/002_registration.py b/recipes/002_registration.py index 484db2be..17ec7abc 100644 --- a/recipes/002_registration.py +++ b/recipes/002_registration.py @@ -80,13 +80,12 @@ - `Abstract `__ section that describes the summary of your package. It should be a markdown paragraph. This section will help attract potential users to your package. -- `Class or Function Names `__ section that describes the classes or functions provided by the package. If you provide multiple classes or functions, you should list them in this section. Note that the section must be a markdown list. If you provide only one class or function, you can simply write the class or function name. Note that the documentation of the classes or functions must be written in their docstrings. If you want to refer to the documentation, please leave the source code link, or write them in the following `Others` section. For example: +- `APIs `__ section that describes the APIs provided by the package. The documentation format is arbitrary, it is helpful to provide enough information about classes, functions, arguments, etc. to use your package. At least the important class/function names that you implemented should be listed here. .. code-block:: markdown - - `DemoSampler1` - - `DemoSampler2` - - `demo_function1` + - `ClassName(*, argment1: argument_type)` + - `argument1`: Description of `argument1`. - An `Installation `__ section that describes how to install the additional dependencies if required. If your package contains ``requirements.txt``, it will be available at ``https://hub.optuna.org/{category}/{your_package_name}/requirements.txt``. Then, the package dependencies can be installed as follows. diff --git a/template/README.md b/template/README.md index d4e40702..2ef91a7a 100644 --- a/template/README.md +++ b/template/README.md @@ -40,13 +40,28 @@ This section will help attract potential users to your package. This package provides a sampler based on Gaussian process-based Bayesian optimization. The sampler is highly sample-efficient, so it is suitable for computationally expensive optimization problems with a limited evaluation budget, such as hyperparameter optimization of machine learning algorithms. -## Class or Function Names +## APIs -Please fill in the class/function names which you implement here. +Please provide API documentation describing how to use your package's functionalities. +The documentation format is arbitrary, but at least the important class/function names that you implemented should be listed here. +More users will take advantage of your package by providing detailed and helpful documentation. **Example** -- GPSampler +- `MoCmaSampler(*, search_space: dict[str, BaseDistribution] | None = None, popsize: int | None = None, seed: int | None = None)` + - `search_space`: A dictionary containing the search space that defines the parameter space. The keys are the parameter names and the values are [the parameter's distribution](https://optuna.readthedocs.io/en/stable/reference/distributions.html). If the search space is not provided, the sampler will infer the search space dynamically. + Example: + ```python + search_space = { + "x": optuna.distributions.FloatDistribution(-5, 5), + "y": optuna.distributions.FloatDistribution(-5, 5), + } + MoCmaSampler(search_space=search_space) + ``` + - `popsize`: Population size of the CMA-ES algorithm. If not provided, the population size will be set based on the search space dimensionality. If you have a sufficient evaluation budget, it is recommended to increase the popsize. + - `seed`: Seed for random number generator. + +Note that because of the limitation of the algorithm, only non-conditional numerical parameters can be sampled by the MO-CMA-ES algorithm, and categorical and conditional parameters are handled by random search. ## Installation diff --git a/template/example.py b/template/example.py new file mode 100644 index 00000000..b7d249d4 --- /dev/null +++ b/template/example.py @@ -0,0 +1,38 @@ +""" +This example is only for sampler. +You can verify your sampler code using this file as well. +Please feel free to remove this file if necessary. +""" + +from __future__ import annotations + +import optuna +import optunahub + + +def objective(trial: optuna.Trial) -> float: + x = trial.suggest_float("x", -5, 5) + y = trial.suggest_float("y", -5, 5) + return x**2 + y**2 + + +# TODO: Change package_name to test your package. +package_name = "samplers/your_sampler" +test_local = True + +if test_local: + # This is an example of how to load a sampler from your local optunahub-registry. + sampler = optunahub.load_local_module( + package=package_name, + registry_root="./", # Path to the root of the optunahub-registry. + ).YourSampler() +else: + # This is an example of how to load a sampler from your fork of the optunahub-registry. + # Please remove repo_owner and ref arguments before submitting a pull request. + sampler = optunahub.load_module( + package=package_name, repo_owner="Your GitHub Account ID", ref="Your Git Branch Name" + ).YourSampler() + +study = optuna.create_study(sampler=sampler) +study.optimize(objective, n_trials=30) +print(study.best_trials)