diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6d3b78b82..ddda4bc0a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -43,8 +43,12 @@ jobs: pip install -e . python -c "import flaml" pip install -e .[test] - - name: If linux or mac, install ray - if: matrix.os == 'macOS-latest' || matrix.os == 'ubuntu-latest' + - name: If linux, install ray 2 + if: matrix.os == 'ubuntu-latest' + run: | + pip install ray[tune] + - name: If mac, install ray + if: matrix.os == 'macOS-latest' run: | pip install -e .[ray] - name: If linux or mac, install prophet on python < 3.9 diff --git a/flaml/__init__.py b/flaml/__init__.py index 7f2862d7d..87bb428b3 100644 --- a/flaml/__init__.py +++ b/flaml/__init__.py @@ -1,4 +1,4 @@ -from flaml.searcher import CFO, BlendSearch, FLOW2, BlendSearchTuner +from flaml.searcher import CFO, BlendSearch, FLOW2, BlendSearchTuner, RandomSearch from flaml.automl import AutoML, logger_formatter from flaml.onlineml.autovw import AutoVW from flaml.version import __version__ diff --git a/flaml/automl.py b/flaml/automl.py index 78ea15c4f..b66d6da1f 100644 --- a/flaml/automl.py +++ b/flaml/automl.py @@ -160,7 +160,7 @@ class SearchState: if starting_point_len > len(starting_point): logger.warning( "Starting points outside of the search space are removed. " - f"Remaining starting points: {starting_point}" + f"Remaining starting points for {learner_class}: {starting_point}" ) starting_point = starting_point or None @@ -2935,8 +2935,11 @@ class AutoML(BaseEstimator): from ray import __version__ as ray_version assert ray_version >= "1.10.0" + if ray_version.startswith("1."): + from ray.tune.suggest import ConcurrencyLimiter + else: + from ray.tune.search import ConcurrencyLimiter import ray - from ray.tune.suggest import ConcurrencyLimiter except (ImportError, AssertionError): raise ImportError( "n_concurrent_trial>1 or use_ray=True requires installation of ray. " @@ -2947,14 +2950,16 @@ class AutoML(BaseEstimator): elif "bs" == self._hpo_method: from flaml import BlendSearch as SearchAlgo elif "random" == self._hpo_method: - from ray.tune.suggest import BasicVariantGenerator as SearchAlgo - from ray.tune.sample import Domain + from flaml import RandomSearch as SearchAlgo elif "optuna" == self._hpo_method: try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune.suggest.optuna import OptunaSearch as SearchAlgo + if ray_version.startswith("1."): + from ray.tune.suggest.optuna import OptunaSearch as SearchAlgo + else: + from ray.tune.search.optuna import OptunaSearch as SearchAlgo except (ImportError, AssertionError): from .searcher.suggestion import OptunaSearch as SearchAlgo else: @@ -2963,77 +2968,56 @@ class AutoML(BaseEstimator): "'auto', 'cfo' and 'bs' are supported." ) space = self.search_space - if self._hpo_method == "random": - # Any point in points_to_evaluate must consist of hyperparamters - # that are tunable, which can be identified by checking whether - # the corresponding value in the search space is an instance of - # the 'Domain' class from flaml or ray.tune - points_to_evaluate = self.points_to_evaluate.copy() - to_del = [] - for k, v in space.items(): - if not isinstance(v, Domain): - to_del.append(k) - for k in to_del: - for p in points_to_evaluate: - if k in p: - del p[k] + self._state.time_from_start = time.time() - self._start_time_flag + time_left = self._state.time_budget - self._state.time_from_start + if self._hpo_method != "optuna": + min_resource = self.min_resource + if isinstance(min_resource, dict): + _min_resource_set = set(min_resource.values()) + min_resource_all_estimator = min(_min_resource_set) + if len(_min_resource_set) > 1: + logger.warning( + "Using the min FLAML_sample_size of all the provided starting points as the starting sample size in the case of parallel search." + ) + else: + min_resource_all_estimator = min_resource search_alg = SearchAlgo( - max_concurrent=self._n_concurrent_trials, - points_to_evaluate=points_to_evaluate, + metric="val_loss", + space=space, + low_cost_partial_config=self.low_cost_partial_config, + points_to_evaluate=self.points_to_evaluate, + cat_hp_cost=self.cat_hp_cost, + resource_attr=self.resource_attr, + min_resource=min_resource_all_estimator, + max_resource=self.max_resource, + config_constraints=[ + (partial(size, self._state), "<=", self._mem_thres) + ], + metric_constraints=self.metric_constraints, + seed=self._seed, + time_budget_s=time_left, ) else: - self._state.time_from_start = time.time() - self._start_time_flag - time_left = self._state.time_budget - self._state.time_from_start - if self._hpo_method != "optuna": - min_resource = self.min_resource - if isinstance(min_resource, dict): - _min_resource_set = set(min_resource.values()) - min_resource_all_estimator = min(_min_resource_set) - if len(_min_resource_set) > 1: - logger.warning( - "Using the min FLAML_sample_size of all the provided starting points as the starting sample size in the case of parallel search." - ) - else: - min_resource_all_estimator = min_resource - search_alg = SearchAlgo( - metric="val_loss", - space=space, - low_cost_partial_config=self.low_cost_partial_config, - points_to_evaluate=self.points_to_evaluate, - cat_hp_cost=self.cat_hp_cost, - resource_attr=self.resource_attr, - min_resource=min_resource_all_estimator, - max_resource=self.max_resource, - config_constraints=[ - (partial(size, self._state), "<=", self._mem_thres) - ], - metric_constraints=self.metric_constraints, - seed=self._seed, - time_budget_s=time_left, - ) - else: - # if self._hpo_method is bo, sometimes the search space and the initial config dimension do not match - # need to remove the extra keys from the search space to be consistent with the initial config - converted_space = SearchAlgo.convert_search_space(space) + # if self._hpo_method is bo, sometimes the search space and the initial config dimension do not match + # need to remove the extra keys from the search space to be consistent with the initial config + converted_space = SearchAlgo.convert_search_space(space) - removed_keys = set(space.keys()).difference(converted_space.keys()) - new_points_to_evaluate = [] - for idx in range(len(self.points_to_evaluate)): - r = self.points_to_evaluate[idx].copy() - for each_key in removed_keys: - r.pop(each_key) - new_points_to_evaluate.append(r) + removed_keys = set(space.keys()).difference(converted_space.keys()) + new_points_to_evaluate = [] + for idx in range(len(self.points_to_evaluate)): + r = self.points_to_evaluate[idx].copy() + for each_key in removed_keys: + r.pop(each_key) + new_points_to_evaluate.append(r) - search_alg = SearchAlgo( - metric="val_loss", - mode="min", - points_to_evaluate=[ - p - for p in new_points_to_evaluate - if len(p) == len(converted_space) - ], - ) - search_alg = ConcurrencyLimiter(search_alg, self._n_concurrent_trials) + search_alg = SearchAlgo( + metric="val_loss", + mode="min", + points_to_evaluate=[ + p for p in new_points_to_evaluate if len(p) == len(converted_space) + ], + ) + search_alg = ConcurrencyLimiter(search_alg, self._n_concurrent_trials) resources_per_trial = self._state.resources_per_trial analysis = ray.tune.run( @@ -3136,7 +3120,10 @@ class AutoML(BaseEstimator): from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune.suggest import ConcurrencyLimiter + if ray_version.startswith("1."): + from ray.tune.suggest import ConcurrencyLimiter + else: + from ray.tune.search import ConcurrencyLimiter except (ImportError, AssertionError): from .searcher.suggestion import ConcurrencyLimiter if self._hpo_method in ("cfo", "grid"): @@ -3146,7 +3133,10 @@ class AutoML(BaseEstimator): from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune.suggest.optuna import OptunaSearch as SearchAlgo + if ray_version.startswith("1."): + from ray.tune.suggest.optuna import OptunaSearch as SearchAlgo + else: + from ray.tune.search.optuna import OptunaSearch as SearchAlgo except (ImportError, AssertionError): from .searcher.suggestion import OptunaSearch as SearchAlgo elif "bs" == self._hpo_method: diff --git a/flaml/searcher/blendsearch.py b/flaml/searcher/blendsearch.py index 54db6e37f..269de2612 100644 --- a/flaml/searcher/blendsearch.py +++ b/flaml/searcher/blendsearch.py @@ -11,8 +11,12 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune.suggest import Searcher - from ray.tune.suggest.optuna import OptunaSearch as GlobalSearch + if ray_version.startswith("1."): + from ray.tune.suggest import Searcher + from ray.tune.suggest.optuna import OptunaSearch as GlobalSearch + else: + from ray.tune.search import Searcher + from ray.tune.search.optuna import OptunaSearch as GlobalSearch except (ImportError, AssertionError): from .suggestion import Searcher from .suggestion import OptunaSearch as GlobalSearch diff --git a/flaml/searcher/flow2.py b/flaml/searcher/flow2.py index d649e7179..d5722db21 100644 --- a/flaml/searcher/flow2.py +++ b/flaml/searcher/flow2.py @@ -10,8 +10,11 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.0.0" - from ray.tune.suggest import Searcher - from ray.tune import sample + if ray_version.startswith("1."): + from ray.tune.suggest import Searcher + from ray.tune import sample + else: + from ray.tune.search import Searcher, sample from ray.tune.utils.util import flatten_dict, unflatten_dict except (ImportError, AssertionError): from .suggestion import Searcher diff --git a/flaml/searcher/search_thread.py b/flaml/searcher/search_thread.py index 4bd2d7c19..0d39507bd 100644 --- a/flaml/searcher/search_thread.py +++ b/flaml/searcher/search_thread.py @@ -9,7 +9,10 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune.suggest import Searcher + if ray_version.startswith("1."): + from ray.tune.suggest import Searcher + else: + from ray.tune.search import Searcher except (ImportError, AssertionError): from .suggestion import Searcher from .flow2 import FLOW2 diff --git a/flaml/searcher/suggestion.py b/flaml/searcher/suggestion.py index 59e50c2d4..30105dcae 100644 --- a/flaml/searcher/suggestion.py +++ b/flaml/searcher/suggestion.py @@ -179,7 +179,7 @@ class ConcurrencyLimiter(Searcher): to finish before updating the underlying searcher. Example: ```python - from ray.tune.suggest import ConcurrencyLimiter + from ray.tune.suggest import ConcurrencyLimiter # ray version < 2 search_alg = HyperOptSearch(metric="accuracy") search_alg = ConcurrencyLimiter(search_alg, max_concurrent=2) tune.run(trainable, search_alg=search_alg) @@ -411,7 +411,7 @@ class OptunaSearch(Searcher): Tune automatically converts search spaces to Optuna's format: ````python - from ray.tune.suggest.optuna import OptunaSearch + from ray.tune.suggest.optuna import OptunaSearch # ray version < 2 config = { "a": tune.uniform(6, 8), "b": tune.loguniform(1e-4, 1e-2)} optuna_search = OptunaSearch(metric="loss", mode="min") @@ -422,7 +422,7 @@ class OptunaSearch(Searcher): look like this: ```python - from ray.tune.suggest.optuna import OptunaSearch + from ray.tune.suggest.optuna import OptunaSearch # ray version < 2 import optuna config = { "a": optuna.distributions.UniformDistribution(6, 8), "b": optuna.distributions.LogUniformDistribution(1e-4, 1e-2)} diff --git a/flaml/searcher/variant_generator.py b/flaml/searcher/variant_generator.py index cd9eb56bc..388b0b574 100644 --- a/flaml/searcher/variant_generator.py +++ b/flaml/searcher/variant_generator.py @@ -23,7 +23,12 @@ import random from ..tune.sample import Categorical, Domain, RandomState try: - from ray.tune.sample import Domain as RayDomain + from ray import __version__ as ray_version + + if ray_version.startswith("1."): + from ray.tune.sample import Domain as RayDomain + else: + from ray.tune.search.sample import Domain as RayDomain except ImportError: RayDomain = Domain diff --git a/flaml/tune/__init__.py b/flaml/tune/__init__.py index 3d6c89101..5e65d8e4b 100644 --- a/flaml/tune/__init__.py +++ b/flaml/tune/__init__.py @@ -13,8 +13,12 @@ try: qloguniform, lograndint, qlograndint, - sample, ) + + if ray_version.startswith("1."): + from ray.tune import sample + else: + from ray.tune.search import sample except (ImportError, AssertionError): from .sample import ( uniform, diff --git a/flaml/tune/sample.py b/flaml/tune/sample.py index c31f36622..0ef50837d 100644 --- a/flaml/tune/sample.py +++ b/flaml/tune/sample.py @@ -36,7 +36,12 @@ except AttributeError: logger = logging.getLogger(__name__) try: - from ray.tune.sample import _BackwardsCompatibleNumpyRng + from ray import __version__ as ray_version + + if ray_version.startswith("1."): + from ray.tune.sample import _BackwardsCompatibleNumpyRng + else: + from ray.tune.search.sample import _BackwardsCompatibleNumpyRng except ImportError: class _BackwardsCompatibleNumpyRng: diff --git a/flaml/tune/space.py b/flaml/tune/space.py index 7e2bf4de3..1c0b5c184 100644 --- a/flaml/tune/space.py +++ b/flaml/tune/space.py @@ -2,8 +2,12 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune import sample - from ray.tune.suggest.variant_generator import generate_variants + if ray_version.startswith("1."): + from ray.tune import sample + from ray.tune.suggest.variant_generator import generate_variants + else: + from ray.tune.search import sample + from ray.tune.search.variant_generator import generate_variants except (ImportError, AssertionError): from . import sample from ..searcher.variant_generator import generate_variants diff --git a/flaml/tune/tune.py b/flaml/tune/tune.py index d112da932..9209dccd1 100644 --- a/flaml/tune/tune.py +++ b/flaml/tune/tune.py @@ -404,7 +404,10 @@ def run( metric = metric or search_alg.metric or DEFAULT_METRIC mode = mode or search_alg.mode if ray_import: - from ray.tune.suggest import ConcurrencyLimiter + if ray_version.startswith("1."): + from ray.tune.suggest import ConcurrencyLimiter + else: + from ray.tune.search import ConcurrencyLimiter else: from flaml.searcher.suggestion import ConcurrencyLimiter if ( diff --git a/flaml/tune/utils.py b/flaml/tune/utils.py index 53dfba3a7..b67ca342f 100644 --- a/flaml/tune/utils.py +++ b/flaml/tune/utils.py @@ -4,7 +4,10 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune import sample + if ray_version.startswith("1."): + from ray.tune import sample + else: + from ray.tune.search import sample except (ImportError, AssertionError): from . import sample diff --git a/flaml/version.py b/flaml/version.py index bd538f76e..66c607f6d 100644 --- a/flaml/version.py +++ b/flaml/version.py @@ -1 +1 @@ -__version__ = "1.0.12" +__version__ = "1.0.13" diff --git a/test/tune/example.py b/test/tune/example.py index 9b7389888..3d541f120 100644 --- a/test/tune/example.py +++ b/test/tune/example.py @@ -22,9 +22,15 @@ def easy_objective(config): def test_blendsearch_tune(smoke_test=True): try: from ray import tune - from ray.tune.suggest import ConcurrencyLimiter from ray.tune.schedulers import AsyncHyperBandScheduler - from ray.tune.suggest.flaml import BlendSearch + from ray import __version__ as ray_version + + if ray_version.startswith("1."): + from ray.tune.suggest import ConcurrencyLimiter + from ray.tune.suggest.flaml import BlendSearch + else: + from ray.tune.search import ConcurrencyLimiter + from ray.tune.search.flaml import BlendSearch except ImportError: print("ray[tune] is not installed, skipping test") return diff --git a/test/tune/test_flaml_raytune_consistency.py b/test/tune/test_flaml_raytune_consistency.py index 0e011279f..98b36e5df 100644 --- a/test/tune/test_flaml_raytune_consistency.py +++ b/test/tune/test_flaml_raytune_consistency.py @@ -52,7 +52,12 @@ def _test_flaml_raytune_consistency( num_samples=-1, max_concurrent_trials=1, searcher_name="cfo" ): try: - from ray import tune as raytune + from ray import tune as raytune, __version__ as ray_version + + if ray_version.startswith("1."): + from ray.tune.suggest import ConcurrencyLimiter + else: + from ray.tune.search import ConcurrencyLimiter except ImportError: print( "skip _test_flaml_raytune_consistency because ray tune cannot be imported." @@ -78,7 +83,6 @@ def _test_flaml_raytune_consistency( print(analysis.best_trial.last_result) # the best trial's result searcher = setup_searcher(searcher_name) - from ray.tune.suggest import ConcurrencyLimiter search_alg = ConcurrencyLimiter(searcher, max_concurrent_trials) analysis = raytune.run( diff --git a/test/tune/test_searcher.py b/test/tune/test_searcher.py index c378eb706..7c1e70c98 100644 --- a/test/tune/test_searcher.py +++ b/test/tune/test_searcher.py @@ -5,7 +5,10 @@ try: from ray import __version__ as ray_version assert ray_version >= "1.10.0" - from ray.tune import sample + if ray_version.startswith("1."): + from ray.tune import sample + else: + from ray.tune.search import sample use_ray = True except (ImportError, AssertionError):