diff --git a/package/samplers/cmamae/README.md b/package/samplers/cmamae/README.md index 03736fa3..7801df9f 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("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, @@ -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..2903a81a 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("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, @@ -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..77f119ff 100644 --- a/package/samplers/cmamae/sampler.py +++ b/package/samplers/cmamae/sampler.py @@ -36,12 +36,13 @@ 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. + 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 @@ -67,6 +68,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 +79,13 @@ 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() + 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} @@ -182,8 +190,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 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]