From 82e866e6cafa34c0d54338e279fd87e584a74576 Mon Sep 17 00:00:00 2001 From: nabenabe0928 Date: Mon, 25 Nov 2024 07:27:05 +0100 Subject: [PATCH 1/4] Follow the Optuna convention --- package/samplers/cmamae/README.md | 26 ++++++++++++-------------- package/samplers/cmamae/example.py | 20 ++++++-------------- package/samplers/cmamae/sampler.py | 25 ++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/package/samplers/cmamae/README.md b/package/samplers/cmamae/README.md index 03736fa3..f712515c 100644 --- a/package/samplers/cmamae/README.md +++ b/package/samplers/cmamae/README.md @@ -31,6 +31,12 @@ quality diversity algorithms using pyribs. - 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 @@ -42,22 +48,24 @@ $ pip install ribs ```python import optuna import optunahub -from optuna.study import StudyDirection module = optunahub.load_module("samplers/cmamae") CmaMaeSampler = module.CmaMaeSampler -def objective(trial: optuna.trial.Trial) -> tuple[float, float, float]: +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) - return x**2 + y**2, x, y + trial.set_user_attr("x", x) + trial.set_user_attr("y", y) + return x**2 + y**2 if __name__ == "__main__": sampler = CmaMaeSampler( param_names=["x", "y"], + measure_names=["x", "y"], archive_dims=[20, 20], archive_ranges=[(-1, 1), (-1, 1)], archive_learning_rate=0.1, @@ -70,17 +78,7 @@ if __name__ == "__main__": emitter_sigma0=0.1, emitter_batch_size=20, ) - study = optuna.create_study( - sampler=sampler, - directions=[ - StudyDirection.MINIMIZE, - # The remaining directions are for the measures, which do not have - # an optimization direction. However, we set MINIMIZE as a - # placeholder direction. - StudyDirection.MINIMIZE, - StudyDirection.MINIMIZE, - ], - ) + study = optuna.create_study(sampler=sampler) study.optimize(objective, n_trials=10000) ``` diff --git a/package/samplers/cmamae/example.py b/package/samplers/cmamae/example.py index 5b68f7c0..a78efe8c 100644 --- a/package/samplers/cmamae/example.py +++ b/package/samplers/cmamae/example.py @@ -1,5 +1,4 @@ import optuna -from optuna.study import StudyDirection import optunahub @@ -7,16 +6,19 @@ CmaMaeSampler = module.CmaMaeSampler -def objective(trial: optuna.trial.Trial) -> tuple[float, float, float]: +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) - return x**2 + y**2, x, y + trial.set_user_attr("x", x) + trial.set_user_attr("y", y) + return x**2 + y**2 if __name__ == "__main__": sampler = CmaMaeSampler( param_names=["x", "y"], + measure_names=["x", "y"], archive_dims=[20, 20], archive_ranges=[(-1, 1), (-1, 1)], archive_learning_rate=0.1, @@ -29,15 +31,5 @@ def objective(trial: optuna.trial.Trial) -> tuple[float, float, float]: emitter_sigma0=0.1, emitter_batch_size=20, ) - study = optuna.create_study( - sampler=sampler, - directions=[ - StudyDirection.MINIMIZE, - # The remaining directions are for the measures, which do not have - # an optimization direction. However, we set MINIMIZE as a - # placeholder direction. - StudyDirection.MINIMIZE, - StudyDirection.MINIMIZE, - ], - ) + study = optuna.create_study(sampler=sampler) study.optimize(objective, n_trials=10000) diff --git a/package/samplers/cmamae/sampler.py b/package/samplers/cmamae/sampler.py index 15e0e4f2..006a185d 100644 --- a/package/samplers/cmamae/sampler.py +++ b/package/samplers/cmamae/sampler.py @@ -67,6 +67,7 @@ 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, @@ -77,7 +78,8 @@ def __init__( emitter_batch_size: int, ) -> None: self._validate_params(param_names, emitter_x0) - self._param_names = param_names[:] + self._param_names = param_names.copy() + self._measure_names = measure_names.copy() # NOTE: SimpleBaseSampler must know Optuna search_space information. search_space = {name: FloatDistribution(-1e9, 1e9) for name in self._param_names} @@ -182,8 +184,25 @@ def after_trial( # Store the trial result. direction0 = study.directions[0] minimize_in_optuna = direction0 == StudyDirection.MINIMIZE - assert values is not None, "MyPy redefinition." - modified_values = list([float(v) for v in values]) + 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 measure in measure_names={self._measure_names} must be set to " + "trial.user_attrs. Please call trial.set_user_attr(, ) " + "for each measure." + ) + + 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] From 8d346a9cfcdaeeb2b705a37b0f23c49918f66818 Mon Sep 17 00:00:00 2001 From: nabenabe0928 Date: Mon, 25 Nov 2024 08:41:48 +0100 Subject: [PATCH 2/4] Apply bryon's comments --- package/samplers/cmamae/README.md | 6 +++--- package/samplers/cmamae/example.py | 6 +++--- package/samplers/cmamae/sampler.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package/samplers/cmamae/README.md b/package/samplers/cmamae/README.md index f712515c..7801df9f 100644 --- a/package/samplers/cmamae/README.md +++ b/package/samplers/cmamae/README.md @@ -57,15 +57,15 @@ 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("x", x) - trial.set_user_attr("y", y) + 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=["x", "y"], + measure_names=["m0", "m1"], archive_dims=[20, 20], archive_ranges=[(-1, 1), (-1, 1)], archive_learning_rate=0.1, diff --git a/package/samplers/cmamae/example.py b/package/samplers/cmamae/example.py index a78efe8c..2903a81a 100644 --- a/package/samplers/cmamae/example.py +++ b/package/samplers/cmamae/example.py @@ -10,15 +10,15 @@ 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("x", x) - trial.set_user_attr("y", y) + 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=["x", "y"], + measure_names=["m0", "m1"], archive_dims=[20, 20], archive_ranges=[(-1, 1), (-1, 1)], archive_learning_rate=0.1, diff --git a/package/samplers/cmamae/sampler.py b/package/samplers/cmamae/sampler.py index 006a185d..a0106326 100644 --- a/package/samplers/cmamae/sampler.py +++ b/package/samplers/cmamae/sampler.py @@ -36,9 +36,9 @@ class CmaMaeSampler(optunahub.samplers.SimpleBaseSampler): However, it is possible to implement many variations of CMA-MAE and other quality diversity algorithms using pyribs. - Note that this sampler assumes the objective function will return a list of - values. The first value will be the objective, and the remaining values will - be the measures. + 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. From 1d303b3e9a9d75095debbdcf91edc3bec3d46243 Mon Sep 17 00:00:00 2001 From: nabenabe0928 Date: Mon, 25 Nov 2024 08:43:35 +0100 Subject: [PATCH 3/4] Refactor --- package/samplers/cmamae/sampler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package/samplers/cmamae/sampler.py b/package/samplers/cmamae/sampler.py index a0106326..34fa32f3 100644 --- a/package/samplers/cmamae/sampler.py +++ b/package/samplers/cmamae/sampler.py @@ -42,6 +42,7 @@ class CmaMaeSampler(optunahub.samplers.SimpleBaseSampler): 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 From 8462391824285ede93eaf7d2678f5761976bb35c Mon Sep 17 00:00:00 2001 From: nabenabe0928 Date: Wed, 27 Nov 2024 15:14:46 +0100 Subject: [PATCH 4/4] Fix --- package/samplers/cmamae/sampler.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/package/samplers/cmamae/sampler.py b/package/samplers/cmamae/sampler.py index 34fa32f3..77f119ff 100644 --- a/package/samplers/cmamae/sampler.py +++ b/package/samplers/cmamae/sampler.py @@ -81,6 +81,11 @@ def __init__( 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} @@ -193,9 +198,9 @@ def after_trial( 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 measure in measure_names={self._measure_names} must be set to " - "trial.user_attrs. Please call trial.set_user_attr(, ) " - "for each measure." + 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)